diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 977c58c443..f5950f3aca 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -962,86 +962,6 @@ } } ], - "./monitoring/loadtest/locust_files/ISA.py": [ - { - "code": "reportCallIssue", - "range": { - "startColumn": 25, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 43, - "endColumn": 53, - "lineCount": 1 - } - } - ], - "./monitoring/loadtest/locust_files/Sub.py": [ - { - "code": "reportCallIssue", - "range": { - "startColumn": 25, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 43, - "endColumn": 53, - "lineCount": 1 - } - } - ], - "./monitoring/loadtest/locust_files/client.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 15, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 41, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 33, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 41, - "lineCount": 1 - } - } - ], - "./monitoring/mock_uss/__init__.py": [ - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 58, - "lineCount": 1 - } - } - ], "./monitoring/mock_uss/auth.py": [ { "code": "reportArgumentType", @@ -1070,24 +990,6 @@ } } ], - "./monitoring/mock_uss/dynamic_configuration/routes.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 5, - "lineCount": 3 - } - } - ], "./monitoring/mock_uss/f3548v21/flight_planning.py": [ { "code": "reportOperatorIssue", @@ -1225,14 +1127,6 @@ "lineCount": 1 } }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 32, - "endColumn": 41, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -1265,22 +1159,6 @@ "lineCount": 1 } }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 12, - "endColumn": 62, - "lineCount": 2 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 62, - "lineCount": 2 - } - }, { "code": "reportOperatorIssue", "range": { @@ -1306,16 +1184,6 @@ } } ], - "./monitoring/mock_uss/f3548v21/routes_scd.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 49, - "endColumn": 67, - "lineCount": 1 - } - } - ], "./monitoring/mock_uss/flight_planning/routes.py": [ { "code": "reportReturnType", @@ -1333,22 +1201,6 @@ "lineCount": 1 } }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 30, - "lineCount": 1 - } - }, { "code": "reportReturnType", "range": { @@ -1436,42 +1288,6 @@ } } ], - "./monitoring/mock_uss/geoawareness/routes_geoawareness.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 51, - "lineCount": 1 - } - } - ], - "./monitoring/mock_uss/geoawareness/routes_geospatial_map.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 30, - "lineCount": 1 - } - } - ], "./monitoring/mock_uss/interaction_logging/logger.py": [ { "code": "reportAttributeAccessIssue", @@ -1490,24 +1306,6 @@ } } ], - "./monitoring/mock_uss/msgsigning/__init__.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 14, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 39, - "endColumn": 61, - "lineCount": 1 - } - } - ], "./monitoring/mock_uss/msgsigning/routes_msgsigning.py": [ { "code": "reportCallIssue", @@ -1524,14 +1322,6 @@ "endColumn": 52, "lineCount": 1 } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 33, - "endColumn": 51, - "lineCount": 1 - } } ], "./monitoring/mock_uss/riddp/clustering.py": [ @@ -1552,24 +1342,6 @@ } } ], - "./monitoring/mock_uss/riddp/routes_behavior.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 43, - "lineCount": 1 - } - } - ], "./monitoring/mock_uss/riddp/routes_observation.py": [ { "code": "reportOptionalMemberAccess", @@ -1802,14 +1574,6 @@ "endColumn": 87, "lineCount": 1 } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 32, - "lineCount": 1 - } } ], "./monitoring/mock_uss/ridsp/behavior.py": [ @@ -1846,24 +1610,6 @@ } } ], - "./monitoring/mock_uss/ridsp/routes_behavior.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 43, - "lineCount": 1 - } - } - ], "./monitoring/mock_uss/ridsp/routes_injection.py": [ { "code": "reportOperatorIssue", @@ -1889,14 +1635,6 @@ "lineCount": 1 } }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 38, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -1928,83 +1666,11 @@ "endColumn": 120, "lineCount": 1 } - }, + } + ], + "./monitoring/mock_uss/ridsp/routes_ridsp_v19.py": [ { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 12, - "endColumn": 86, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 12, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 16, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 27, - "lineCount": 1 - } - } - ], - "./monitoring/mock_uss/ridsp/routes_ridsp_v19.py": [ - { - "code": "reportArgumentType", + "code": "reportArgumentType", "range": { "startColumn": 27, "endColumn": 48, @@ -2071,14 +1737,6 @@ } ], "./monitoring/mock_uss/ridsp/user_notifications.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 65, - "endColumn": 70, - "lineCount": 1 - } - }, { "code": "reportCallIssue", "range": { @@ -2110,81 +1768,9 @@ "endColumn": 61, "lineCount": 1 } - }, - { - "code": "reportInvalidTypeForm", - "range": { - "startColumn": 10, - "endColumn": 18, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeForm", - "range": { - "startColumn": 45, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 51, - "lineCount": 1 - } } ], "./monitoring/mock_uss/scd_injection/routes_injection.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 55, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 68, - "endColumn": 71, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 30, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -2201,14 +1787,6 @@ "lineCount": 1 } }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 30, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -2233,14 +1811,6 @@ "lineCount": 1 } }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 50, - "endColumn": 67, - "lineCount": 1 - } - }, { "code": "reportOptionalMemberAccess", "range": { @@ -2284,37 +1854,27 @@ } } ], - "./monitoring/mock_uss/tracer/context.py": [ + "./monitoring/monitorlib/auth.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 44, + "startColumn": 68, + "endColumn": 81, "lineCount": 1 } }, { - "code": "reportAssignmentType", - "range": { - "startColumn": 24, - "endColumn": 44, - "lineCount": 1 - } - } - ], - "./monitoring/mock_uss/tracer/diff.py": [ - { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 49, - "endColumn": 58, + "startColumn": 26, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAssignmentType", "range": { - "startColumn": 60, + "startColumn": 31, "endColumn": 69, "lineCount": 1 } @@ -2322,82 +1882,76 @@ { "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 60, - "endColumn": 69, + "startColumn": 67, + "endColumn": 87, "lineCount": 1 } } ], - "./monitoring/mock_uss/tracer/kml.py": [ + "./monitoring/monitorlib/auth_validation.py": [ { "code": "reportAttributeAccessIssue", "range": { - "startColumn": 17, - "endColumn": 22, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 65, + "startColumn": 60, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 9, - "endColumn": 42, + "startColumn": 34, + "endColumn": 37, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/clients/flight_planning/client_scd.py": [ { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 1, - "endColumn": 59, + "startColumn": 41, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 34, + "startColumn": 43, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 30, - "endColumn": 55, + "startColumn": 55, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 27, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 52, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, @@ -2405,7 +1959,7 @@ "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 33, + "endColumn": 35, "lineCount": 1 } }, @@ -2413,752 +1967,774 @@ "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 40, + "endColumn": 35, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/clients/flight_planning/client_v1.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 43, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 43, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportInvalidTypeForm", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 34, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 59, - "endColumn": 67, + "startColumn": 16, + "endColumn": 35, + "lineCount": 1 + } + } + ], + "./monitoring/monitorlib/clients/flight_planning/flight_info.py": [ + { + "code": "reportOptionalIterable", + "range": { + "startColumn": 63, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 53, + "startColumn": 39, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 61, - "endColumn": 78, + "startColumn": 43, + "endColumn": 62, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 30, - "endColumn": 38, + "startColumn": 66, + "endColumn": 71, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 32, - "endColumn": 40, + "startColumn": 39, + "endColumn": 46, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 42, + "startColumn": 39, + "endColumn": 58, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/tracer/observation_area_operations.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 38, + "startColumn": 64, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 56, - "endColumn": 82, + "startColumn": 60, + "endColumn": 65, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/tracer/observation_areas.py": [ + }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 83, + "startColumn": 48, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 83, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } } ], - "./monitoring/mock_uss/tracer/routes/observation_areas.py": [ + "./monitoring/monitorlib/clients/flight_planning/flight_info_template.py": [ { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 1, - "endColumn": 59, + "startColumn": 27, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 25, - "endColumn": 69, + "startColumn": 27, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 78, + "startColumn": 38, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 74, + "startColumn": 38, + "endColumn": 65, "lineCount": 1 } } ], - "./monitoring/mock_uss/tracer/routes/scd.py": [ + "./monitoring/monitorlib/clients/flight_planning/planning.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 18, - "endColumn": 21, + "startColumn": 61, + "endColumn": 66, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/clients/geospatial_info/client_geospatial_map.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 19, + "endColumn": 35, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/clients/geospatial_info/querying.py": [ { - "code": "reportOptionalSubscript", + "code": "reportOptionalIterable", "range": { - "startColumn": 17, - "endColumn": 21, + "startColumn": 58, + "endColumn": 74, + "lineCount": 1 + } + } + ], + "./monitoring/monitorlib/clients/mock_uss/interactions.py": [ + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 47, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 18, - "endColumn": 21, + "startColumn": 48, + "endColumn": 56, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 19, + "startColumn": 50, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportOptionalSubscript", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 29, + "startColumn": 51, + "endColumn": 59, "lineCount": 1 } } ], - "./monitoring/mock_uss/tracer/routes/ui.py": [ + "./monitoring/monitorlib/clients/scd.py": [ { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 1, - "endColumn": 46, + "startColumn": 11, + "endColumn": 17, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 35, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 60, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 36, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 27, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 34, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/clients/versioning/client_interuss.py": [ { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 27, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 43, + "startColumn": 42, + "endColumn": 61, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/fetch/__init__.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 69, + "startColumn": 47, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 56, + "startColumn": 37, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 78, + "startColumn": 36, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 1, - "endColumn": 62, + "startColumn": 15, + "endColumn": 42, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/tracer/subscriptions.py": [ + }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 35, - "endColumn": 43, + "startColumn": 20, + "endColumn": 26, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 31, - "endColumn": 39, + "startColumn": 15, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 24, - "endColumn": 32, + "startColumn": 11, + "endColumn": 37, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 22, - "endColumn": 30, + "startColumn": 28, + "endColumn": 31, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/tracer/tracer_poll.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 56, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 69, - "endColumn": 81, + "startColumn": 49, + "endColumn": 67, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/fetch/evaluation.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 68, + "endColumn": 76, + "lineCount": 1 + } + } + ], + "./monitoring/monitorlib/fetch/rid.py": [ + { + "code": "reportReturnType", "range": { "startColumn": 19, - "endColumn": 22, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 29, - "endColumn": 37, + "startColumn": 19, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 19, + "endColumn": 33, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 30, + "startColumn": 38, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeForm", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 9, - "lineCount": 3 + "startColumn": 43, + "endColumn": 53, + "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 26, + "startColumn": 41, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 22, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, + "startColumn": 35, "endColumn": 37, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 19, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportInvalidTypeForm", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 76, + "startColumn": 34, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, - "endColumn": 61, + "startColumn": 35, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 22, + "startColumn": 34, + "endColumn": 44, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 37, + "startColumn": 35, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 34, + "endColumn": 42, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/tracer/tracerlog.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 80, - "endColumn": 84, + "startColumn": 35, + "endColumn": 43, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/ui/auth.py": [ + }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 51, - "endColumn": 56, + "startColumn": 19, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 27, - "endColumn": 49, + "startColumn": 19, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 44, - "endColumn": 65, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 13, - "endColumn": 9, - "lineCount": 4 + "startColumn": 49, + "endColumn": 57, + "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 17, - "endColumn": 44, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, - "endColumn": 47, + "startColumn": 49, + "endColumn": 58, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/uspace/flight_auth.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 47, + "startColumn": 20, + "endColumn": 31, "lineCount": 1 } - } - ], - "./monitoring/mock_uss/versioning/routes.py": [ + }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 11, - "endColumn": 5, - "lineCount": 6 + "startColumn": 36, + "endColumn": 49, + "lineCount": 1 } - } - ], - "./monitoring/monitorlib/auth.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 68, - "endColumn": 81, + "startColumn": 50, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 29, + "startColumn": 36, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportAssignmentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 31, - "endColumn": 69, + "startColumn": 50, + "endColumn": 59, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 67, + "startColumn": 76, "endColumn": 87, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/auth_validation.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalIterable", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 25, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 60, - "endColumn": 63, + "startColumn": 40, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalIterable", "range": { - "startColumn": 34, - "endColumn": 37, + "startColumn": 25, + "endColumn": 57, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/flight_planning/client.py": [ + }, { - "code": "reportRedeclaration", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 22, + "startColumn": 41, + "endColumn": 57, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/flight_planning/client_scd.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 46, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 43, - "endColumn": 50, + "startColumn": 36, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 55, - "endColumn": 74, + "startColumn": 50, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 48, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 37, + "endColumn": 50, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/flight_planning/client_v1.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 51, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 49, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/flight_planning/flight_info.py": [ + }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 63, - "endColumn": 72, + "startColumn": 36, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 44, + "startColumn": 50, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 44, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 48, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 43, - "endColumn": 62, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 66, + "startColumn": 37, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 51, "endColumn": 71, "lineCount": 1 } @@ -3166,122 +2742,112 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 39, - "endColumn": 46, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 39, - "endColumn": 58, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 64, - "endColumn": 69, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 60, - "endColumn": 65, + "startColumn": 36, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 56, + "startColumn": 50, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/flight_planning/flight_info_template.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 31, + "startColumn": 48, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 31, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, - "endColumn": 65, + "startColumn": 37, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, - "endColumn": 65, + "startColumn": 51, + "endColumn": 71, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/flight_planning/planning.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 61, - "endColumn": 66, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/geospatial_info/client_geospatial_map.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/geospatial_info/querying.py": [ + }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, - "endColumn": 74, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/mock_uss/interactions.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 55, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, @@ -3289,321 +2855,311 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 48, - "endColumn": 56, + "endColumn": 57, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 58, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 59, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/scd.py": [ + }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 11, - "endColumn": 17, + "startColumn": 49, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 48, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/clients/versioning/client_interuss.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 49, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 61, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/fetch/__init__.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 59, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 45, + "startColumn": 48, + "endColumn": 62, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 44, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 42, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 26, + "startColumn": 49, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 33, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 11, - "endColumn": 37, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 31, + "startColumn": 48, + "endColumn": 62, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 67, + "startColumn": 35, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportAssignmentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 17, - "endColumn": 35, + "startColumn": 49, + "endColumn": 63, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 30, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 49, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 40, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, + "startColumn": 35, "endColumn": 48, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 22, - "endColumn": 31, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 25, + "startColumn": 36, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 30, + "startColumn": 50, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 43, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/fetch/evaluation.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 68, - "endColumn": 76, + "startColumn": 48, + "endColumn": 54, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/fetch/rid.py": [ + }, { - "code": "reportReturnType", + "code": "reportOperatorIssue", "range": { - "startColumn": 19, - "endColumn": 33, + "startColumn": 16, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 34, + "startColumn": 36, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOperatorIssue", "range": { "startColumn": 19, - "endColumn": 33, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, - "endColumn": 43, + "startColumn": 52, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 43, - "endColumn": 53, + "startColumn": 16, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 49, + "startColumn": 36, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 19, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 35, - "endColumn": 37, + "startColumn": 19, + "endColumn": 34, "lineCount": 1 } }, @@ -3611,7 +3167,7 @@ "code": "reportReturnType", "range": { "startColumn": 19, - "endColumn": 34, + "endColumn": 45, "lineCount": 1 } }, @@ -3624,105 +3180,105 @@ } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 35, - "endColumn": 47, + "startColumn": 19, + "endColumn": 46, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 44, + "startColumn": 35, + "endColumn": 46, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 45, + "startColumn": 49, + "endColumn": 68, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 42, + "startColumn": 38, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, + "startColumn": 37, "endColumn": 43, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 33, + "startColumn": 22, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 34, + "startColumn": 30, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 24, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 57, + "startColumn": 30, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 24, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 58, + "startColumn": 30, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 31, + "startColumn": 24, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, + "startColumn": 30, "endColumn": 49, "lineCount": 1 } @@ -3730,39 +3286,39 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 58, + "startColumn": 31, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 49, + "startColumn": 39, + "endColumn": 56, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 59, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 76, - "endColumn": 87, + "startColumn": 35, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, + "startColumn": 53, "endColumn": 56, "lineCount": 1 } @@ -3770,24 +3326,24 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 56, + "startColumn": 35, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 57, + "startColumn": 53, + "endColumn": 56, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 57, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, @@ -3795,198 +3351,198 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 34, - "endColumn": 54, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 49, + "startColumn": 52, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 70, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 37, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 66, + "startColumn": 55, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 34, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 50, + "startColumn": 52, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 71, + "startColumn": 26, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 47, + "endColumn": 56, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 67, + "startColumn": 68, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 35, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 49, + "startColumn": 37, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 70, + "startColumn": 55, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 35, + "endColumn": 52, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 53, + "startColumn": 53, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 19, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 50, + "startColumn": 34, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 71, + "startColumn": 31, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 34, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 41, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 23, + "endColumn": 59, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 49, + "startColumn": 39, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 70, + "startColumn": 46, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 19, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, + "startColumn": 34, "endColumn": 53, "lineCount": 1 } @@ -3994,96 +3550,96 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 31, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 50, + "startColumn": 34, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 71, + "startColumn": 41, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 23, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 39, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 46, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 19, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 48, - "endColumn": 57, + "startColumn": 19, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 34, + "endColumn": 44, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 45, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 58, + "startColumn": 35, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 46, + "endColumn": 51, "lineCount": 1 } }, @@ -4091,15 +3647,15 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 34, - "endColumn": 47, + "endColumn": 42, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 66, + "startColumn": 43, + "endColumn": 51, "lineCount": 1 } }, @@ -4107,23 +3663,23 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 35, - "endColumn": 55, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 44, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 49, - "endColumn": 67, + "startColumn": 19, + "endColumn": 75, "lineCount": 1 } }, @@ -4131,14 +3687,14 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 34, - "endColumn": 54, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, + "startColumn": 35, "endColumn": 47, "lineCount": 1 } @@ -4146,16 +3702,16 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 62, + "startColumn": 34, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 19, + "endColumn": 53, "lineCount": 1 } }, @@ -4163,807 +3719,805 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 35, - "endColumn": 48, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 63, + "startColumn": 34, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 35, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 19, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 48, - "endColumn": 62, + "startColumn": 19, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 15, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 38, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 49, - "endColumn": 63, + "startColumn": 15, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 39, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 48, + "startColumn": 12, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 49, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 70, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 12, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 54, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportOptionalIterable", "range": { - "startColumn": 16, - "endColumn": 50, + "startColumn": 55, + "endColumn": 88, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAssignmentType", "range": { - "startColumn": 36, - "endColumn": 49, + "startColumn": 15, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportAssignmentType", "range": { - "startColumn": 19, + "startColumn": 15, "endColumn": 54, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 52, - "endColumn": 65, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 51, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 50, + "startColumn": 12, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 33, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalIterable", "range": { - "startColumn": 19, - "endColumn": 34, + "startColumn": 50, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 45, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 45, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 46, + "startColumn": 12, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 46, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 68, + "startColumn": 12, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 51, + "startColumn": 14, + "endColumn": 20, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 22, + "startColumn": 27, "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 43, + "startColumn": 12, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 39, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 45, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 30, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 36, + "startColumn": 12, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 43, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 30, - "endColumn": 49, + "startColumn": 27, + "endColumn": 60, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/fetch/scd.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportRedeclaration", "range": { - "startColumn": 31, - "endColumn": 51, + "startColumn": 4, + "endColumn": 13, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 56, + "startColumn": 8, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 54, + "startColumn": 8, + "endColumn": 24, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 52, + "startColumn": 40, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/geo.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 53, - "endColumn": 56, + "startColumn": 33, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 52, + "startColumn": 54, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 53, - "endColumn": 56, + "startColumn": 33, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 54, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 34, - "endColumn": 51, + "startColumn": 70, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 52, - "endColumn": 60, + "startColumn": 38, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 42, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 54, + "startColumn": 38, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 55, - "endColumn": 75, + "startColumn": 42, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 51, + "startColumn": 33, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 52, - "endColumn": 60, + "startColumn": 63, + "endColumn": 68, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 31, + "startColumn": 33, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 56, + "startColumn": 63, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalSubscript", "range": { - "startColumn": 68, - "endColumn": 73, + "startColumn": 16, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 35, - "endColumn": 55, + "startColumn": 25, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 37, - "endColumn": 54, + "startColumn": 25, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 55, - "endColumn": 75, + "startColumn": 49, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 52, + "startColumn": 85, + "endColumn": 90, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 53, - "endColumn": 66, + "startColumn": 58, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalIterable", "range": { - "startColumn": 19, - "endColumn": 47, + "startColumn": 56, + "endColumn": 85, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 34, - "endColumn": 47, + "startColumn": 43, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 31, - "endColumn": 51, + "startColumn": 64, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 34, - "endColumn": 40, + "startColumn": 44, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 41, - "endColumn": 61, + "startColumn": 66, + "endColumn": 81, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 23, - "endColumn": 59, + "startColumn": 43, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 45, + "startColumn": 66, + "endColumn": 80, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 46, - "endColumn": 59, + "startColumn": 43, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 19, - "endColumn": 53, + "startColumn": 66, + "endColumn": 80, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 34, - "endColumn": 53, + "startColumn": 41, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 31, - "endColumn": 51, + "startColumn": 41, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 34, - "endColumn": 40, + "startColumn": 41, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { "startColumn": 41, - "endColumn": 61, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalIterable", "range": { - "startColumn": 23, - "endColumn": 61, + "startColumn": 54, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 39, - "endColumn": 45, + "startColumn": 11, + "endColumn": 31, "lineCount": 1 } - }, + } + ], + "./monitoring/monitorlib/geotemporal.py": [ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 61, + "startColumn": 43, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 33, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 34, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportGeneralTypeIssues", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 53, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 44, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 60, + "startColumn": 56, + "endColumn": 64, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 45, + "startColumn": 29, + "endColumn": 37, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 51, + "startColumn": 56, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportGeneralTypeIssues", + "code": "reportOptionalIterable", "range": { - "startColumn": 26, - "endColumn": 34, + "startColumn": 21, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 51, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 42, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 51, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 58, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 43, + "startColumn": 38, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 49, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 75, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 43, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 47, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 52, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 53, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 35, - "endColumn": 53, + "startColumn": 8, + "endColumn": 16, "lineCount": 1 } }, @@ -4971,180 +4525,196 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 34, - "endColumn": 39, + "endColumn": 42, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, + "startColumn": 32, "endColumn": 40, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalIterable", "range": { - "startColumn": 19, - "endColumn": 33, + "startColumn": 25, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 34, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 49, + "startColumn": 12, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 15, - "endColumn": 50, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 39, - "endColumn": 53, + "startColumn": 64, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 23, + "endColumn": 32, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/action_generator.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 9, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 12, - "endColumn": 41, + "startColumn": 15, + "endColumn": 24, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/astm/f3411/for_each_dss.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 8, + "endColumn": 30, "lineCount": 1 } }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 55, + "endColumn": 9, + "lineCount": 3 + } + }, { "code": "reportArgumentType", "range": { "startColumn": 12, - "endColumn": 40, + "endColumn": 61, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 8, + "endColumn": 30, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/documentation/documentation.py": [ { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 12, - "endColumn": 41, + "startColumn": 5, + "endColumn": 35, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 8, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportAssignmentType", "range": { - "startColumn": 55, - "endColumn": 88, - "lineCount": 1 + "startColumn": 59, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportAssignmentType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 54, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/interuss/mock_uss/with_locality.py": [ { - "code": "reportAssignmentType", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 15, - "endColumn": 54, + "startColumn": 8, + "endColumn": 30, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/action_generators/repetition/repeat.py": [ { - "code": "reportArgumentType", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 8, + "endColumn": 30, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/documentation.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 33, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 12, + "startColumn": 35, "endColumn": 41, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { "startColumn": 28, "endColumn": 36, @@ -5152,33 +4722,45 @@ } }, { - "code": "reportOptionalIterable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 50, - "endColumn": 77, + "startColumn": 25, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 27, + "endColumn": 35, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/fileio.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 64, + "endColumn": 69, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/main.py": [ { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, + "startColumn": 33, "endColumn": 41, "lineCount": 1 } @@ -5186,454 +4768,460 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 28, + "startColumn": 37, + "endColumn": 56, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 20, + "startColumn": 8, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalSubscript", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 36, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalSubscript", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 39, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 41, + "startColumn": 53, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 19, + "endColumn": 28, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/requirements/documentation.py": [ { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 61, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 41, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 12, - "endColumn": 41, + "startColumn": 31, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 38, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportAttributeAccessIssue", "range": { "startColumn": 27, - "endColumn": 60, + "endColumn": 35, "lineCount": 1 } - } - ], - "./monitoring/monitorlib/fetch/scd.py": [ + }, { - "code": "reportRedeclaration", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 4, - "endColumn": 13, + "startColumn": 38, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 8, - "endColumn": 23, + "startColumn": 61, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 8, - "endColumn": 24, + "startColumn": 35, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", "range": { "startColumn": 40, - "endColumn": 43, + "endColumn": 48, "lineCount": 1 } } ], - "./monitoring/monitorlib/geo.py": [ + "./monitoring/uss_qualifier/resources/astm/f3548/v21/__init__.py": [ { - "code": "reportOptionalIterable", + "code": "reportUnsupportedDunderAll", "range": { - "startColumn": 33, - "endColumn": 46, + "startColumn": 11, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportUnsupportedDunderAll", "range": { - "startColumn": 54, - "endColumn": 67, + "startColumn": 34, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportUnsupportedDunderAll", "range": { - "startColumn": 33, - "endColumn": 46, + "startColumn": 58, + "endColumn": 80, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 54, - "endColumn": 67, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 70, - "endColumn": 78, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 48, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 48, + "startColumn": 25, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 48, + "startColumn": 29, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalIterable", "range": { - "startColumn": 42, - "endColumn": 48, + "startColumn": 213, + "endColumn": 247, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 38, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 63, - "endColumn": 68, + "startColumn": 25, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 38, + "startColumn": 25, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 63, - "endColumn": 68, + "startColumn": 25, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalSubscript", + "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 47, + "startColumn": 25, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { "startColumn": 25, - "endColumn": 56, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { "startColumn": 25, - "endColumn": 56, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 78, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 85, - "endColumn": 90, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 58, - "endColumn": 87, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 56, - "endColumn": 85, + "startColumn": 25, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportReturnType", "range": { - "startColumn": 43, - "endColumn": 57, + "startColumn": 19, + "endColumn": 50, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/dev/test_exclusions.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportReturnType", "range": { - "startColumn": 64, - "endColumn": 78, + "startColumn": 19, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportReturnType", "range": { - "startColumn": 44, - "endColumn": 59, + "startColumn": 19, + "endColumn": 53, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/flight_planning/flight_intent.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 66, - "endColumn": 81, + "startColumn": 48, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 43, - "endColumn": 57, + "startColumn": 67, + "endColumn": 73, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 66, - "endColumn": 80, + "startColumn": 24, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 43, + "startColumn": 49, "endColumn": 57, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 66, - "endColumn": 80, + "startColumn": 38, + "endColumn": 42, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/flight_planning/flight_intent_validation.py": [ { - "code": "reportOptionalIterable", + "code": "reportOperatorIssue", "range": { - "startColumn": 41, - "endColumn": 70, - "lineCount": 1 + "startColumn": 23, + "endColumn": 21, + "lineCount": 4 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 70, + "startColumn": 45, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 70, + "startColumn": 54, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 70, + "startColumn": 124, + "endColumn": 132, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 67, + "startColumn": 274, + "endColumn": 282, "lineCount": 1 } }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 31, - "lineCount": 1 - } - } - ], - "./monitoring/monitorlib/geotemporal.py": [ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 43, - "endColumn": 51, + "startColumn": 45, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 54, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 124, + "endColumn": 132, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 271, + "endColumn": 279, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 56, + "endColumn": 64, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 130, + "endColumn": 138, "lineCount": 1 } }, @@ -5648,330 +5236,340 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 37, + "startColumn": 130, + "endColumn": 138, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 56, - "endColumn": 64, + "startColumn": 54, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 21, - "endColumn": 57, + "startColumn": 126, + "endColumn": 134, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 54, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 126, + "endColumn": 134, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 16, + "endColumn": 50, "lineCount": 1 } - }, - { + } + ], + "./monitoring/uss_qualifier/resources/flight_planning/flight_intents_resource.py": [ + { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 26, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { "startColumn": 38, - "endColumn": 41, + "endColumn": 69, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/flight_planning/flight_planners.py": [ { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 40, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 51, + "endColumn": 56, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/interuss/id_generator.py": [ { - "code": "reportArgumentType", + "code": "reportAssignmentType", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 29, + "endColumn": 33, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/interuss/mock_uss/client.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 38, + "endColumn": 57, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 42, + "endColumn": 68, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/interuss/query_behavior.py": [ { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 36, + "endColumn": 69, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/interuss/uss_identification.py": [ { - "code": "reportIncompatibleMethodOverride", + "code": "reportOptionalSubscript", "range": { - "startColumn": 8, - "endColumn": 16, + "startColumn": 62, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalSubscript", "range": { - "startColumn": 34, - "endColumn": 42, + "startColumn": 63, + "endColumn": 69, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/netrid/flight_data_resources.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 40, + "startColumn": 26, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 25, - "endColumn": 61, + "startColumn": 16, + "endColumn": 73, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 64, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 30, + "startColumn": 41, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 41, + "endColumn": 52, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 64, - "endColumn": 72, + "startColumn": 36, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 32, + "startColumn": 35, + "endColumn": 43, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/action_generators/action_generator.py": [ + "./monitoring/uss_qualifier/resources/netrid/observers.py": [ { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 9, - "endColumn": 28, + "startColumn": 20, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 24, + "startColumn": 20, + "endColumn": 39, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/action_generators/astm/f3411/for_each_dss.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 55, - "endColumn": 9, - "lineCount": 3 - } - }, + "./monitoring/uss_qualifier/resources/netrid/service_providers.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 61, + "startColumn": 35, + "endColumn": 50, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/action_generators/astm/f3548/for_each_dss.py": [ + "./monitoring/uss_qualifier/resources/netrid/simulation/adjacent_circular_flights_simulator.py": [ { - "code": "reportIncompatibleMethodOverride", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 8, - "endColumn": 30, + "startColumn": 29, + "endColumn": 45, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/action_generators/documentation/documentation.py": [ + }, { - "code": "reportReturnType", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 5, - "endColumn": 35, + "startColumn": 9, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 60, - "endColumn": 73, + "startColumn": 38, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 11, - "endColumn": 49, + "startColumn": 64, + "endColumn": 75, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/netrid/simulation/kml_flights.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 77, - "endColumn": 87, + "startColumn": 22, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 58, - "endColumn": 68, + "startColumn": 17, + "endColumn": 34, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/resources/overrides.py": [ { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { "startColumn": 12, - "endColumn": 56, + "endColumn": 22, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 39, - "endColumn": 55, + "startColumn": 15, + "endColumn": 25, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/resources/resource.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 64, - "endColumn": 80, + "startColumn": 33, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 64, - "endColumn": 78, + "startColumn": 26, + "endColumn": 44, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py": [ + }, { - "code": "reportIncompatibleMethodOverride", + "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 30, + "endColumn": 34, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py": [ { - "code": "reportAssignmentType", + "code": "reportCallIssue", "range": { - "startColumn": 59, - "endColumn": 9, + "startColumn": 38, + "endColumn": 17, "lineCount": 3 } }, @@ -5979,914 +5577,912 @@ "code": "reportArgumentType", "range": { "startColumn": 20, - "endColumn": 41, + "endColumn": 40, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/action_generators/interuss/mock_uss/with_locality.py": [ + }, { - "code": "reportIncompatibleMethodOverride", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 30, + "startColumn": 16, + "endColumn": 66, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/action_generators/repetition/repeat.py": [ + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py": [ { - "code": "reportIncompatibleMethodOverride", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 30, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/documentation.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 33, - "endColumn": 39, + "startColumn": 60, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 41, + "startColumn": 43, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 29, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 25, - "endColumn": 33, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 30, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 30, + "endColumn": 41, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/fileio.py": [ + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/heavy_traffic_concurrent.py": [ { - "code": "reportReturnType", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 27, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportRedeclaration", "range": { - "startColumn": 64, - "endColumn": 69, + "startColumn": 4, + "endColumn": 17, "lineCount": 1 } }, { - "code": "reportPrivateImportUsage", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 55, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPrivateImportUsage", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 43, - "endColumn": 48, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportPrivateImportUsage", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 44, - "endColumn": 49, + "startColumn": 31, + "endColumn": 42, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/main.py": [ + }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 33, - "endColumn": 41, + "startColumn": 93, + "endColumn": 104, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 18, - "endColumn": 37, + "startColumn": 23, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 56, + "startColumn": 28, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 37, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 8, - "endColumn": 29, + "startColumn": 35, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 19, - "endColumn": 28, + "startColumn": 58, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 55, - "endColumn": 64, + "startColumn": 31, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 19, - "endColumn": 28, + "startColumn": 82, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 15, - "endColumn": 24, + "startColumn": 61, + "endColumn": 64, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 29, + "startColumn": 23, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 47, + "startColumn": 28, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 72, + "startColumn": 23, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 36, - "endColumn": 42, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalSubscript", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 36, - "endColumn": 66, + "startColumn": 31, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalSubscript", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 72, + "startColumn": 82, + "endColumn": 87, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 53, - "endColumn": 64, + "startColumn": 23, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 28, + "startColumn": 28, + "endColumn": 39, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/capabilities.py": [ + }, { - "code": "reportGeneralTypeIssues", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 60, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 20, - "endColumn": 41, + "startColumn": 31, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 32, - "endColumn": 45, + "startColumn": 83, + "endColumn": 94, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 32, - "endColumn": 44, + "startColumn": 54, + "endColumn": 59, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_expiry.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 55, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_simple.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 60, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 32, - "endColumn": 59, + "startColumn": 28, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportPrivateImportUsage", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 47, + "startColumn": 32, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 52, + "startColumn": 158, + "endColumn": 165, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 30, - "endColumn": 79, + "startColumn": 21, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 78, + "startColumn": 40, + "endColumn": 59, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/globally_expanded/generate.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 74, + "startColumn": 45, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 66, - "endColumn": 74, + "startColumn": 40, + "endColumn": 59, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 41, - "endColumn": 54, + "startColumn": 45, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 110, - "endColumn": 116, + "startColumn": 40, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 52, - "endColumn": 60, + "startColumn": 45, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalSubscript", "range": { - "startColumn": 37, - "endColumn": 45, + "startColumn": 32, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 54, + "startColumn": 60, + "endColumn": 77, "lineCount": 1 } }, { "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 37, + "startColumn": 36, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 57, + "endColumn": 74, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 31, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 8, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 71, - "endColumn": 79, + "startColumn": 8, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 50, + "startColumn": 8, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 60, + "startColumn": 8, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 58, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 48, - "endColumn": 56, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 43, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 32, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 35, - "lineCount": 2 + "startColumn": 18, + "endColumn": 34, + "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 47, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 39, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 61, - "endColumn": 66, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 35, - "lineCount": 2 + "startColumn": 18, + "endColumn": 34, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 67, - "endColumn": 80, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 39, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 57, - "endColumn": 69, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 93, - "endColumn": 102, + "startColumn": 49, + "endColumn": 67, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 39, + "startColumn": 221, + "endColumn": 239, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 57, - "endColumn": 69, + "startColumn": 12, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 93, - "endColumn": 102, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 58, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 48, - "endColumn": 56, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 29, + "startColumn": 18, "endColumn": 37, "lineCount": 1 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 28, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 28, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 28, + "startColumn": 18, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 16, - "endColumn": 28, + "startColumn": 39, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 32, - "endColumn": 48, + "startColumn": 49, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 32, + "startColumn": 46, "endColumn": 48, "lineCount": 1 } }, { - "code": "reportCallIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 32, - "endColumn": 53, + "startColumn": 253, + "endColumn": 255, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 44, - "endColumn": 52, + "startColumn": 39, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 36, - "lineCount": 3 + "startColumn": 49, + "endColumn": 51, + "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/report.py": [ + }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 47, + "startColumn": 46, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 44, - "endColumn": 46, + "startColumn": 42, + "endColumn": 60, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 43, + "startColumn": 207, + "endColumn": 225, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 47, + "startColumn": 62, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 65, - "endColumn": 85, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 47, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 48, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 50, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 60, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 57, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 66, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 60, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 58, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 54, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 50, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 50, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 40, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/sequence_view/events.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 43, + "startColumn": 62, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 24, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/sequence_view/generate.py": [ + }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 57, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 56, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 72, - "endColumn": 82, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 59, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 67, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 74, - "endColumn": 87, + "startColumn": 49, + "endColumn": 67, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 62, + "startColumn": 221, + "endColumn": 239, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 38, + "startColumn": 12, "endColumn": 42, "lineCount": 1 } @@ -6894,72 +6490,72 @@ { "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 65, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 79, - "endColumn": 86, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 39, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 50, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 81, - "endColumn": 88, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 55, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 58, + "startColumn": 18, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 51, - "endColumn": 55, + "startColumn": 39, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 46, + "startColumn": 49, + "endColumn": 51, "lineCount": 1 } }, @@ -6967,907 +6563,837 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 46, - "endColumn": 58, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 43, - "endColumn": 57, + "startColumn": 253, + "endColumn": 255, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { "startColumn": 39, - "endColumn": 53, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 65, + "startColumn": 49, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { "startColumn": 46, - "endColumn": 59, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 77, + "startColumn": 42, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 69, - "endColumn": 77, + "startColumn": 207, + "endColumn": 225, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/sequence_view/kml.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 17, - "endColumn": 22, + "startColumn": 62, + "endColumn": 69, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py": [ { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 9, - "endColumn": 26, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportInvalidTypeForm", "range": { - "startColumn": 14, - "endColumn": 17, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 74, + "startColumn": 38, + "endColumn": 59, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validator.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 85, - "endColumn": 90, + "startColumn": 27, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 28, - "endColumn": 52, + "startColumn": 30, + "endColumn": 33, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 66, - "endColumn": 74, + "startColumn": 16, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 74, + "startColumn": 29, + "endColumn": 44, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 69, + "startColumn": 16, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 1, - "endColumn": 71, + "startColumn": 16, + "endColumn": 47, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/reports/sequence_view/summary_types.py": [ + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_simple.py": [ { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 17, - "endColumn": 34, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/tested_requirements/breakdown.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 53, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 64, - "endColumn": 70, + "startColumn": 8, + "endColumn": 62, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 12, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 50, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 56, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 57, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 11, - "endColumn": 44, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 67, - "endColumn": 77, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 60, - "endColumn": 70, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 51, + "startColumn": 22, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 50, + "startColumn": 12, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 55, + "startColumn": 12, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 35, + "startColumn": 16, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 52, + "startColumn": 16, + "endColumn": 40, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/reports/tested_requirements/generate.py": [ + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_validation.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 49, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 7, - "endColumn": 41, + "startColumn": 79, + "endColumn": 86, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 56, - "endColumn": 75, + "startColumn": 40, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 22, - "endColumn": 41, + "startColumn": 43, + "endColumn": 56, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/token_validation.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 54, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 45, + "startColumn": 28, + "endColumn": 35, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/tested_requirements/summaries.py": [ + }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 22, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 53, + "startColumn": 22, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 33, - "endColumn": 46, + "startColumn": 27, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 47, - "lineCount": 1 + "startColumn": 22, + "endColumn": 21, + "lineCount": 5 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 63, - "lineCount": 1 + "startColumn": 22, + "endColumn": 21, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 58, - "endColumn": 63, - "lineCount": 1 + "startColumn": 22, + "endColumn": 21, + "lineCount": 5 } }, { - "code": "reportOptionalSubscript", + "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 73, - "lineCount": 1 + "startColumn": 22, + "endColumn": 21, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 68, - "endColumn": 73, + "startColumn": 22, + "endColumn": 77, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/reports/validation/report_validation.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 22, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { "startColumn": 27, - "endColumn": 44, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 35, + "startColumn": 22, + "endColumn": 77, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 33, + "startColumn": 22, + "endColumn": 77, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/requirements/documentation.py": [ + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 61, - "endColumn": 69, + "startColumn": 49, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 49, + "startColumn": 24, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 39, + "startColumn": 57, + "endColumn": 71, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss_interoperability.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportRedeclaration", "range": { - "startColumn": 38, - "endColumn": 46, + "startColumn": 4, + "endColumn": 12, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 46, + "startColumn": 23, + "endColumn": 27, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 61, - "endColumn": 69, + "startColumn": 57, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 35, - "endColumn": 43, + "startColumn": 19, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 35, + "startColumn": 31, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 40, - "endColumn": 48, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/astm/f3548/v21/__init__.py": [ - { - "code": "reportUnsupportedDunderAll", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 11, - "endColumn": 32, + "startColumn": 47, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportUnsupportedDunderAll", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 34, - "endColumn": 56, + "startColumn": 48, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportUnsupportedDunderAll", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, - "endColumn": 80, + "startColumn": 60, + "endColumn": 62, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 74, + "endColumn": 76, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 19, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 31, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 75, + "startColumn": 52, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 29, - "endColumn": 30, + "startColumn": 39, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 213, - "endColumn": 247, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 70, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 25, + "startColumn": 19, "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 54, + "startColumn": 31, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 51, + "startColumn": 50, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 25, - "endColumn": 51, + "startColumn": 51, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 51, + "startColumn": 63, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 51, + "startColumn": 80, + "endColumn": 85, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 19, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 31, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 56, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 25, - "endColumn": 52, + "startColumn": 28, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 50, + "startColumn": 40, + "endColumn": 51, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/resources/dev/test_exclusions.py": [ + }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 53, + "startColumn": 38, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportPossiblyUnboundVariable", "range": { "startColumn": 19, - "endColumn": 53, + "endColumn": 30, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/resources/flight_planning/flight_intent.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 54, + "startColumn": 31, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 67, - "endColumn": 73, + "startColumn": 55, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 24, - "endColumn": 57, + "startColumn": 32, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 57, + "startColumn": 44, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, - "endColumn": 42, + "startColumn": 42, + "endColumn": 52, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/resources/flight_planning/flight_intent_validation.py": [ + }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 23, - "endColumn": 21, - "lineCount": 4 + "startColumn": 19, + "endColumn": 30, + "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 53, + "startColumn": 31, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 62, + "startColumn": 53, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 124, - "endColumn": 132, + "startColumn": 32, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 274, - "endColumn": 282, + "startColumn": 44, + "endColumn": 52, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 53, + "startColumn": 42, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 62, + "startColumn": 44, + "endColumn": 46, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 124, - "endColumn": 132, + "startColumn": 73, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 271, - "endColumn": 279, + "startColumn": 59, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 56, - "endColumn": 64, + "startColumn": 86, + "endColumn": 88, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 130, - "endColumn": 138, + "startColumn": 44, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 56, - "endColumn": 64, + "startColumn": 78, + "endColumn": 85, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 130, - "endColumn": 138, + "startColumn": 53, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 62, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 126, - "endColumn": 134, + "startColumn": 45, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 62, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 126, - "endColumn": 134, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 50, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/flight_planning/flight_intents_resource.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 38, - "endColumn": 69, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/flight_planning/flight_planners.py": [ - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 40, - "endColumn": 72, + "startColumn": 53, + "endColumn": 56, "lineCount": 1 } }, @@ -7875,4465 +7401,399 @@ "code": "reportOptionalMemberAccess", "range": { "startColumn": 51, - "endColumn": 56, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/interuss/datastore/datastore.py": [ - { - "code": "reportReturnType", - "range": { - "startColumn": 34, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 26, - "endColumn": 32, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/interuss/id_generator.py": [ - { - "code": "reportAssignmentType", - "range": { - "startColumn": 29, - "endColumn": 33, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/interuss/mock_uss/client.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 38, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 68, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/interuss/query_behavior.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 36, - "endColumn": 69, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/interuss/uss_identification.py": [ - { - "code": "reportOptionalSubscript", - "range": { - "startColumn": 62, - "endColumn": 68, - "lineCount": 1 - } - }, - { - "code": "reportOptionalSubscript", - "range": { - "startColumn": 63, - "endColumn": 69, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/netrid/flight_data_resources.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 73, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 64, - "endColumn": 72, + "startColumn": 44, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 55, + "startColumn": 78, + "endColumn": 85, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 52, + "startColumn": 53, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 44, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 43, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/netrid/observers.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 39, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/netrid/service_providers.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 35, - "endColumn": 50, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/netrid/simulation/adjacent_circular_flights_simulator.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 29, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 9, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 38, - "endColumn": 55, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 64, - "endColumn": 75, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/netrid/simulation/kml_flights.py": [ - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 22, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 17, - "endColumn": 34, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/overrides.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 22, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 25, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/resources/resource.py": [ - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 22, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 26, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 34, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py": [ - { - "code": "reportCallIssue", - "range": { - "startColumn": 38, - "endColumn": 17, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 66, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 60, - "endColumn": 74, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 43, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 30, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 30, - "endColumn": 41, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/heavy_traffic_concurrent.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 27, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportRedeclaration", - "range": { - "startColumn": 4, - "endColumn": 17, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 42, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 31, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 93, - "endColumn": 104, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 23, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 42, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 35, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 58, - "endColumn": 67, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 31, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 82, - "endColumn": 87, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 61, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 23, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 23, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 42, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 31, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 82, - "endColumn": 87, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 23, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 42, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 31, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 83, - "endColumn": 94, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 54, - "endColumn": 59, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_expiry.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_simple.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 28, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 32, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 158, - "endColumn": 165, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 21, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 40, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 45, - "endColumn": 62, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 40, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 45, - "endColumn": 62, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 40, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 45, - "endColumn": 62, - "lineCount": 1 - } - }, - { - "code": "reportOptionalSubscript", - "range": { - "startColumn": 32, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 60, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 36, - "endColumn": 55, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 57, - "endColumn": 74, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 67, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 221, - "endColumn": 239, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 12, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 39, - "endColumn": 72, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 253, - "endColumn": 255, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 39, - "endColumn": 72, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 42, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 207, - "endColumn": 225, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 62, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 62, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 67, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 221, - "endColumn": 239, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 12, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 39, - "endColumn": 72, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 253, - "endColumn": 255, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 39, - "endColumn": 72, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 49, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 42, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 207, - "endColumn": 225, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 62, - "endColumn": 69, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeForm", - "range": { - "startColumn": 38, - "endColumn": 59, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validator.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 27, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 47, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_simple.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 71, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 62, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 31, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 29, - "endColumn": 65, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 40, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_validation.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 79, - "endColumn": 86, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 40, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 43, - "endColumn": 56, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/token_validation.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 28, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 27, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 21, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 21, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 21, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 21, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 27, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 77, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/utils.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 49, - "endColumn": 63, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 24, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 57, - "endColumn": 71, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/dss_interoperability.py": [ - { - "code": "reportRedeclaration", - "range": { - "startColumn": 4, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 23, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 57, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 31, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 47, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 48, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 60, - "endColumn": 62, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 74, - "endColumn": 76, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 31, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 52, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 39, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 70, - "endColumn": 77, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 31, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 50, - "endColumn": 55, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 51, - "endColumn": 62, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 63, - "endColumn": 68, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 80, - "endColumn": 85, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 31, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 56, - "endColumn": 67, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 28, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 40, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 38, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 31, - "endColumn": 41, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 55, - "endColumn": 65, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 32, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 54, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 42, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 19, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 31, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 32, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 42, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 73, - "endColumn": 75, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 59, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 86, - "endColumn": 88, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 78, - "endColumn": 85, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 45, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 56, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 54, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 78, - "endColumn": 85, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 45, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 75, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 56, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 54, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 19, - "endColumn": 17, - "lineCount": 4 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 45, - "endColumn": 55, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 46, - "endColumn": 56, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 63, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 63, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 19, - "endColumn": 87, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 79, - "endColumn": 87, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 53, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 51, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 73, - "endColumn": 75, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 73, - "endColumn": 75, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 36, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 26, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportOptionalIterable", - "range": { - "startColumn": 30, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 58, - "endColumn": 71, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 57, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 41, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 41, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 57, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 58, - "endColumn": 71, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 59, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 25, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportCallIssue", - "range": { - "startColumn": 53, - "endColumn": 70, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/networked_uas_disconnect.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 71, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 26, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 46, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 64, - "endColumn": 78, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 35, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 30, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 64, - "endColumn": 72, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeForm", - "range": { - "startColumn": 26, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 17, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 11, - "endColumn": 17, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 19, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 37, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_slow_update.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 27, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 26, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 31, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 31, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 36, - "endColumn": 41, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 44, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 45, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 18, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 19, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 31, - "endColumn": 56, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py": [ - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 23, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 40, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportOptionalOperand", - "range": { - "startColumn": 24, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportOptionalOperand", - "range": { - "startColumn": 27, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 34, - "endColumn": 54, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 22, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 37, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 19, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 38, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 52, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 39, - "endColumn": 40, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 8, - "endColumn": 5, - "lineCount": 94 - } - }, - { - "code": "reportCallIssue", - "range": { - "startColumn": 8, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 5, - "lineCount": 11 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 20, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 33, - "endColumn": 49, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 29, - "lineCount": 1 - } - }, - { - "code": "reportOptionalSubscript", - "range": { - "startColumn": 24, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 27, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 27, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 27, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 27, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 27, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 21, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportOptionalSubscript", - "range": { - "startColumn": 24, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 46, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 46, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 46, - "endColumn": 64, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 52, - "endColumn": 63, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 41, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 54, - "endColumn": 65, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 52, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 54, - "endColumn": 65, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 52, - "endColumn": 65, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 17, - "endColumn": 18, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 13, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 23, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 23, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 19, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 48, - "endColumn": 50, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 65, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 47, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 13, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 36, - "endColumn": 47, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 49, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 34, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 9, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 26, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 34, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 50, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 44, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 44, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 3 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 50, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 44, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 44, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 45, + "endColumn": 48, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 46, + "endColumn": 49, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 28, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 53, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 51, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 45, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 46, + "endColumn": 56, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 28, + "endColumn": 63, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 53, + "endColumn": 63, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 51, + "endColumn": 61, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 44, + "endColumn": 52, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 79, + "endColumn": 87, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 28, + "endColumn": 61, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 53, + "endColumn": 61, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 51, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 44, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 73, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 73, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 36, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 26, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 30, + "endColumn": 57, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 58, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 57, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 28, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 28, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 57, + "endColumn": 64, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 58, + "endColumn": 71, + "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 12, + "endColumn": 59, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 12, + "endColumn": 50, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 25, + "endColumn": 66, "lineCount": 1 } }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 53, + "endColumn": 70, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/networked_uas_disconnect.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 12, + "endColumn": 71, + "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 39, + "endColumn": 43, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 26, + "endColumn": 37, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 64, + "endColumn": 78, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 35, + "endColumn": 42, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 30, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, @@ -12341,743 +7801,751 @@ "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 78, + "endColumn": 20, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 64, + "endColumn": 72, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 19, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 18, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 12, + "endColumn": 16, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 12, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 12, + "endColumn": 37, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_slow_update.py": [ { - "code": "reportArgumentType", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 27, + "endColumn": 35, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 26, + "endColumn": 34, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 31, + "endColumn": 36, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 31, + "endColumn": 36, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 33, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 33, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 36, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 44, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 45, + "endColumn": 53, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 18, + "endColumn": 43, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 19, + "endColumn": 45, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 32, + "endColumn": 58, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 31, + "endColumn": 56, + "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py": [ { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 23, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 12, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 12, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 78, + "startColumn": 12, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 22, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 22, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 23, + "endColumn": 26, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, + "startColumn": 22, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 22, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalOperand", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 24, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalOperand", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 27, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 34, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 22, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 70, + "startColumn": 22, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAssignmentType", "range": { - "startColumn": 4, - "endColumn": 68, + "startColumn": 37, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOperatorIssue", "range": { - "startColumn": 4, - "endColumn": 68, + "startColumn": 19, + "endColumn": 52, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 68, + "startColumn": 38, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 68, + "startColumn": 52, + "endColumn": 64, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py": [ { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 26, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 26, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 32, + "endColumn": 42, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 4, + "startColumn": 11, "endColumn": 5, - "lineCount": 5 + "lineCount": 11 } }, { - "code": "reportArgumentType", + "code": "reportAssignmentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 20, + "endColumn": 24, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 29, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 21, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 24, + "endColumn": 32, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 24, + "endColumn": 25, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 33, + "endColumn": 49, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 25, + "endColumn": 39, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 28, + "endColumn": 36, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalSubscript", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 24, + "endColumn": 52, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 21, + "endColumn": 25, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 21, + "endColumn": 25, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 21, + "endColumn": 25, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 21, + "endColumn": 25, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalSubscript", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 24, + "endColumn": 52, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 46, + "endColumn": 64, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 46, + "endColumn": 64, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 46, + "endColumn": 64, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 9, + "endColumn": 24, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 26, + "endColumn": 37, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 39, + "endColumn": 50, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 52, + "endColumn": 63, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 9, + "endColumn": 24, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 26, + "endColumn": 39, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 41, + "endColumn": 52, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 54, + "endColumn": 65, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 3 + "startColumn": 9, + "endColumn": 24, + "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 26, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 39, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 54, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 9, + "endColumn": 24, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 26, + "endColumn": 37, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 39, + "endColumn": 50, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 52, + "endColumn": 65, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 17, + "endColumn": 18, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, @@ -13085,7 +8553,7 @@ "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 82, + "endColumn": 19, "lineCount": 1 } }, @@ -13093,271 +8561,271 @@ "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 82, + "endColumn": 19, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 13, + "endColumn": 28, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { "startColumn": 12, - "endColumn": 74, + "endColumn": 23, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 8, + "endColumn": 19, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 8, + "endColumn": 21, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 8, + "endColumn": 21, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 19, + "endColumn": 20, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 8, + "endColumn": 21, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 8, + "endColumn": 21, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 8, + "endColumn": 21, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 48, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 65, + "endColumn": 66, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 8, + "endColumn": 12, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 8, + "endColumn": 19, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 73, + "startColumn": 8, + "endColumn": 19, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 72, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 72, + "startColumn": 26, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 72, + "startColumn": 32, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 72, + "startColumn": 47, + "endColumn": 60, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 13, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 30, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 36, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 49, + "endColumn": 60, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 9, + "endColumn": 24, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 82, + "endColumn": 12, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, @@ -13365,7 +8833,7 @@ "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 82, + "endColumn": 21, "lineCount": 1 } }, @@ -13373,7 +8841,7 @@ "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 82, + "endColumn": 12, "lineCount": 1 } }, @@ -13381,63 +8849,71 @@ "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "endColumn": 12, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 9, + "endColumn": 24, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 26, + "endColumn": 28, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 30, + "endColumn": 32, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 34, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 9, + "endColumn": 24, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 26, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 32, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 34, + "endColumn": 36, "lineCount": 1 } }, @@ -13564,65 +9040,129 @@ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 6 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 6 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 6 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 6 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { @@ -13660,32 +9200,64 @@ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 12, + "endColumn": 13, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 12, + "endColumn": 13, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 12, + "endColumn": 13, "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 12, + "endColumn": 13, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 13, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 13, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 13, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 13, "lineCount": 3 } }, @@ -13777,6 +9349,38 @@ "lineCount": 1 } }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 + } + }, { "code": "reportArgumentType", "range": { @@ -13966,7 +9570,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 5 + "lineCount": 3 } }, { @@ -13974,7 +9578,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 5 + "lineCount": 3 } }, { @@ -13982,7 +9586,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 5 + "lineCount": 3 } }, { @@ -13990,38 +9594,102 @@ "range": { "startColumn": 8, "endColumn": 9, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 16, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, @@ -14118,7 +9786,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 5 + "lineCount": 3 } }, { @@ -14126,7 +9794,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 5 + "lineCount": 3 } }, { @@ -14134,7 +9802,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 5 + "lineCount": 3 } }, { @@ -14142,190 +9810,198 @@ "range": { "startColumn": 8, "endColumn": 9, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 16, + "endColumn": 82, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 16, + "endColumn": 82, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 16, + "endColumn": 82, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 4, - "endColumn": 5, - "lineCount": 5 + "startColumn": 16, + "endColumn": 82, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 4, + "endColumn": 70, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 4, + "endColumn": 70, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 4, + "endColumn": 70, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 4, + "endColumn": 70, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 4, + "endColumn": 70, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 4, + "endColumn": 70, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 4, + "endColumn": 70, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 5 + "startColumn": 4, + "endColumn": 70, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 4, + "endColumn": 68, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 4, + "endColumn": 68, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 4, + "endColumn": 68, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 74, + "startColumn": 4, + "endColumn": 68, "lineCount": 1 } }, @@ -14484,89 +10160,161 @@ { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 82, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 43, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 64, - "endColumn": 68, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 3 } }, { @@ -14596,56 +10344,32 @@ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, + "startColumn": 4, + "endColumn": 5, "lineCount": 5 } }, @@ -14653,31 +10377,7 @@ "code": "reportArgumentType", "range": { "startColumn": 8, - "endColumn": 9, - "lineCount": 5 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 50, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 44, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 44, - "endColumn": 45, + "endColumn": 82, "lineCount": 1 } }, @@ -14708,192 +10408,192 @@ { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 12, + "endColumn": 74, + "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 51, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 45, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 82, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 73, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 73, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 73, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 9, - "lineCount": 3 + "startColumn": 4, + "endColumn": 73, + "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 4, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 4, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 4, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 78, + "startColumn": 4, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 4, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 4, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 4, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 74, + "startColumn": 4, + "endColumn": 72, "lineCount": 1 } }, @@ -14921,6 +10621,38 @@ "lineCount": 1 } }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 + } + }, { "code": "reportArgumentType", "range": { @@ -14958,7 +10690,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 3 + "lineCount": 5 } }, { @@ -14966,7 +10698,7 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 3 + "lineCount": 5 } }, { @@ -14974,2728 +10706,2634 @@ "range": { "startColumn": 8, "endColumn": 9, - "lineCount": 3 + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 28, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 41, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportOptionalSubscript", - "range": { - "startColumn": 15, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 27, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 63, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportOperatorIssue", - "range": { - "startColumn": 27, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 63, - "endColumn": 66, - "lineCount": 1 - } - }, - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 38, - "endColumn": 41, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 64, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 41, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 45, - "endColumn": 64, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 38, - "endColumn": 41, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 45, - "endColumn": 64, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 52, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 52, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 33, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 52, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 50, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 55, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 39, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 65, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 52, - "endColumn": 64, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 82, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalSubscript", + "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 68, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 48, - "endColumn": 56, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 35, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 38, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 37, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 6 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 47, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 6 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 66, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 6 } }, { "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 50, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 6 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 51, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 41, - "endColumn": 44, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 59, - "endColumn": 62, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 66, - "endColumn": 69, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 84, - "endColumn": 87, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 50, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 64, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 80, - "endColumn": 88, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 16, - "endColumn": 49, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 16, - "endColumn": 49, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 40, - "endColumn": 48, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 27, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 37, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 80, - "endColumn": 88, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 38, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 37, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 22, - "endColumn": 44, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 43, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator_test.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 30, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 13, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 30, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 29, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 20, - "endColumn": 36, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 36, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 40, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 35, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 53, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 43, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 44, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 90, - "endColumn": 92, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 110, - "endColumn": 131, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 58, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 115, - "endColumn": 122, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 58, - "endColumn": 65, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 42, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/injected_flight_collection.py": [ + }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 50, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 49, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 65, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 43, - "endColumn": 65, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 62, - "endColumn": 65, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 43, - "endColumn": 65, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 65, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 62, - "endColumn": 65, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 65, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 65, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 62, - "endColumn": 65, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 65, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 65, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 62, - "endColumn": 65, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 49, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 49, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 49, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 50, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 49, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 20, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/injection.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 46, - "endColumn": 73, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 47, - "endColumn": 71, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 282, - "endColumn": 285, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 321, - "endColumn": 324, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 362, - "endColumn": 365, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 398, - "endColumn": 401, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 77, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 75, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 405, - "endColumn": 408, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 444, - "endColumn": 447, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 485, - "endColumn": 488, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 521, - "endColumn": 524, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/store_flight_data.py": [ + }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 53, - "endColumn": 103, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 69, - "endColumn": 102, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportCallIssue", + "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 85, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 61, - "endColumn": 84, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 84, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 84, + "startColumn": 8, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 40, - "endColumn": 43, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 60, - "endColumn": 63, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 80, - "endColumn": 83, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/netrid/virtual_observer.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 53, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/aggregate_checks.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 84, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 80, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/clear_area_validation.py": [ + }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 39, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 60, - "endColumn": 67, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 43, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 25, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 35, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 53, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 53, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 30, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 30, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 52, - "endColumn": 68, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 66, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 53, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 30, - "lineCount": 1 + "startColumn": 4, + "endColumn": 5, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 66, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportUnusedExpression", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 80, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/expected_interactions_test_steps.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 58, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 106, - "endColumn": 116, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 106, - "endColumn": 116, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 106, - "endColumn": 116, + "startColumn": 16, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 30, + "endColumn": 34, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/wait.py": [ + }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 27, + "startColumn": 39, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 34, + "startColumn": 64, + "endColumn": 68, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py": [ + }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 16, - "endColumn": 30, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 16, - "endColumn": 39, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 16, - "endColumn": 38, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 55, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 53, - "endColumn": 71, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 54, - "endColumn": 73, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 49, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 34, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 34, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 18, - "endColumn": 31, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 44, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 5 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 57, - "endColumn": 77, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 11, - "endColumn": 23, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 61, - "endColumn": 81, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/availability_api_validator.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 53, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 59, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 58, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 61, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 54, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 48, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 62, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 53, + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 + } + }, + { + "code": "reportInvalidTypeVarUse", + "range": { + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 23, - "endColumn": 49, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 28, - "endColumn": 59, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 49, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 58, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 53, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 61, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 53, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 54, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/cr_api_validator.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 41, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 34, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 50, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 47, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 76, - "endColumn": 79, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 41, + "startColumn": 12, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 27, - "endColumn": 36, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 76, - "endColumn": 79, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 41, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 41, + "startColumn": 12, + "endColumn": 74, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 33, - "endColumn": 40, + "startColumn": 50, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 50, - "endColumn": 58, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 46, - "endColumn": 53, + "startColumn": 44, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 47, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/oir_api_validator.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 41, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 36, + "startColumn": 8, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 47, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 77, - "endColumn": 80, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 41, - "lineCount": 1 + "startColumn": 8, + "endColumn": 9, + "lineCount": 3 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 38, + "startColumn": 24, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 77, - "endColumn": 80, + "startColumn": 30, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 48, + "startColumn": 43, + "endColumn": 54, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py": [ + "./monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 42, - "endColumn": 49, + "startColumn": 30, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalSubscript", "range": { - "startColumn": 42, - "endColumn": 49, + "startColumn": 15, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 42, - "endColumn": 50, + "startColumn": 27, + "endColumn": 66, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 56, + "startColumn": 63, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 36, - "endColumn": 44, + "startColumn": 27, + "endColumn": 66, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 50, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 43, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 62, - "endColumn": 68, + "startColumn": 63, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 35, + "startColumn": 38, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOperatorIssue", "range": { - "startColumn": 51, - "endColumn": 70, + "startColumn": 45, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 38, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOperatorIssue", "range": { - "startColumn": 107, - "endColumn": 114, + "startColumn": 45, + "endColumn": 64, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/__init__.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 38, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOperatorIssue", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 45, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 11, - "endColumn": 14, + "startColumn": 30, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOperatorIssue", "range": { - "startColumn": 43, - "endColumn": 46, + "startColumn": 37, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 99, - "endColumn": 102, + "startColumn": 30, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOperatorIssue", "range": { - "startColumn": 29, - "endColumn": 32, + "startColumn": 37, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 112, - "endColumn": 115, + "startColumn": 30, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOperatorIssue", "range": { - "startColumn": 34, - "endColumn": 37, + "startColumn": 37, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalIterable", "range": { - "startColumn": 112, - "endColumn": 115, + "startColumn": 31, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 35, + "startColumn": 36, + "endColumn": 55, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/crud/__init__.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 16, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOperatorIssue", "range": { - "startColumn": 5, - "endColumn": 1, - "lineCount": 5 + "startColumn": 20, + "endColumn": 65, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 34, + "startColumn": 52, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOperatorIssue", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 20, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalSubscript", "range": { - "startColumn": 5, - "endColumn": 1, - "lineCount": 5 + "startColumn": 24, + "endColumn": 68, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 34, + "startColumn": 48, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 24, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportCallIssue", "range": { - "startColumn": 5, - "endColumn": 71, + "startColumn": 16, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 26, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalIterable", "range": { - "startColumn": 5, - "endColumn": 45, + "startColumn": 27, + "endColumn": 47, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/sub/crud/__init__.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalIterable", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 46, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 31, + "startColumn": 30, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalIterable", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 31, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 92, - "endColumn": 101, + "startColumn": 41, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 23, + "startColumn": 59, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 52, + "startColumn": 66, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 84, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 31, + "startColumn": 16, + "endColumn": 49, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 16, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 51, + "startColumn": 40, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 47, + "startColumn": 16, + "endColumn": 27, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 45, + "startColumn": 16, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportCallIssue", "range": { - "startColumn": 42, - "endColumn": 45, + "startColumn": 16, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 38, - "endColumn": 46, + "startColumn": 26, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportCallIssue", "range": { - "startColumn": 24, - "endColumn": 38, + "startColumn": 22, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 50, + "startColumn": 32, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator_test.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 30, - "endColumn": 38, + "startColumn": 26, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 42, + "startColumn": 12, + "endColumn": 13, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 41, - "endColumn": 51, + "startColumn": 26, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 47, + "startColumn": 28, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 24, - "endColumn": 45, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 45, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 16, - "endColumn": 37, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 37, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 51, - "endColumn": 61, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 62, - "endColumn": 67, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 49, - "endColumn": 57, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, - "endColumn": 63, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 67, - "endColumn": 88, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 85, - "endColumn": 88, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 78, - "endColumn": 85, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 36, - "endColumn": 38, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 69, - "endColumn": 71, + "startColumn": 20, + "endColumn": 36, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 48, + "startColumn": 16, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 60, - "endColumn": 63, + "startColumn": 16, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 37, - "endColumn": 47, + "startColumn": 110, + "endColumn": 131, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/injected_flight_collection.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportCallIssue", "range": { - "startColumn": 48, - "endColumn": 53, + "startColumn": 20, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 75, - "endColumn": 85, + "startColumn": 30, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 86, - "endColumn": 91, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 45, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 51, + "startColumn": 62, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 73, - "endColumn": 81, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 82, - "endColumn": 87, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 30, + "startColumn": 62, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 57, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 79, - "endColumn": 81, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 69, - "endColumn": 71, + "startColumn": 62, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 52, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 64, - "endColumn": 67, + "startColumn": 43, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 51, + "startColumn": 62, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 52, - "endColumn": 57, + "startColumn": 20, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 79, - "endColumn": 89, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOperatorIssue", "range": { - "startColumn": 90, - "endColumn": 95, + "startColumn": 20, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, + "startColumn": 46, "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportCallIssue", "range": { - "startColumn": 50, - "endColumn": 55, + "startColumn": 20, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 77, - "endColumn": 85, + "startColumn": 30, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportReturnType", "range": { - "startColumn": 86, - "endColumn": 91, + "startColumn": 15, + "endColumn": 20, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/injection.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 52, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 51, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 18, + "startColumn": 46, + "endColumn": 73, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 24, + "startColumn": 47, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 45, + "startColumn": 282, + "endColumn": 285, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 51, + "startColumn": 321, + "endColumn": 324, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 79, - "endColumn": 81, + "startColumn": 362, + "endColumn": 365, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 65, - "endColumn": 67, + "startColumn": 398, + "endColumn": 401, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 74, - "endColumn": 89, + "startColumn": 50, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 47, + "startColumn": 51, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 99, - "endColumn": 103, + "startColumn": 405, + "endColumn": 408, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 55, - "endColumn": 57, + "startColumn": 444, + "endColumn": 447, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 29, + "startColumn": 485, + "endColumn": 488, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 75, - "endColumn": 77, + "startColumn": 521, + "endColumn": 524, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/store_flight_data.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportCallIssue", "range": { - "startColumn": 65, - "endColumn": 67, + "startColumn": 53, + "endColumn": 103, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 29, + "startColumn": 69, + "endColumn": 102, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportCallIssue", "range": { - "startColumn": 75, - "endColumn": 77, + "startColumn": 45, + "endColumn": 85, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 65, - "endColumn": 67, + "startColumn": 61, + "endColumn": 84, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 57, - "endColumn": 59, + "startColumn": 24, + "endColumn": 84, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 66, - "endColumn": 68, + "startColumn": 24, + "endColumn": 84, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 88, - "endColumn": 91, + "startColumn": 40, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 47, + "startColumn": 60, + "endColumn": 63, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 53, + "startColumn": 80, + "endColumn": 83, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/netrid/virtual_observer.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 75, - "endColumn": 85, + "startColumn": 49, + "endColumn": 53, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/aggregate_checks.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 86, - "endColumn": 91, + "startColumn": 20, + "endColumn": 84, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 45, + "startColumn": 16, + "endColumn": 80, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/clear_area_validation.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 46, - "endColumn": 51, + "startColumn": 29, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 73, - "endColumn": 81, + "startColumn": 60, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 82, - "endColumn": 87, + "startColumn": 33, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 53, - "endColumn": 58, + "startColumn": 15, + "endColumn": 25, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 23, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 53, - "endColumn": 58, + "startColumn": 51, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 51, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, - "endColumn": 63, + "startColumn": 28, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 56, - "endColumn": 61, + "startColumn": 28, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 25, - "endColumn": 41, + "startColumn": 52, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 51, + "startColumn": 50, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 29, + "startColumn": 51, + "endColumn": 53, "lineCount": 1 } }, @@ -17708,204 +13346,200 @@ } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 37, + "startColumn": 50, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportUnusedExpression", "range": { - "startColumn": 61, - "endColumn": 63, + "startColumn": 8, + "endColumn": 80, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/expected_interactions_test_steps.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 137, - "endColumn": 140, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 64, - "endColumn": 66, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 25, - "endColumn": 41, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 51, + "startColumn": 16, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 26, - "endColumn": 37, + "startColumn": 16, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 30, + "startColumn": 16, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 45, + "startColumn": 45, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 61, - "endColumn": 63, + "startColumn": 53, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 30, + "startColumn": 54, + "endColumn": 73, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 45, + "startColumn": 35, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 61, - "endColumn": 63, + "startColumn": 26, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 137, - "endColumn": 148, + "startColumn": 26, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, + "startColumn": 18, "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 61, - "endColumn": 63, + "startColumn": 16, + "endColumn": 44, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 105, - "endColumn": 107, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 57, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 12, - "endColumn": 19, + "startColumn": 11, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 19, + "startColumn": 61, + "endColumn": 81, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/availability_api_validator.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 36, + "startColumn": 28, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 39, + "startColumn": 28, + "endColumn": 59, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 47, + "startColumn": 32, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 32, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 47, + "startColumn": 28, + "endColumn": 54, "lineCount": 1 } }, @@ -17913,598 +13547,614 @@ "code": "reportPossiblyUnboundVariable", "range": { "startColumn": 37, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 25, - "endColumn": 37, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 66, - "endColumn": 73, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 42, - "endColumn": 55, + "startColumn": 55, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 25, - "endColumn": 37, + "startColumn": 28, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 64, - "endColumn": 71, + "startColumn": 23, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 55, + "startColumn": 28, + "endColumn": 59, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 84, + "startColumn": 23, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 75, - "endColumn": 82, + "startColumn": 32, + "endColumn": 58, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 66, + "startColumn": 27, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 57, - "endColumn": 64, + "startColumn": 32, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 28, + "startColumn": 27, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 89, - "endColumn": 98, + "startColumn": 28, + "endColumn": 54, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/cr_api_validator.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 25, - "endColumn": 48, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 61, - "endColumn": 68, + "startColumn": 27, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 56, + "startColumn": 20, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 65, - "endColumn": 72, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 25, - "endColumn": 48, + "startColumn": 76, + "endColumn": 79, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 25, - "endColumn": 48, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 33, - "endColumn": 56, + "startColumn": 27, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 27, + "startColumn": 76, + "endColumn": 79, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 44, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 45, + "startColumn": 33, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 62, - "endColumn": 70, + "startColumn": 50, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, - "endColumn": 75, + "startColumn": 46, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 62, - "endColumn": 70, + "startColumn": 39, + "endColumn": 47, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 58, - "endColumn": 75, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/oir_api_validator.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 22, - "endColumn": 25, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 24, - "endColumn": 41, + "startColumn": 28, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 35, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 77, + "startColumn": 77, + "endColumn": 80, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 29, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 31, - "endColumn": 45, + "startColumn": 28, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 63, + "startColumn": 77, "endColumn": 80, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 67, - "endColumn": 84, + "startColumn": 40, + "endColumn": 48, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py": [ { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 42, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 38, + "startColumn": 42, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 38, + "startColumn": 42, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 37, + "startColumn": 51, + "endColumn": 56, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 37, + "startColumn": 36, + "endColumn": 44, "lineCount": 1 } }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 45, + "endColumn": 50, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 54, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 54, + "startColumn": 62, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 74, - "endColumn": 81, + "startColumn": 29, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 57, + "startColumn": 51, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 39, - "endColumn": 60, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 57, - "endColumn": 60, + "startColumn": 107, + "endColumn": 114, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/__init__.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 24, - "endColumn": 36, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 76, - "endColumn": 86, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 76, - "endColumn": 82, + "startColumn": 11, + "endColumn": 14, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 25, - "endColumn": 35, + "startColumn": 43, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 66, - "endColumn": 73, + "startColumn": 99, + "endColumn": 102, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 55, + "startColumn": 29, + "endColumn": 32, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 19, - "endColumn": 28, + "startColumn": 112, + "endColumn": 115, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 89, - "endColumn": 98, + "startColumn": 34, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 104, - "endColumn": 110, + "startColumn": 112, + "endColumn": 115, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/report.py": [ + }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 34, + "endColumn": 35, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/oir/crud/__init__.py": [ { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 29, - "endColumn": 35, - "lineCount": 1 + "startColumn": 5, + "endColumn": 1, + "lineCount": 5 } }, { "code": "reportArgumentType", "range": { "startColumn": 20, - "endColumn": 84, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 28, - "endColumn": 35, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportReturnType", "range": { - "startColumn": 42, - "endColumn": 46, + "startColumn": 5, + "endColumn": 1, + "lineCount": 5 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 54, - "endColumn": 55, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportReturnType", "range": { - "startColumn": 53, - "endColumn": 54, + "startColumn": 5, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 12, - "endColumn": 38, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 20, - "endColumn": 84, + "startColumn": 5, + "endColumn": 45, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/sub/crud/__init__.py": [ + { + "code": "reportInvalidTypeVarUse", + "range": { + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 28, - "endColumn": 35, + "startColumn": 15, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 42, - "endColumn": 46, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 54, - "endColumn": 55, + "startColumn": 92, + "endColumn": 101, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportReturnType", "range": { - "startColumn": 16, - "endColumn": 17, + "startColumn": 19, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 12, - "endColumn": 38, + "startColumn": 15, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 12, - "endColumn": 19, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 12, - "endColumn": 19, + "startColumn": 15, + "endColumn": 31, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py": [ { "code": "reportArgumentType", "range": { @@ -18514,179 +14164,187 @@ } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 35, + "startColumn": 41, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 38, + "startColumn": 39, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 79, - "endColumn": 81, + "startColumn": 24, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 84, + "startColumn": 42, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 35, + "startColumn": 38, + "endColumn": 46, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, + "startColumn": 24, "endColumn": 38, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { "startColumn": 42, - "endColumn": 46, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 47, + "startColumn": 30, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 84, + "startColumn": 32, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 28, - "endColumn": 35, + "startColumn": 41, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 38, + "startColumn": 39, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 46, + "startColumn": 24, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 47, + "startColumn": 42, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 19, + "startColumn": 16, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 19, + "startColumn": 34, + "endColumn": 37, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 51, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 62, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 33, - "endColumn": 37, + "startColumn": 49, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 32, - "endColumn": 36, + "startColumn": 58, + "endColumn": 63, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 59, + "startColumn": 67, + "endColumn": 88, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 49, + "startColumn": 85, + "endColumn": 88, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 38, + "startColumn": 78, + "endColumn": 85, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 37, "endColumn": 43, "lineCount": 1 } @@ -18694,1392 +14352,1432 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 39, + "startColumn": 36, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 47, + "startColumn": 69, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 38, - "endColumn": 43, + "startColumn": 45, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 12, - "endColumn": 47, + "startColumn": 60, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 39, - "endColumn": 13, - "lineCount": 3 + "startColumn": 37, + "endColumn": 47, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 40, + "startColumn": 48, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 47, + "startColumn": 75, + "endColumn": 85, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 53, - "endColumn": 81, + "startColumn": 86, + "endColumn": 91, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 13, - "lineCount": 3 + "startColumn": 37, + "endColumn": 45, + "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 53, - "endColumn": 81, + "startColumn": 46, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 55, + "startColumn": 73, + "endColumn": 81, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 48, - "endColumn": 53, + "startColumn": 82, + "endColumn": 87, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 28, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 55, + "endColumn": 57, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 75, - "endColumn": 82, + "startColumn": 79, + "endColumn": 81, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 48, + "startColumn": 69, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 74, - "endColumn": 81, + "startColumn": 49, + "endColumn": 52, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py": [ + }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 37, - "endColumn": 51, + "startColumn": 64, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 33, + "startColumn": 41, "endColumn": 51, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 20, + "startColumn": 52, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 88, + "startColumn": 79, "endColumn": 89, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 43, + "startColumn": 90, + "endColumn": 95, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 19, - "endColumn": 22, + "startColumn": 41, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 18, - "endColumn": 19, + "startColumn": 50, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 52, + "startColumn": 77, + "endColumn": 85, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 36, - "endColumn": 39, + "startColumn": 86, + "endColumn": 91, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 43, + "startColumn": 49, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 19, - "endColumn": 21, + "startColumn": 48, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 18, - "endColumn": 19, + "startColumn": 15, + "endColumn": 18, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 20, + "endColumn": 24, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 40, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 36, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 79, + "endColumn": 81, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 65, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 74, + "endColumn": 89, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 43, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 99, + "endColumn": 103, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 55, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 26, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 75, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 65, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 26, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 75, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 65, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 57, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 42, + "startColumn": 66, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 63, - "endColumn": 71, + "startColumn": 88, + "endColumn": 91, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 67, - "endColumn": 75, + "startColumn": 37, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 48, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 75, + "endColumn": 85, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 86, + "endColumn": 91, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 37, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 46, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 73, + "endColumn": 81, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 82, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 53, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 53, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 58, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 56, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 25, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 49, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 26, + "endColumn": 29, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 61, + "startColumn": 28, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 33, - "endColumn": 53, + "startColumn": 34, + "endColumn": 37, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 61, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 54, - "endColumn": 61, + "startColumn": 137, + "endColumn": 140, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 33, - "endColumn": 53, + "startColumn": 64, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 25, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 44, + "startColumn": 49, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 27, - "endColumn": 28, + "startColumn": 26, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 49, + "startColumn": 28, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 46, - "endColumn": 49, + "startColumn": 34, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 57, + "startColumn": 61, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 27, - "endColumn": 29, + "startColumn": 28, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 33, - "endColumn": 53, + "startColumn": 34, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 61, + "endColumn": 63, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 27, - "endColumn": 28, + "startColumn": 137, + "endColumn": 148, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 49, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 49, + "startColumn": 61, + "endColumn": 63, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 57, + "startColumn": 105, + "endColumn": 107, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 35, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 21, - "endColumn": 32, + "startColumn": 12, + "endColumn": 19, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 17, - "endColumn": 28, + "startColumn": 12, + "endColumn": 19, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 56, - "endColumn": 67, + "startColumn": 24, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 33, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 59, - "endColumn": 87, + "startColumn": 37, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 50, - "endColumn": 53, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, + "startColumn": 37, "endColumn": 47, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py": [ + }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { "startColumn": 37, - "endColumn": 51, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 33, - "endColumn": 44, + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 66, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 43, + "startColumn": 42, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 19, - "endColumn": 20, + "startColumn": 25, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 97, - "endColumn": 98, + "startColumn": 64, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { "startColumn": 42, - "endColumn": 43, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 23, + "startColumn": 45, + "endColumn": 84, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 18, - "endColumn": 19, + "startColumn": 75, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 59, - "endColumn": 87, + "startColumn": 12, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 57, + "endColumn": 64, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 28, - "endColumn": 32, + "startColumn": 19, + "endColumn": 28, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 43, + "startColumn": 89, + "endColumn": 98, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 23, + "startColumn": 25, + "endColumn": 48, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 18, - "endColumn": 19, + "startColumn": 61, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 49, + "endColumn": 56, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 65, + "endColumn": 72, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 25, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 25, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 33, + "endColumn": 56, "lineCount": 1 } }, + { + "code": "reportPossiblyUnboundVariable", + "range": { + "startColumn": 15, + "endColumn": 27, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 24, + "endColumn": 44, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 24, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 58, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 58, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 22, + "endColumn": 25, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 24, + "endColumn": 41, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 16, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 34, + "endColumn": 77, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 12, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 31, + "endColumn": 45, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 63, + "endColumn": 80, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 67, + "endColumn": 84, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 24, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 24, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 16, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 34, + "endColumn": 37, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 33, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 43, + "startColumn": 51, + "endColumn": 54, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 63, - "endColumn": 71, + "startColumn": 74, + "endColumn": 81, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 67, - "endColumn": 75, + "startColumn": 50, + "endColumn": 57, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 39, + "endColumn": 60, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 57, + "endColumn": 60, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.py": [ { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 24, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 76, + "endColumn": 86, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 76, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 25, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 66, + "endColumn": 73, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 42, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 19, + "endColumn": 28, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 89, + "endColumn": 98, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/report.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 52, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 29, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 20, + "endColumn": 84, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 28, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 57, + "startColumn": 42, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalOperand", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 16, - "endColumn": 36, + "startColumn": 54, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 58, - "endColumn": 61, + "startColumn": 53, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 61, + "startColumn": 12, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 44, + "startColumn": 20, + "endColumn": 84, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 45, + "startColumn": 28, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 28, - "endColumn": 29, + "startColumn": 42, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 29, - "endColumn": 50, + "startColumn": 54, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 50, + "startColumn": 16, + "endColumn": 17, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 58, + "startColumn": 12, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 31, + "startColumn": 12, + "endColumn": 19, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 55, + "startColumn": 12, + "endColumn": 19, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 52, - "endColumn": 55, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 29, + "startColumn": 29, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 50, + "startColumn": 12, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 50, + "startColumn": 79, + "endColumn": 81, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 58, + "startColumn": 20, + "endColumn": 84, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 34, + "startColumn": 28, "endColumn": 35, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 21, - "endColumn": 32, + "startColumn": 12, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 17, - "endColumn": 28, + "startColumn": 42, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 56, - "endColumn": 67, + "startColumn": 46, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 20, + "endColumn": 84, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 83, + "startColumn": 28, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 38, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 31, - "endColumn": 35, + "startColumn": 42, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 40, + "startColumn": 46, "endColumn": 47, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 51, + "startColumn": 12, + "endColumn": 19, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 44, + "startColumn": 12, + "endColumn": 19, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 47, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, @@ -20092,324 +15790,334 @@ } }, { - "code": "reportReturnType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 15, - "endColumn": 41, + "startColumn": 33, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 20, - "endColumn": 32, + "startColumn": 32, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 60, + "startColumn": 8, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 30, + "startColumn": 12, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 31, - "endColumn": 36, + "startColumn": 38, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 34, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 28, + "startColumn": 42, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 34, + "startColumn": 38, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 12, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 26, - "endColumn": 33, - "lineCount": 1 + "startColumn": 39, + "endColumn": 13, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 41, - "endColumn": 61, + "startColumn": 16, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 53, - "endColumn": 60, + "startColumn": 12, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { "startColumn": 53, - "endColumn": 60, + "endColumn": 81, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 68, - "endColumn": 75, - "lineCount": 1 + "startColumn": 19, + "endColumn": 13, + "lineCount": 3 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 68, - "endColumn": 75, + "startColumn": 53, + "endColumn": 81, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 50, + "startColumn": 50, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, + "startColumn": 48, + "endColumn": 53, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 37, "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 42, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 75, + "endColumn": 82, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 41, + "startColumn": 40, + "endColumn": 48, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 74, + "endColumn": 81, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 38, + "startColumn": 37, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 33, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 60, - "endColumn": 78, + "startColumn": 19, + "endColumn": 20, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 60, - "endColumn": 78, + "startColumn": 88, + "endColumn": 89, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 60, - "endColumn": 67, + "startColumn": 42, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 60, - "endColumn": 67, + "startColumn": 19, + "endColumn": 22, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 16, - "endColumn": 41, + "startColumn": 18, + "endColumn": 19, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 60, - "endColumn": 67, + "startColumn": 24, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 60, - "endColumn": 67, + "startColumn": 36, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 51, - "endColumn": 58, + "startColumn": 42, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 51, - "endColumn": 58, + "startColumn": 19, + "endColumn": 21, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 47, - "endColumn": 71, + "startColumn": 18, + "endColumn": 19, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 32, - "endColumn": 52, + "startColumn": 16, + "endColumn": 42, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 63, + "endColumn": 71, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 57, - "endColumn": 64, + "startColumn": 67, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 45, - "endColumn": 52, + "startColumn": 54, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 46, + "startColumn": 33, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 39, - "endColumn": 46, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/uss_availability_synchronization.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 51, + "startColumn": 54, + "endColumn": 61, "lineCount": 1 } }, @@ -20417,138 +16125,142 @@ "code": "reportArgumentType", "range": { "startColumn": 33, - "endColumn": 44, + "endColumn": 53, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/test_step_fragments.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 110, - "endColumn": 121, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 41, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", + "range": { + "startColumn": 27, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", "range": { - "startColumn": 111, - "endColumn": 122, + "startColumn": 29, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 47, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 93, - "endColumn": 104, + "startColumn": 50, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 43, - "endColumn": 50, + "startColumn": 27, + "endColumn": 29, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 56, - "endColumn": 62, + "startColumn": 33, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 69, - "endColumn": 80, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 43, - "endColumn": 50, + "startColumn": 27, + "endColumn": 28, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 56, - "endColumn": 63, + "startColumn": 29, + "endColumn": 49, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/validators/cr_validator.py": [ + }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 68, - "endColumn": 75, + "startColumn": 46, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 57, - "endColumn": 65, + "startColumn": 50, + "endColumn": 57, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 53, - "endColumn": 61, + "startColumn": 34, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 54, - "endColumn": 66, + "startColumn": 21, + "endColumn": 32, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 57, - "endColumn": 69, + "startColumn": 17, + "endColumn": 28, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 57, + "startColumn": 56, "endColumn": 67, "lineCount": 1 } @@ -20556,146 +16268,138 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 57, - "endColumn": 65, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 38, + "startColumn": 59, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 12, - "endColumn": 36, + "startColumn": 50, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 40, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 45, + "startColumn": 37, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 40, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/validators/oir_validator.py": [ - { - "code": "reportOptionalMemberAccess", - "range": { - "startColumn": 69, - "endColumn": 76, + "startColumn": 33, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 57, - "endColumn": 65, + "startColumn": 24, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 53, - "endColumn": 61, + "startColumn": 19, + "endColumn": 20, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 56, - "endColumn": 68, + "startColumn": 97, + "endColumn": 98, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 58, - "endColumn": 70, + "startColumn": 42, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 58, - "endColumn": 68, + "startColumn": 20, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 58, - "endColumn": 66, + "startColumn": 18, + "endColumn": 19, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 59, + "endColumn": 87, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 58, - "endColumn": 63, + "startColumn": 28, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 16, - "endColumn": 39, + "startColumn": 42, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 33, + "startColumn": 20, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 12, - "endColumn": 37, + "startColumn": 18, + "endColumn": 19, "lineCount": 1 } }, @@ -20703,56 +16407,54 @@ "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 41, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 45, + "startColumn": 63, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 41, + "startColumn": 67, + "endColumn": 75, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/dss/validators/subscription_validator.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalOperand", "range": { "startColumn": 16, - "endColumn": 37, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 40, + "startColumn": 58, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 47, - "endColumn": 65, + "startColumn": 46, + "endColumn": 61, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, + "startColumn": 24, "endColumn": 44, "lineCount": 1 } @@ -20760,452 +16462,466 @@ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 53, - "endColumn": 71, + "startColumn": 42, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 20, - "endColumn": 45, + "startColumn": 28, + "endColumn": 29, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 44, + "startColumn": 29, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 53, - "endColumn": 71, + "startColumn": 47, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 52, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 16, - "endColumn": 50, + "startColumn": 28, + "endColumn": 31, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 20, - "endColumn": 23, + "startColumn": 34, + "endColumn": 55, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 53, - "endColumn": 71, + "startColumn": 52, + "endColumn": 55, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py": [ + }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", "range": { - "startColumn": 17, - "endColumn": 82, + "startColumn": 29, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 34, - "endColumn": 42, + "startColumn": 47, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 30, - "endColumn": 38, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 60, - "endColumn": 65, + "startColumn": 34, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 192, - "endColumn": 197, + "startColumn": 21, + "endColumn": 32, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 62, - "endColumn": 67, + "startColumn": 17, + "endColumn": 28, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 192, - "endColumn": 197, + "startColumn": 56, + "endColumn": 67, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 174, - "endColumn": 179, + "startColumn": 55, + "endColumn": 83, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 45, - "endColumn": 50, + "startColumn": 31, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 170, - "endColumn": 175, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 35, + "startColumn": 37, + "endColumn": 51, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 53, - "endColumn": 62, + "startColumn": 33, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 54, - "endColumn": 56, + "startColumn": 33, + "endColumn": 47, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/make_uss_report.py": [ + }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 49, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportReturnType", "range": { - "startColumn": 46, - "endColumn": 65, + "startColumn": 15, + "endColumn": 41, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/assets/make_assets.py": [ + }, { - "code": "reportMissingImports", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 7, - "endColumn": 10, + "startColumn": 20, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 40, + "endColumn": 60, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 20, + "endColumn": 30, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 31, + "endColumn": 36, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, + "startColumn": 23, "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 20, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", "range": { "startColumn": 29, - "endColumn": 43, + "endColumn": 34, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, + "startColumn": 23, "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 26, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 41, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 53, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 53, + "endColumn": 60, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 68, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 68, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 20, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, + "startColumn": 23, "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 44, + "startColumn": 20, + "endColumn": 42, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, + "startColumn": 23, "endColumn": 43, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 28, + "startColumn": 20, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { "startColumn": 23, - "endColumn": 35, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 53, + "startColumn": 20, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 23, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 60, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 61, + "startColumn": 60, + "endColumn": 78, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/assets/make_assets.py": [ + }, { - "code": "reportMissingImports", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 7, - "endColumn": 10, + "startColumn": 60, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 60, + "endColumn": 67, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 16, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 60, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 60, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 44, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 51, + "endColumn": 58, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 47, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 32, + "endColumn": 52, "lineCount": 1 } }, @@ -21218,364 +16934,338 @@ } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 43, + "startColumn": 57, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 44, + "startColumn": 45, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 39, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 44, + "startColumn": 39, + "endColumn": 46, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/uss_availability_synchronization.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 44, + "startColumn": 37, + "endColumn": 51, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 33, + "endColumn": 44, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/test_step_fragments.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 35, + "startColumn": 110, + "endColumn": 121, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 65, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 111, + "endColumn": 122, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 65, + "startColumn": 40, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 93, + "endColumn": 104, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 21, + "startColumn": 43, + "endColumn": 50, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 56, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 31, + "startColumn": 69, + "endColumn": 80, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 35, + "startColumn": 43, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 35, - "endColumn": 53, + "startColumn": 56, + "endColumn": 63, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/validators/cr_validator.py": [ { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 23, - "endColumn": 35, + "startColumn": 68, + "endColumn": 75, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 33, - "endColumn": 71, + "startColumn": 57, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 62, - "endColumn": 69, + "startColumn": 53, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 23, + "startColumn": 54, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 15, - "endColumn": 21, + "startColumn": 57, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 33, + "startColumn": 57, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportArgumentType", - "range": { - "startColumn": 39, - "endColumn": 49, - "lineCount": 1 - } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py": [ - { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 33, - "endColumn": 46, + "startColumn": 57, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 21, + "startColumn": 16, + "endColumn": 38, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py": [ + }, { - "code": "reportAttributeAccessIssue", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 35, + "startColumn": 12, + "endColumn": 36, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 56, - "endColumn": 66, + "startColumn": 16, + "endColumn": 40, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/assets/make_assets.py": [ + }, { - "code": "reportMissingImports", + "code": "reportArgumentType", "range": { - "startColumn": 7, - "endColumn": 10, + "startColumn": 16, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 37, - "endColumn": 43, + "startColumn": 16, + "endColumn": 40, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 23, - "endColumn": 35, - "lineCount": 1 - } - }, + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/validators/oir_validator.py": [ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 53, + "startColumn": 69, + "endColumn": 76, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 31, + "startColumn": 57, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 76, + "startColumn": 53, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 31, + "startColumn": 56, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 78, + "startColumn": 58, + "endColumn": 70, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 20, - "endColumn": 31, + "startColumn": 58, + "endColumn": 68, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 76, + "startColumn": 58, + "endColumn": 66, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 85, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/test_steps/validate_notification_received.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 58, + "endColumn": 63, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 18, - "endColumn": 34, + "startColumn": 16, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportGeneralTypeIssues", + "code": "reportArgumentType", "range": { - "startColumn": 24, - "endColumn": 40, + "startColumn": 12, + "endColumn": 33, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportArgumentType", "range": { - "startColumn": 82, - "endColumn": 88, + "startColumn": 12, + "endColumn": 37, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 23, + "endColumn": 41, "lineCount": 1 } }, @@ -21583,23 +17273,25 @@ "code": "reportArgumentType", "range": { "startColumn": 16, - "endColumn": 52, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 44, - "endColumn": 52, + "startColumn": 16, + "endColumn": 41, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/dss/validators/subscription_validator.py": [ { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 39, + "startColumn": 16, + "endColumn": 37, "lineCount": 1 } }, @@ -21607,1102 +17299,1136 @@ "code": "reportArgumentType", "range": { "startColumn": 20, - "endColumn": 47, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportCallIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 73, + "startColumn": 47, + "endColumn": 65, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 72, + "startColumn": 20, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportAssignmentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 51, - "endColumn": 55, + "startColumn": 53, + "endColumn": 71, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 74, - "endColumn": 80, + "startColumn": 20, + "endColumn": 45, "lineCount": 1 } }, { - "code": "reportAssignmentType", + "code": "reportArgumentType", "range": { - "startColumn": 36, - "endColumn": 40, + "startColumn": 20, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportIncompatibleVariableOverride", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 8, - "endColumn": 16, + "startColumn": 53, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportIncompatibleVariableOverride", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 16, + "startColumn": 16, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportIncompatibleMethodOverride", + "code": "reportArgumentType", "range": { - "startColumn": 8, - "endColumn": 14, + "startColumn": 16, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 20, + "endColumn": 23, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 53, + "endColumn": 71, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/astm/utm/versioning/evaluate_system_versions.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py": [ { - "code": "reportArgumentType", + "code": "reportOperatorIssue", "range": { - "startColumn": 46, - "endColumn": 61, + "startColumn": 17, + "endColumn": 82, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/documentation/autoformat.py": [ + }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 50, - "endColumn": 63, + "startColumn": 34, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 61, - "endColumn": 69, + "startColumn": 30, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 49, + "startColumn": 60, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportIndexIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 38, + "startColumn": 192, + "endColumn": 197, "lineCount": 1 } }, { - "code": "reportIndexIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 24, - "endColumn": 38, + "startColumn": 62, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 74, - "endColumn": 82, + "startColumn": 192, + "endColumn": 197, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 41, + "startColumn": 49, + "endColumn": 54, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 41, + "startColumn": 174, + "endColumn": 179, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 61, - "endColumn": 69, + "startColumn": 45, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportAttributeAccessIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 41, - "endColumn": 49, + "startColumn": 170, + "endColumn": 175, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/documentation/requirements.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py": [ { - "code": "reportArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 12, - "endColumn": 40, + "startColumn": 23, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 22, - "endColumn": 50, + "startColumn": 53, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 21, + "startColumn": 54, + "endColumn": 56, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/make_uss_report.py": [ { - "code": "reportArgumentType", + "code": "reportOptionalIterable", "range": { - "startColumn": 12, - "endColumn": 21, + "startColumn": 37, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 42, + "startColumn": 46, + "endColumn": 65, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/assets/make_assets.py": [ + { + "code": "reportMissingImports", + "range": { + "startColumn": 7, + "endColumn": 10, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 29, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 35, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 12, - "endColumn": 32, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/documentation/validation.py": [ + }, { "code": "reportArgumentType", "range": { - "startColumn": 33, - "endColumn": 46, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 47, - "endColumn": 60, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 46, - "endColumn": 59, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 45, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 46, - "endColumn": 59, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 49, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { "code": "reportArgumentType", "range": { - "startColumn": 64, - "endColumn": 77, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/flight_planning/prioritization_test_steps.py": [ + }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py": [ { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 46, + "endColumn": 61, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py": [ + "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/assets/make_assets.py": [ { - "code": "reportInvalidTypeVarUse", + "code": "reportMissingImports", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 7, + "endColumn": 10, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportArgumentType", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 29, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 16, - "endColumn": 20, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 23, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 84, - "endColumn": 88, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 127, - "endColumn": 131, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 22, - "endColumn": 26, + "startColumn": 29, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 31, - "endColumn": 35, + "startColumn": 29, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 39, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 23, + "startColumn": 29, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 60, - "endColumn": 64, + "startColumn": 29, + "endColumn": 44, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 103, - "endColumn": 107, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 42, - "endColumn": 47, + "startColumn": 50, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 12, - "endColumn": 16, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 38, + "startColumn": 50, + "endColumn": 65, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 65, - "endColumn": 69, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportReturnType", "range": { - "startColumn": 108, - "endColumn": 112, + "startColumn": 15, + "endColumn": 21, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 34, - "endColumn": 39, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 11, - "endColumn": 15, + "startColumn": 16, + "endColumn": 31, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py": [ { - "code": "reportInvalidTypeVarUse", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 23, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 30, - "endColumn": 35, + "startColumn": 33, + "endColumn": 71, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 29, - "endColumn": 33, + "startColumn": 62, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 57, - "endColumn": 61, + "startColumn": 20, + "endColumn": 23, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 66, - "endColumn": 70, + "startColumn": 15, + "endColumn": 21, + "lineCount": 1 + } + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 33, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportReturnType", "range": { - "startColumn": 12, - "endColumn": 16, + "startColumn": 15, + "endColumn": 21, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/prep_planners.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 16, - "endColumn": 20, + "startColumn": 23, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 19, - "endColumn": 23, + "startColumn": 56, + "endColumn": 66, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/assets/make_assets.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportMissingImports", "range": { - "startColumn": 64, - "endColumn": 68, + "startColumn": 7, + "endColumn": 10, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 111, - "endColumn": 115, + "startColumn": 37, + "endColumn": 43, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 69, - "endColumn": 73, + "startColumn": 23, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 116, - "endColumn": 120, + "startColumn": 35, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 34, - "endColumn": 39, + "startColumn": 20, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 14, - "endColumn": 30, + "startColumn": 35, + "endColumn": 76, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/interuss/flight_authorization/general_flight_authorization.py": [ + }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 54, - "endColumn": 59, + "startColumn": 20, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 35, + "endColumn": 78, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 20, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 35, + "endColumn": 76, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 35, + "endColumn": 85, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/test_steps/validate_notification_received.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 51, - "endColumn": 59, + "startColumn": 18, + "endColumn": 34, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportGeneralTypeIssues", "range": { - "startColumn": 25, - "endColumn": 29, + "startColumn": 24, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalIterable", "range": { - "startColumn": 23, - "endColumn": 27, + "startColumn": 82, + "endColumn": 88, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 76, - "endColumn": 80, + "startColumn": 16, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 120, - "endColumn": 124, + "startColumn": 44, + "endColumn": 52, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 12, + "endColumn": 39, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 73, - "endColumn": 77, + "startColumn": 20, + "endColumn": 47, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportCallIssue", "range": { - "startColumn": 23, - "endColumn": 27, + "startColumn": 29, + "endColumn": 73, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 68, + "startColumn": 33, "endColumn": 72, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAssignmentType", "range": { - "startColumn": 153, - "endColumn": 157, + "startColumn": 51, + "endColumn": 55, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 74, + "endColumn": 80, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAssignmentType", "range": { - "startColumn": 73, - "endColumn": 77, + "startColumn": 36, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportIncompatibleVariableOverride", "range": { - "startColumn": 23, - "endColumn": 27, + "startColumn": 8, + "endColumn": 16, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportIncompatibleVariableOverride", "range": { - "startColumn": 76, - "endColumn": 80, + "startColumn": 8, + "endColumn": 16, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportIncompatibleMethodOverride", "range": { - "startColumn": 120, - "endColumn": 124, + "startColumn": 8, + "endColumn": 14, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 73, - "endColumn": 77, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/astm/utm/versioning/evaluate_system_versions.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 27, + "startColumn": 46, + "endColumn": 61, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/documentation/autoformat.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 68, - "endColumn": 72, + "startColumn": 50, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 153, - "endColumn": 157, + "startColumn": 61, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 41, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportIndexIssue", "range": { - "startColumn": 73, - "endColumn": 77, + "startColumn": 24, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportIndexIssue", "range": { - "startColumn": 16, - "endColumn": 20, + "startColumn": 24, + "endColumn": 38, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 20, - "endColumn": 24, + "startColumn": 74, + "endColumn": 82, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 28, - "endColumn": 32, + "startColumn": 35, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 163, - "endColumn": 167, + "startColumn": 35, + "endColumn": 41, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 59, - "endColumn": 67, + "startColumn": 61, + "endColumn": 69, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 45, + "startColumn": 41, "endColumn": 49, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/documentation/requirements.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 32, + "startColumn": 12, + "endColumn": 40, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 180, - "endColumn": 184, + "startColumn": 22, + "endColumn": 50, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 59, - "endColumn": 67, + "startColumn": 12, + "endColumn": 21, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 45, - "endColumn": 49, + "startColumn": 12, + "endColumn": 21, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 15, - "endColumn": 19, + "startColumn": 33, + "endColumn": 42, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 28, - "endColumn": 32, + "startColumn": 12, + "endColumn": 29, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 12, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 29, - "endColumn": 37, + "startColumn": 12, + "endColumn": 32, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/documentation/validation.py": [ { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 23, - "endColumn": 31, + "startColumn": 33, + "endColumn": 46, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportArgumentType", "range": { - "startColumn": 132, - "endColumn": 140, + "startColumn": 47, + "endColumn": 60, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 55, - "endColumn": 63, + "startColumn": 46, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportOperatorIssue", "range": { - "startColumn": 41, - "endColumn": 49, + "startColumn": 15, + "endColumn": 45, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/interuss/geospatial_map/geospatial_feature_comprehension.py": [ + }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 40, - "endColumn": 45, + "startColumn": 46, + "endColumn": 59, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportOptionalIterable", "range": { - "startColumn": 46, - "endColumn": 51, + "startColumn": 35, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { - "startColumn": 39, - "endColumn": 44, + "startColumn": 64, + "endColumn": 77, "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/interuss/mock_uss/configure_locality.py": [ + "./monitoring/uss_qualifier/scenarios/flight_planning/prioritization_test_steps.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 69, - "endColumn": 77, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 69, - "endColumn": 77, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 69, - "endColumn": 82, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 69, - "endColumn": 77, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py": [ + }, { "code": "reportInvalidTypeVarUse", "range": { @@ -22712,432 +18438,468 @@ } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 31, - "endColumn": 43, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportPossiblyUnboundVariable", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 61, - "endColumn": 66, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 15, - "endColumn": 9, - "lineCount": 3 + "startColumn": 14, + "endColumn": 30, + "lineCount": 1 } } ], - "./monitoring/uss_qualifier/scenarios/interuss/mock_uss/unconfigure_locality.py": [ + "./monitoring/uss_qualifier/scenarios/interuss/flight_authorization/general_flight_authorization.py": [ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 69, - "endColumn": 77, + "startColumn": 51, + "endColumn": 59, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.py": [ + }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 42, - "endColumn": 49, + "startColumn": 25, + "endColumn": 29, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 15, - "endColumn": 18, + "startColumn": 23, + "endColumn": 27, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 15, - "endColumn": 18, + "startColumn": 76, + "endColumn": 80, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 39, - "endColumn": 42, + "startColumn": 120, + "endColumn": 124, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 55, + "endColumn": 63, "lineCount": 1 } }, { "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 113, - "endColumn": 114, + "startColumn": 73, + "endColumn": 77, "lineCount": 1 } - } - ], - "./monitoring/uss_qualifier/scenarios/scenario.py": [ + }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 11, - "endColumn": 47, + "startColumn": 23, + "endColumn": 27, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 34, - "endColumn": 39, + "startColumn": 68, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 11, - "endColumn": 45, + "startColumn": 153, + "endColumn": 157, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 40, - "endColumn": 45, + "startColumn": 55, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 18, - "endColumn": 66, + "startColumn": 73, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 61, - "endColumn": 66, + "startColumn": 23, + "endColumn": 27, "lineCount": 1 } }, { - "code": "reportOptionalSubscript", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 8, - "endColumn": 35, + "startColumn": 76, + "endColumn": 80, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, - "endColumn": 35, + "startColumn": 120, + "endColumn": 124, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 58, + "startColumn": 55, "endColumn": 63, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, - "endColumn": 35, + "startColumn": 73, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 65, - "endColumn": 70, + "startColumn": 23, + "endColumn": 27, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 127, - "endColumn": 131, + "startColumn": 68, + "endColumn": 72, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 65, - "endColumn": 70, + "startColumn": 153, + "endColumn": 157, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 140, - "endColumn": 144, + "startColumn": 55, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 26, - "endColumn": 31, + "startColumn": 73, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 11, - "endColumn": 45, + "startColumn": 16, + "endColumn": 20, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, - "endColumn": 37, + "startColumn": 20, + "endColumn": 24, "lineCount": 1 } }, { - "code": "reportOptionalIterable", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, - "endColumn": 55, + "startColumn": 28, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 48, - "endColumn": 55, + "startColumn": 163, + "endColumn": 167, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 26, - "endColumn": 33, + "startColumn": 59, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 34, - "endColumn": 40, + "startColumn": 45, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 66, - "endColumn": 72, + "startColumn": 28, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportArgumentType", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 24, - "endColumn": 41, + "startColumn": 180, + "endColumn": 184, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 35, - "endColumn": 44, + "startColumn": 59, + "endColumn": 67, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 26, - "endColumn": 34, + "startColumn": 45, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportPossiblyUnboundVariable", "range": { "startColumn": 15, - "endColumn": 21, + "endColumn": 19, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 26, - "endColumn": 34, + "startColumn": 28, + "endColumn": 32, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 30, - "endColumn": 38, + "startColumn": 55, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, + "startColumn": 29, "endColumn": 37, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 26, - "endColumn": 34, + "startColumn": 23, + "endColumn": 31, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, - "endColumn": 38, + "startColumn": 132, + "endColumn": 140, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 30, - "endColumn": 45, + "startColumn": 55, + "endColumn": 63, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 30, - "endColumn": 40, + "startColumn": 41, + "endColumn": 49, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/interuss/mock_uss/configure_locality.py": [ { - "code": "reportOperatorIssue", + "code": "reportOptionalMemberAccess", "range": { - "startColumn": 11, - "endColumn": 57, + "startColumn": 69, + "endColumn": 77, "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 30, - "endColumn": 40, + "startColumn": 69, + "endColumn": 77, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportArgumentType", "range": { - "startColumn": 43, - "endColumn": 9, - "lineCount": 3 + "startColumn": 69, + "endColumn": 82, + "lineCount": 1 } }, { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 49, - "endColumn": 54, + "startColumn": 69, + "endColumn": 77, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportInvalidTypeVarUse", "range": { - "startColumn": 46, - "endColumn": 56, + "startColumn": 14, + "endColumn": 30, "lineCount": 1 } }, { - "code": "reportOperatorIssue", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 11, - "endColumn": 45, + "startColumn": 31, + "endColumn": 43, "lineCount": 1 } }, { - "code": "reportOptionalMemberAccess", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 54, - "endColumn": 61, + "startColumn": 61, + "endColumn": 66, "lineCount": 1 } }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 9, + "lineCount": 3 + } + } + ], + "./monitoring/uss_qualifier/scenarios/interuss/mock_uss/unconfigure_locality.py": [ { "code": "reportOptionalMemberAccess", "range": { - "startColumn": 62, - "endColumn": 75, + "startColumn": 69, + "endColumn": 77, "lineCount": 1 } - }, + } + ], + "./monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.py": [ { - "code": "reportOptionalMemberAccess", + "code": "reportArgumentType", "range": { "startColumn": 42, - "endColumn": 52, + "endColumn": 49, "lineCount": 1 } }, { - "code": "reportReturnType", + "code": "reportPossiblyUnboundVariable", "range": { "startColumn": 15, - "endColumn": 36, + "endColumn": 18, "lineCount": 1 } }, { - "code": "reportInvalidTypeVarUse", + "code": "reportPossiblyUnboundVariable", "range": { - "startColumn": 10, - "endColumn": 26, + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportPossiblyUnboundVariable", + "range": { + "startColumn": 39, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportPossiblyUnboundVariable", + "range": { + "startColumn": 113, + "endColumn": 114, "lineCount": 1 } } @@ -23227,14 +18989,6 @@ "lineCount": 1 } }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 13, - "lineCount": 1 - } - }, { "code": "reportAttributeAccessIssue", "range": { @@ -23342,16 +19096,6 @@ } } ], - "./monitoring/uss_qualifier/test_data/make_flight_intent_kml.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 17, - "endColumn": 22, - "lineCount": 1 - } - } - ], "./schemas/manage_type_schemas.py": [ { "code": "reportArgumentType", diff --git a/.dockerignore b/.dockerignore index 03c694bf8c..ac33113210 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,7 @@ build/workspace *.pyc .git .venv +monitoring/uss_qualifier/output/* +monitoring/uss_qualifier/htmlcov/* +monitoring/mock_uss/output/* +**/__pycache__ diff --git a/.github/actions/load-image/action.yml b/.github/actions/load-image/action.yml new file mode 100644 index 0000000000..fae7af93ec --- /dev/null +++ b/.github/actions/load-image/action.yml @@ -0,0 +1,19 @@ +name: 'Load monitoring image' +description: 'Load single monitoring image built by the build-monitoring-image job in the ci.yml workflow (composite action)' +runs: + using: "composite" + steps: + - name: Download monitoring image + uses: actions/download-artifact@v5 + with: + name: monitoring-docker-image + path: monitoring-image + - name: Load monitoring image + shell: bash + run: | + set -euo pipefail + docker load --input monitoring-image/monitoring-image.tar + cp monitoring-image/monitoring/image monitoring/image + cp monitoring-image/monitoring/image-dev monitoring/image-dev + touch monitoring/image monitoring/image-dev + rm -rf monitoring-image diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f00a4378b..2999daa0fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,50 @@ on: branches: - main jobs: + build-monitoring-images: + name: Build monitoring images + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Job information + run: | + echo "Job information" + echo "Trigger: ${{ github.event_name }}" + echo "Host: ${{ runner.os }}" + echo "Repository: ${{ github.repository }}" + echo "Branch: ${{ github.ref }}" + docker images + docker version + docker compose version + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: true + fetch-tags: true + fetch-depth: 0 + filter: tree:0 + - name: Build monitoring images + run: | + make image + make image-dev + - name: Save monitoring image artifact + run: | + docker save interuss/monitoring interuss/monitoring-dev -o monitoring-image.tar + mkdir -p image-artifact/monitoring + mv monitoring-image.tar image-artifact/ + cp monitoring/image image-artifact/monitoring/ + cp monitoring/image-dev image-artifact/monitoring/ + - name: Upload monitoring image artifact + uses: actions/upload-artifact@v7 + with: + name: monitoring-docker-image + path: image-artifact + hygiene-tests: name: Repository hygiene + needs: + - build-monitoring-images # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#permissions permissions: contents: read @@ -23,16 +65,45 @@ jobs: docker version docker compose version - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true + - name: Load monitoring image + uses: ./.github/actions/load-image - name: Automated hygiene verification run: make check-hygiene - - name: Unit tests - run: make unit-test + + unit-tests: + name: Unit tests + needs: + - build-monitoring-images + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Job information + run: | + echo "Job information" + echo "Trigger: ${{ github.event_name }}" + echo "Host: ${{ runner.os }}" + echo "Repository: ${{ github.repository }}" + echo "Branch: ${{ github.ref }}" + docker images + docker version + docker compose version + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: true + - name: Load monitoring image + uses: ./.github/actions/load-image + - name: Unit tests + run: make unit-test mock_uss-test: name: mock_uss tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -44,6 +115,8 @@ jobs: uss_qualifier-noop-test: name: uss_qualifier configurations.dev.noop tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -57,6 +130,8 @@ jobs: uss_qualifier-geoawareness_cis-test: name: uss_qualifier configurations.dev.geoawareness_cis tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -70,6 +145,8 @@ jobs: uss_qualifier-generate_rid_test_data-test: name: uss_qualifier configurations.dev.generate_rid_test_data tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -83,6 +160,8 @@ jobs: uss_qualifier-geospatial_comprehension-test: name: uss_qualifier configurations.dev.geospatial_comprehension tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -96,6 +175,8 @@ jobs: uss_qualifier-general_flight_auth-test: name: uss_qualifier configurations.dev.general_flight_auth tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -109,6 +190,8 @@ jobs: uss_qualifier-message_signing-test: name: uss_qualifier configurations.dev.message_signing tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -122,6 +205,8 @@ jobs: uss_qualifier-dss_probing-test: name: uss_qualifier configurations.dev.dss_probing tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -135,6 +220,8 @@ jobs: uss_qualifier-f3548_self_contained-test: name: uss_qualifier configurations.dev.f3548_self_contained tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -148,6 +235,8 @@ jobs: uss_qualifier-utm_implementation_us-test: name: uss_qualifier configurations.dev.utm_implementation_us tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -161,6 +250,8 @@ jobs: uss_qualifier-netrid_v22a-test: name: uss_qualifier configurations.dev.netrid_v22a tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -174,6 +265,8 @@ jobs: uss_qualifier-netrid_v19-test: name: uss_qualifier configurations.dev.netrid_v19 tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -187,6 +280,8 @@ jobs: uss_qualifier-uspace-test: name: uss_qualifier configurations.dev.uspace tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -198,21 +293,50 @@ jobs: cd monitoring/uss_qualifier make test - uss_qualifier-minimal_probing-test: - name: uss_qualifier configurations.dev.minimal_probing tests + uss_qualifier-minimal_probing_simulations-crdb-test: + name: uss_qualifier configurations.dev.minimal_probing_simulations tests with crdb + needs: + - build-monitoring-images + permissions: + contents: read + uses: ./.github/workflows/monitoring-test.yml + with: + name: uss_qualifier-minimal_probing_simulations-crdb-test + script: | + export CONFIG_NAME="configurations.dev.minimal_probing" + export NUM_USS=2 + export NUM_NODES=2 + export DB_TYPE=crdb + export INTRA_USS_NETEM_CONF="delay 250us 25us 25% distribution normal loss 0.0025% 10%" + export INTER_USS_NETEM_CONF="delay 25ms 7.5ms 50% distribution paretonormal loss 0.025% 25%" + + cd monitoring/uss_qualifier + make test + + uss_qualifier-minimal_probing_simulations-ybdb-test: + name: uss_qualifier configurations.dev.minimal_probing_simulations tests with ybdb + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml with: - name: uss_qualifier-minimal_probing-test + name: uss_qualifier-minimal_probing_simulations-ybdb-test script: | export CONFIG_NAME="configurations.dev.minimal_probing" + export NUM_USS=2 + export NUM_NODES=2 + export DB_TYPE=ybdb + export INTRA_USS_NETEM_CONF="delay 250us 25us 25% distribution normal loss 0.0025% 10%" + export INTER_USS_NETEM_CONF="delay 25ms 7.5ms 50% distribution paretonormal loss 0.025% 25%" cd monitoring/uss_qualifier make test prober-test: name: prober tests + needs: + - build-monitoring-images permissions: contents: read uses: ./.github/workflows/monitoring-test.yml @@ -248,13 +372,13 @@ jobs: group: ${{ github.workflow }}-${{ github.ref }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true path: monitoring - name: Get uss_qualifier reports - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: path: ./artifacts @@ -262,7 +386,7 @@ jobs: run: ./monitoring/github_pages/make_site_content.sh - name: Deploy - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 if: github.ref == 'refs/heads/main' with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dev-checks.yml b/.github/workflows/dev-checks.yml index 11ab8aaaad..8fd34671c4 100644 --- a/.github/workflows/dev-checks.yml +++ b/.github/workflows/dev-checks.yml @@ -11,20 +11,13 @@ jobs: name: Clone on Windows runs-on: windows-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Checkout on Windows run: echo "Project successfully cloned on ${{ runner.os }}. See `Set up Job` stage for more details about the Runner." macos: name: Clone on Mac runs-on: macos-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Checkout on Mac run: echo "Project successfully cloned on ${{ runner.os }}. See `Set up Job` stage for more details about the Runner." - ubuntu: - name: Clone on Ubuntu - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Checkout on Ubuntu - run: echo "Project successfully cloned on ${{ runner.os }}. See `Set up Job` stage for more details about the Runner." diff --git a/.github/workflows/image-publish.yml b/.github/workflows/image-publish.yml index fb93f48683..9a6215ae67 100644 --- a/.github/workflows/image-publish.yml +++ b/.github/workflows/image-publish.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Install Cosign - uses: sigstore/cosign-installer@v3.10.0 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: Job information run: | echo "Job information" @@ -39,13 +39,13 @@ jobs: cosign version - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true fetch-depth: 0 - name: Log in to Docker Hub - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: docker.io username: ${{ secrets.DOCKER_USERNAME }} diff --git a/.github/workflows/monitoring-test.yml b/.github/workflows/monitoring-test.yml index c892da5fde..3920acf3f5 100644 --- a/.github/workflows/monitoring-test.yml +++ b/.github/workflows/monitoring-test.yml @@ -30,14 +30,16 @@ jobs: docker version docker compose version - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true + - name: Load monitoring image + uses: ./.github/actions/load-image - name: Run ${{ inputs.name }} test run: ${{ inputs.script }} - name: Save containers and tracer logs as artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: monitoring-test-${{ inputs.name }}-logs path: | @@ -45,7 +47,7 @@ jobs: monitoring/mock_uss/output - name: Save USS qualifier reports as artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: monitoring-test-${{ inputs.name }}-reports path: | diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 1818ac3f73..4f755d275e 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -11,6 +11,8 @@ on: jobs: noResponse: + permissions: + issues: write runs-on: ubuntu-latest steps: - uses: lee-dohm/no-response@9bb0a4b5e6a45046f00353d5de7d90fb8bd773bb diff --git a/.gitmodules b/.gitmodules index c01a66aba4..a52edf3c1e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "interfaces/automated_testing"] path = interfaces/automated_testing url = https://github.com/interuss/automated_testing_interfaces +[submodule "schemas/ed318"] + path = schemas/ed318 + url = git@github.com:UASGeoZones/ED-318.git diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..6189260e59 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# AI Agent Collaboration Guide + +This document contains key context, nuances, and troubleshooting tips specifically designed to help AI agents (and human developers) quickly understand and contribute to this repository, avoiding common pitfalls. + +## 1. Project Dependencies and Environment +- **Environment**: This project primarily uses [uv](https://docs.astral.sh/uv/) for dependency management and locking. +- **External Schemas**: Many UTM standards implementations (such as the ASTM F3548 schemas) are provided by the external `uas_standards` package instead of living directly within the local `./monitoring` or `./schemas` directories. +- **Agent Tip**: Since external packages reside in the virtual environment, you cannot explore them directly with standard local file `grep` searches. To inspect an external schema's properties (e.g., `OperationalIntentReference`), it is much faster to run a Python introspection script through `uv`: + ```bash + uv run python -c "from uas_standards.astm.f3548.v21.api import OperationalIntentReference; print(OperationalIntentReference.__annotations__)" + ``` + +## 2. Navigating Data Schemas +- **Implicit Types**: Many schema objects inherit from `ImplicitDict`. This means that reading their raw Python class definitions may not reveal all their expected structure. Rely on their `__annotations__` or their OpenAPI documentation. +- **Hidden Schema Nuances**: Certain F3548 properties intuitively behave differently than standard logic might predict (e.g., F3548's `OperationalIntentReference` does *not* represent 4D `altitude` extents, whereas `Volume4D` details do). Always verify property existence programmatically instead of relying on assumptions. +- **Datetime Types**: Fields annotated as `StringBasedDateTime` (or generic `Time` structures wrapping it) inherently expose a `.datetime` property mapping directly to Python's built-in `datetime.datetime` type. Always rely on this `.datetime` attribute for date-math instead of manually parsing string strings. + +## 3. `uss_qualifier` Test Development Rules +- **Markdown Documentation is Mandatory**: Every check (e.g., `self._scenario.check(...)`) added to a Python test scenario must be meticulously documented in the corresponding Markdown documentation file for that specific test scenario or fragment. +- **Documentation Traceability**: All documented test checks must trace back to exactly one or more requirements using a specific bold format, and feature a severity emoji prefix (e.g., `## 🛑 Correct operational intent details check`). You must refer to `monitoring/uss_qualifier/scenarios/README.md` for specific markup details before modifying test steps. + +## 4. Local Testing constraints +- **Docker Dependency**: Mock USS and DSS environments require active Docker containers. Standard testing commands are typically structured via bash scripts like `./monitoring/uss_qualifier/run_locally.sh `. If container-building fails due to `Authentication` or package registry issues in the agent's environment, gracefully halt and ask the human user to run the script instead. + +## 5. Continuous Improvement of this Guide +- **Pay It Forward**: As an AI agent, your ability to acquire context quickly is critical. If, during your work, you find yourself spending significant time overcoming a misunderstanding, discovering a hidden project nuance, or writing introspection scripts to understand a schema, **please proactively update this `AGENTS.md` file**. Add concise tips or warnings to help future agents (including yourself) avoid the same friction, alongside completing your core task. +- **Correcting Mistakes**: Documentation can become outdated or contain errors over time. If your core work reveals that information in this `AGENTS.md` file is mistaken or missing critical context, please take the initiative to fix those errors while completing your primary objectives! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d385be492..201a785ef2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,6 +52,8 @@ If the test scenario is long, note that the documentation does not need to be cr Once all necessary prerequisites (e.g., resources) are available and the test scenario documentation is complete, actually writing the code of the test scenario should be fairly straightforward. Before creating a PR, the test scenario code must at least have been successfully tested by the developer. Ideally, a test configuration included in the CI (see list of configurations tested in [uss_qualifier's run_locally.sh](monitoring/uss_qualifier/run_locally.sh)) should run the test scenario. If the test scenario is not run as part of the CI, the PR author must clearly indicate why they are sure the test scenario has been implemented correctly. +See more information about test scenarios and their implementation in [test scenario documentation](./monitoring/uss_qualifier/scenarios/README.md). + #### Fake URLs In some tests, fake URLs must be provided (for instance, the base URL when creating an operational intent reference in a DSS instance when the details for that operational intent reference will not actually be served by any USS). In all cases when such fake URLs are provided, they should use the value obtained (or would be obtained) from `make_fake_url` in [monitorlib/testing.py](./monitoring/monitorlib/testing.py). If the fake URL is specified in a data file rather than a Python file, it should follow the format of `https://testdummy.interuss.org/interuss` followed by the path of the file (minus extension) relative to the repository root; e.g., `https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/f3548_self_contained` if the URL was defined in [uss_qualifier/configurations/dev/f3548_self_contained.yaml](./monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml). One or more suffix paths can be appended if multiple different fake URLs are needed in the same originating file; e.g., `https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/f3548_self_contained/planning_area/original` diff --git a/Makefile b/Makefile index 5c83fc7e28..6abbfde413 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ else endif .PHONY: format -format: image - docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring uv run ruff format - docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring uv run ruff check --fix - docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring uv run basedpyright +format: image-dev + docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring-dev uv run ruff format + docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring-dev uv run ruff check --fix + docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring-dev uv run basedpyright cd monitoring && make format cd schemas && make format @@ -23,16 +23,16 @@ lint: shell-lint python-lint cd schemas && make lint .PHONY: check-hygiene -check-hygiene: image lint validate-uss-qualifier-docs +check-hygiene: image-dev lint validate-uss-qualifier-docs test/repo_hygiene/repo_hygiene.sh .PHONY: python-lint -python-lint: image +python-lint: image-dev - docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring uv run ruff format --check || (echo "Linter didn't succeed. You can use the following command to fix python linter issues: make format" && exit 1) - docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring uv run ruff check || (echo "Linter didn't succeed. You can use the following command to fix python linter issues: make format" && exit 1) + docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring-dev uv run ruff format --check || (echo "Linter didn't succeed. You can use the following command to fix python linter issues: make format" && exit 1) + docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring-dev uv run ruff check || (echo "Linter didn't succeed. You can use the following command to fix python linter issues: make format" && exit 1) shasum -b -a 256 .basedpyright/baseline.json > /tmp/baseline-before.hash - docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring uv run basedpyright || (echo "Typing check didn't succeed. Please fix issue and run make format to validate changes." && exit 1) + docker run --rm -u ${USER_GROUP} -v "$(CURDIR):/app" -w /app interuss/monitoring-dev uv run basedpyright || (echo "Typing check didn't succeed. Please fix issue and run make format to validate changes." && exit 1) shasum -b -a 256 .basedpyright/baseline.json > /tmp/baseline-after.hash diff /tmp/baseline-before.hash /tmp/baseline-after.hash || (echo "Basedpyright baseline changed, probably dues to issues that have been cleanup. Use the following command to update baseline: make format" && exit 1) @@ -42,7 +42,7 @@ validate-uss-qualifier-docs: .PHONY: shell-lint shell-lint: - find . -name '*.sh' ! -path "./interfaces/*" | xargs docker run --rm -v "$(CURDIR):/monitoring" -w /monitoring koalaman/shellcheck + find . -name '*.sh' ! -path "./interfaces/*" | git check-ignore --stdin --no-index -n -v --non-matching | grep '^::' | cut -f2 | xargs docker run --rm -v "$(CURDIR):/monitoring" -w /monitoring koalaman/shellcheck:v0.11.0 .PHONY: unit-test unit-test: @@ -52,12 +52,16 @@ unit-test: image: cd monitoring && make image +.PHONY: image-dev +image-dev: + cd monitoring && make image-dev + tag: scripts/tag.sh $(UPSTREAM_OWNER)/monitoring/v$(VERSION) .PHONY: start-locally start-locally: - build/dev/run_locally.sh up -d + build/dev/run_locally.sh up --wait .PHONY: probe-locally probe-locally: diff --git a/NEXT_RELEASE_NOTES.md b/NEXT_RELEASE_NOTES.md index a1055d9487..82826a2179 100644 --- a/NEXT_RELEASE_NOTES.md +++ b/NEXT_RELEASE_NOTES.md @@ -33,7 +33,7 @@ The release notes should contain at least the following sections: -------------------------------------------------------------------------------------------------------------------- -# Release Notes for v0.19.1 +# Release Notes for v0.31.0 ## Mandatory migration tasks diff --git a/assets/generated/local_uss_qualifier.png b/assets/generated/local_uss_qualifier.png index 983ee57c65..e49b6c9cf2 100644 Binary files a/assets/generated/local_uss_qualifier.png and b/assets/generated/local_uss_qualifier.png differ diff --git a/assets/generated/run_locally_architecture.png b/assets/generated/run_locally_architecture.png index d56208083a..8634e91811 100644 Binary files a/assets/generated/run_locally_architecture.png and b/assets/generated/run_locally_architecture.png differ diff --git a/assets/local_uss_qualifier.gv b/assets/local_uss_qualifier.gv index 653ca65c95..eb2b8be7b4 100644 --- a/assets/local_uss_qualifier.gv +++ b/assets/local_uss_qualifier.gv @@ -19,11 +19,11 @@ digraph G { subgraph cluster_local_infra { label=make start-locally
make down-locally> style="dashed" - Auth [label=local_infra-oauth-1
port 8085>] + Auth [label=local_infra_1-1-oauth-1
port 8085>] subgraph cluster_dss { label="DSS" - CoreService [label=local_infra-dss-1
port 8082>] - CRDB [label=local_infra-crdb-1
web UI on port 8080>] + CoreService [label=local_infra_1-1-dss-1
port 8001>] + CRDB [label=local_infra_1-1-crdb-1
web UI on port 8101>] CoreService -> CRDB } Auth -> CoreService [label="Public key",style="dashed",color="gray",fontcolor="gray"] diff --git a/assets/run_locally_architecture.gv b/assets/run_locally_architecture.gv index 6de9183668..5af805eed4 100644 --- a/assets/run_locally_architecture.gv +++ b/assets/run_locally_architecture.gv @@ -12,9 +12,9 @@ digraph G { PublicKey -> PrivateKey [style=dashed,dir=back]; subgraph cluster_0 { label="InterUSS DSS instance"; - CoreService -> CRDB [label="port 26257"]; + CoreService -> CRDB [label="port 26201"]; } - USS -> CoreService [label="http://localhost:8082 "]; + USS -> CoreService [label="http://localhost:8001 "]; USS -> OAuth [label="http://localhost:8085/token"]; PublicKey -> CoreService [dir=back,style=dotted]; USS -> PublicKey [style=invis]; //To help with formatting diff --git a/build/build_and_push.sh b/build/build_and_push.sh index 97dff81bd0..9a40f2b988 100755 --- a/build/build_and_push.sh +++ b/build/build_and_push.sh @@ -52,8 +52,9 @@ else echo "Signing docker image ${TAG} (digest: ${DIGEST})..." cosign sign --yes "${DIGEST}" - echo "Verifying signature of docker image ${TAG} (digest: ${DIGEST})..." - cosign verify "${DIGEST}" --certificate-identity="${CERT_IDENTITY}" --certificate-oidc-issuer="${CERT_ISSUER}" + echo "Verifying signature of docker image ${TAG} (digest: ${DIGEST}) after a 30 seconds wait..." + sleep 30 # the signature may not be returned immediately after being published, so as a mitigation we wait for 30 seconds before verifying + cosign verify --certificate-identity="${CERT_IDENTITY}" --certificate-oidc-issuer="${CERT_ISSUER}" "${DIGEST}" echo "Signed and verified signature of docker image ${TAG} (digest: ${DIGEST})..." diff --git a/build/dev/docker-compose.yaml b/build/dev/docker-compose.yaml index 89bb1cd7c5..14e0ecb36f 100644 --- a/build/dev/docker-compose.yaml +++ b/build/dev/docker-compose.yaml @@ -1,83 +1,201 @@ -# Brings up a local interoperability ecosystem consisting of a DSS instance and dummy OAuth server. - -# To bring up this system, run ./run_locally.sh up -d and wait for all containers to succeed or become healthy. +# This Docker Compose file must be used through `./run_locally.sh`. services: crdb: - hostname: crdb.uss1.localutm image: cockroachdb/cockroach:v24.1.3 - command: start-single-node --insecure - expose: - - 26257 - ports: - - "8080:8080" - - "26257:26257" + entrypoint: /db-entrypoint.sh + command: /cockroach/cockroach.sh start --insecure --join=db1.uss1.localutm + volumes: + - $PWD/startup/db-entrypoint.sh:/db-entrypoint.sh:ro + profiles: [crdb] restart: always + cap_add: [NET_ADMIN] + hostname: db${USS_NODE_IDX:?}.uss${USS_IDX:?}.localutm networks: - - dss_internal_network + dss_internal_network: + ipv4_address: 172.27.${USS_IDX:?}.${USS_NODE_IDX:?} + ports: + - "81${PADDED_NODE_IDX:?}:8080" + - "262${PADDED_NODE_IDX:?}:26257" + environment: + - INTRA_USS_SUBNET=172.27.${USS_IDX:?}.0/24 + - INTER_USS_SUBNET=172.27.0.0/16 + - INTRA_USS_NETEM_CONF=${INTRA_USS_NETEM_CONF-} + - INTER_USS_NETEM_CONF=${INTER_USS_NETEM_CONF-} healthcheck: test: curl -f 'http://localhost:8080/health?ready=1' || exit 1 interval: 3m start_period: 30s start_interval: 5s + ybdb: + image: interuss/yugabyte:2025.1.2.1-interuss + entrypoint: /db-entrypoint.sh + # ysql_output_buffer_size needs to be increased to allow ysql to retry read restart errors. https://docs.yugabyte.com/preview/reference/configuration/yb-tserver/#ysql-output-buffer-size + command: sh -c "bin/yugabyted start --background=false --tserver_flags=ysql_output_buffer_size=1048576 --advertise_address=$$YBDB_HOST --join=$$([ "$$YBDB_HOST" = db1.uss1.localutm ] || echo db1.uss1.localutm)" + volumes: + - $PWD/startup/db-entrypoint.sh:/db-entrypoint.sh:ro + profiles: [ybdb] + restart: always + cap_add: [ NET_ADMIN ] + hostname: db${USS_NODE_IDX:?}.uss${USS_IDX:?}.localutm + networks: + dss_internal_network: + ipv4_address: 172.27.${USS_IDX:?}.${USS_NODE_IDX:?} + ports: + - "70${PADDED_NODE_IDX:?}:7000" + - "90${PADDED_NODE_IDX:?}:9000" + - "154${PADDED_NODE_IDX:?}:15433" + - "54${PADDED_NODE_IDX:?}:5433" + - "91${PADDED_NODE_IDX:?}:9042" + environment: + - YBDB_HOST=db${USS_NODE_IDX:?}.uss${USS_IDX:?}.localutm + - INTRA_USS_SUBNET=172.27.${USS_IDX:?}.0/24 + - INTER_USS_SUBNET=172.27.0.0/16 + - INTRA_USS_NETEM_CONF=${INTRA_USS_NETEM_CONF-} + - INTER_USS_NETEM_CONF=${INTER_USS_NETEM_CONF-} + healthcheck: + test: /home/yugabyte/postgres/bin/pg_isready -h $$YBDB_HOST || exit 1 + interval: 5s + timeout: 5s + retries: 10 + + crdb-init: + image: cockroachdb/cockroach:v24.1.3 + profiles: [bootstrap-crdb] + depends_on: [crdb] + entrypoint: > + sh -c " + out=$$(/cockroach/cockroach.sh init --insecure --host=db1.uss1.localutm 2>&1); + if [ $$? -eq 0 ]; then + echo "$$out"; + exit 0; + fi; + echo "$$out"; + if echo \"$$out\" | grep -q \"already been initialized\"; then + exit 0; + fi; + exit 1; + " + networks: [dss_internal_network] + rid_bootstrapper: - image: interuss/dss:v0.19.0-rc2 - command: /usr/bin/db-manager migrate --schemas_dir=/db-schemas/rid --db_version "latest" --cockroach_host crdb + image: interuss/dss:v0.22.0 + profiles: [bootstrap-crdb] + entrypoint: sh -c "/usr/bin/db-manager migrate --schemas_dir=/db-schemas/rid --db_version latest --cockroach_host db1.uss1.localutm" depends_on: - crdb: - condition: service_healthy - networks: - - dss_internal_network + crdb-init: + condition: service_completed_successfully + networks: [dss_internal_network] + healthcheck: + disable: true scd_bootstrapper: - image: interuss/dss:v0.19.0-rc2 - command: /usr/bin/db-manager migrate --schemas_dir=/db-schemas/scd --db_version "latest" --cockroach_host crdb + image: interuss/dss:v0.22.0 + profiles: [bootstrap-crdb] + entrypoint: sh -c "/usr/bin/db-manager migrate --schemas_dir=/db-schemas/scd --db_version latest --cockroach_host db1.uss1.localutm" depends_on: - crdb: - condition: service_healthy - networks: - - dss_internal_network + crdb-init: + condition: service_completed_successfully + networks: [dss_internal_network] + healthcheck: + disable: true + + aux_bootstrapper: + image: interuss/dss:v0.22.0 + profiles: [bootstrap-crdb] + entrypoint: sh -c "/usr/bin/db-manager migrate --schemas_dir=/db-schemas/aux_ --db_version latest --cockroach_host db1.uss1.localutm" + depends_on: + crdb-init: + condition: service_completed_successfully + networks: [dss_internal_network] + healthcheck: + disable: true + + rid_bootstrapper-ybdb: + image: interuss/dss:v0.22.0 + profiles: [bootstrap-ybdb] + entrypoint: sh -c "/usr/bin/db-manager migrate --schemas_dir=/db-schemas/yugabyte/rid --db_version latest --datastore_host db1.uss1.localutm --datastore_user yugabyte --datastore_port 5433" + restart: on-failure + networks: [dss_internal_network] + healthcheck: + disable: true + + scd_bootstrapper-ybdb: + image: interuss/dss:v0.22.0 + profiles: [bootstrap-ybdb] + entrypoint: sh -c "/usr/bin/db-manager migrate --schemas_dir=/db-schemas/yugabyte/scd --db_version latest --datastore_host db1.uss1.localutm --datastore_user yugabyte --datastore_port 5433" + restart: on-failure + networks: [dss_internal_network] + healthcheck: + disable: true + + aux_bootstrapper-ybdb: + image: interuss/dss:v0.22.0 + profiles: [bootstrap-ybdb] + entrypoint: sh -c "/usr/bin/db-manager migrate --schemas_dir=/db-schemas/yugabyte/aux_ --db_version latest --datastore_host db1.uss1.localutm --datastore_user yugabyte --datastore_port 5433" + restart: on-failure + networks: [dss_internal_network] + healthcheck: + disable: true dss: - hostname: dss.uss1.localutm - image: interuss/dss:v0.19.0-rc2 + image: interuss/dss:v0.22.0 + command: /startup/core_service.sh ${DEBUG_ON:-0} + profiles: [ crdb, ybdb ] + restart: always volumes: - $PWD/../test-certs:/var/test-certs:ro - $PWD/startup/core_service.sh:/startup/core_service.sh:ro - command: /startup/core_service.sh ${DEBUG_ON:-0} - expose: - - 8082 - ports: - - "4000:4000" - - "8082:80" depends_on: rid_bootstrapper: condition: service_completed_successfully + required: false scd_bootstrapper: condition: service_completed_successfully + required: false + aux_bootstrapper: + condition: service_completed_successfully + required: false + rid_bootstrapper-ybdb: + condition: service_completed_successfully + required: false + scd_bootstrapper-ybdb: + condition: service_completed_successfully + required: false + aux_bootstrapper-ybdb: + condition: service_completed_successfully + required: false + hostname: dss${USS_NODE_IDX:?}.uss${USS_IDX:?}.localutm networks: - dss_internal_network: {} - interop_ecosystem_network: - aliases: - - dss.uss2.localutm + - dss_internal_network + - interop_ecosystem_network + ports: + - "40${PADDED_NODE_IDX:?}:4000" + - "80${PADDED_NODE_IDX:?}:80" + environment: + COMPOSE_PROFILES: ${COMPOSE_PROFILES} + JWT_AUDIENCES: dss${USS_NODE_IDX:?}.uss${USS_IDX:?}.localutm + DATASTORE_HOST: db${USS_NODE_IDX:?}.uss${USS_IDX:?}.localutm healthcheck: test: wget -O - 'http://localhost/healthy' || exit 1 - interval: 3m - start_period: 30s + start_period: 120s # yugabyte may be slow to be ready, and the dss service needs the ybdb service live in order to be healthy start_interval: 5s oauth: - hostname: oauth.authority.localutm image: interuss/dummy-oauth command: -private_key_file /var/test-certs/auth2.key + volumes: + - $PWD/../test-certs:/var/test-certs:ro + profiles: [oauth] + hostname: oauth.authority.localutm + networks: + - interop_ecosystem_network expose: - 8085 ports: - "8085:8085" - networks: - - interop_ecosystem_network healthcheck: test: wget -O - 'http://localhost:8085/token?intended_audience=-&scope=-' || exit 1 interval: 3m @@ -87,8 +205,10 @@ services: networks: dss_internal_network: name: dss_internal_network + external: true interop_ecosystem_network: name: interop_ecosystem_network + external: true volumes: dss_component_coordination: diff --git a/build/dev/run_locally.sh b/build/dev/run_locally.sh index a4c842a174..0cc489af0b 100755 --- a/build/dev/run_locally.sh +++ b/build/dev/run_locally.sh @@ -2,8 +2,20 @@ set -eo pipefail -# This script will deploy an interoperability ecosystem consisting of a standalone DSS instance and dummy OAuth server -# (both accessible on the interop_ecosystem_network) with docker compose using the DSS image from Docker Hub. +# This script will deploy an interoperability ecosystem consisting of a chosen number of DSS instances and a dummy OAuth +# server (all accessible on the interop_ecosystem_network) with docker compose using the DSS image from Docker Hub. +# Run `./run_locally.sh up -d` to start two DSS instances using CockroachDB. +# +# The following environment variables may be used to simulate different conditions: +# - NUM_USS: number of USSs (default: 2); note that the standard local mock ecosystem requires at least 2 USSs +# - NUM_NODES: number of nodes per USS (default: 1) +# - DB_TYPE: crdb or ybdb (default: crdb) +# - INTRA_USS_NETEM_CONF: tc netem configuration to apply to traffic between DB nodes of an USS (default: ) +# sensible value (low latency/jitter, very low loss (e.g., within same availability DC)): +# "delay 250us 25us 25% distribution normal loss 0.0025% 10%" +# - INTER_USS_NETEM_CONF: tc netem configuration to apply to traffic between DB nodes of different USS (default: ) +# sensible value (higher latency/jitter, moderate loss (e.g., cross-country)): +# "delay 25ms 7.5ms 50% distribution paretonormal loss 0.025% 25%" if [[ -z $(command -v docker) ]]; then echo "docker is required but not installed. Visit https://docs.docker.com/install/ to install." @@ -20,17 +32,56 @@ fi cd "${BASEDIR}" || exit 1 +NUM_USS=${NUM_USS:-2} +NUM_NODES=${NUM_NODES:-1} +DB_TYPE=${DB_TYPE:-crdb} + DC_COMMAND=$* if [[ ! "$DC_COMMAND" ]]; then DC_COMMAND="up" - DC_OPTIONS="--build" + DC_OPTIONS="--build --wait" elif [[ "$DC_COMMAND" == "down" ]]; then DC_OPTIONS="--volumes --remove-orphans" elif [[ "$DC_COMMAND" == "debug" ]]; then DC_COMMAND=up + DC_OPTIONS="--wait" export DEBUG_ON=1 fi -# shellcheck disable=SC2086 -docker compose -f docker-compose.yaml -p local_infra $DC_COMMAND $DC_OPTIONS +if [[ "$DC_COMMAND" == up* ]]; then + echo "Creating networks..." + docker network create --subnet=172.27.0.0/16 \ + --ip-range=172.27.0.0/24 \ + --gateway=172.27.0.1 \ + dss_internal_network || true + docker network create interop_ecosystem_network || true + echo "Starting containers..." +fi + +for ((i=1; i<=NUM_USS; i++)); do + for ((j=1; j<=NUM_NODES; j++)); do + export USS_IDX=$i + export USS_NODE_IDX=$j + PADDED_NODE_IDX=$(printf "%02d" $(( (i-1) * NUM_NODES + j))) + export PADDED_NODE_IDX + + export COMPOSE_PROFILES=${DB_TYPE} + if [ "$i" -eq 1 ] && [ "$j" -eq 1 ]; then + export COMPOSE_PROFILES=${COMPOSE_PROFILES},oauth + fi + if [ "$i" -eq "$NUM_USS" ] && [ "$j" -eq "$NUM_NODES" ]; then + export COMPOSE_PROFILES=${COMPOSE_PROFILES},bootstrap-${DB_TYPE} + fi + + # shellcheck disable=SC2086 + docker compose -f docker-compose.yaml -p "local_infra_${USS_IDX}-${USS_NODE_IDX}" $DC_COMMAND $DC_OPTIONS & + done +done +wait + +if [[ "$DC_COMMAND" == "down" ]]; then + echo "Removing networks..." + docker network rm dss_internal_network || true + docker network rm interop_ecosystem_network || true +fi diff --git a/build/dev/startup/core_service.sh b/build/dev/startup/core_service.sh index a0760567e1..085ff8317e 100755 --- a/build/dev/startup/core_service.sh +++ b/build/dev/startup/core_service.sh @@ -1,32 +1,52 @@ #!/bin/sh +set -e + # This startup script is meant to be invoked from within a Docker container # started by docker-compose.yaml, not on a local system. DEBUG_ON=${1:-0} +JWT_AUDIENCES="localhost,host.docker.internal,${JWT_AUDIENCES}" + +# POSIX compliant test to check if ybdb profile is enabled. +if [ "${COMPOSE_PROFILES#*"ybdb"}" != "${COMPOSE_PROFILES}" ]; then + echo "Using Yugabyte" + DATASTORE_CONNECTION="-datastore_host ${DATASTORE_HOST} -datastore_user yugabyte --datastore_port 5433" +else + echo "Using CockroachDB" + DATASTORE_CONNECTION="-datastore_host ${DATASTORE_HOST}" +fi if [ "$DEBUG_ON" = "1" ]; then echo "Debug Mode: on" + # Linter is disabled to properly unwrap $DATASTORE_CONNECTION. + # shellcheck disable=SC2086 dlv --headless --listen=:4000 --api-version=2 --accept-multiclient exec --continue /usr/bin/core-service -- \ - -cockroach_host crdb.uss1.localutm \ + ${DATASTORE_CONNECTION} \ -public_key_files /var/test-certs/auth2.pem \ -log_format console \ -dump_requests \ -addr :80 \ - -accepted_jwt_audiences localhost,host.docker.internal,dss.uss1.localutm,dss.uss2.localutm \ + -accepted_jwt_audiences ${JWT_AUDIENCES} \ -enable_scd \ - -enable_http + -allow_http_base_urls \ + -locality local_dev \ + -public_endpoint http://127.0.0.1:80 else echo "Debug Mode: off" + # Linter is disabled to properly unwrap $DATASTORE_CONNECTION. + # shellcheck disable=SC2086 /usr/bin/core-service \ - -cockroach_host crdb.uss1.localutm \ + ${DATASTORE_CONNECTION} \ -public_key_files /var/test-certs/auth2.pem \ -log_format console \ -dump_requests \ -addr :80 \ - -accepted_jwt_audiences localhost,host.docker.internal,dss.uss1.localutm,dss.uss2.localutm \ + -accepted_jwt_audiences ${JWT_AUDIENCES} \ -enable_scd \ - -enable_http + -allow_http_base_urls \ + -locality local_dev \ + -public_endpoint http://127.0.0.1:80 fi diff --git a/build/dev/startup/db-entrypoint.sh b/build/dev/startup/db-entrypoint.sh new file mode 100755 index 0000000000..022dbcb9df --- /dev/null +++ b/build/dev/startup/db-entrypoint.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# shellcheck disable=SC2086 +# Script to be used as the Docker entrypoint for crdb and ybdb to apply tc rules. + +set -e + +if [ -n "$INTRA_USS_NETEM_CONF" ] || [ -n "$INTER_USS_NETEM_CONF" ]; then + + # install iproute-tc + if [ -e /cockroach/cockroach.sh ]; then # crdb + cat << 'EOF' > /etc/yum.repos.d/CentOS-BaseOS.repo +[centos-baseos] +name=CentOS Stream 9 - BaseOS +baseurl=http://mirror.stream.centos.org/9-stream/BaseOS/$basearch/os/ +gpgcheck=0 +enabled=1 +EOF + microdnf -y install iproute-tc + elif [ -e /home/yugabyte/bin/yugabyted ]; then # ybdb + dnf -y install iproute-tc + fi + + # create handle on default interface + tc qdisc add dev eth0 root handle 1: prio + + # apply netem config for intra-USS subnet + if [ -n "$INTRA_USS_NETEM_CONF" ]; then + tc qdisc add dev eth0 parent 1:2 handle 30: netem $INTRA_USS_NETEM_CONF + tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip dst "$INTRA_USS_SUBNET" flowid 1:2 + fi + + # apply netem config for inter-USS subnet + if [ -n "$INTER_USS_NETEM_CONF" ]; then + tc qdisc add dev eth0 parent 1:3 handle 31: netem $INTER_USS_NETEM_CONF + tc filter add dev eth0 parent 1:0 protocol ip prio 2 u32 match ip dst "$INTER_USS_SUBNET" flowid 1:3 + fi +fi +exec "$@" diff --git a/build/test-certs/auth2.key b/build/test-certs/auth2.key index 3a0fd0f1e2..7fef110629 100644 --- a/build/test-certs/auth2.key +++ b/build/test-certs/auth2.key @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgHkNtpy3GB0YTCl2VCCd22i0rJwIGBSazD4QRKvH6rch0IP4igb+ -02r7t0X//tuj0VbwtJz3cEICP8OGSqrdTSCGj5Y03Oa2gPkx/0c0V8D0eSXS/CUC -0qrYHnAGLqko7eW87HW0rh7nnl2bB4Lu+R8fOmQt5frCJ5eTkzwK5YczAgMBAAEC -gYAtSgMjGKEt6XQ9IucQmN6Iiuf1LFYOB2gYZC+88PuQblc7uJWzTk08vlXwG3l3 -JQ/h7gY0n6JhH8RJW4m96TO8TrlHLx5aVcW8E//CtgayMn3vBgXida3wvIlAXT8G -WezsNsWorXLVmz5yov0glu+TIk31iWB5DMs4xXhXdH/t8QJBALQzvF+y5bZEhZin -qTXkiKqMsKsJbXjP1Sp/3t52VnYVfbxN3CCb7yDU9kg5QwNa3ungE3cXXNMUr067 -9zIraekCQQCr+NSeWAXIEutWewPIykYMQilVtiJH4oFfoEpxvecVv7ulw6kM+Jsb -o6Pi7x86tMVkwOCzZzy/Uyo/gSHnEZq7AkEAm0hBuU2VuTzOyr8fhvtJ8X2O97QG -C6c8j4Tk7lqXIuZeFRga6la091vMZmxBnPB/SpX28BbHvHUEpBpBZ5AVkQJAX7Lq -7urg3MPafpeaNYSKkovG4NGoJgSgJgzXIJCjJfE6hTZqvrMh7bGUo9aZtFugdT74 -TB2pKncnTYuYyDN9vQJACDVr+wvYYA2VdnA9k+/1IyGc1HHd2npQqY9EduCeOGO8 -rXQedG6rirVOF6ypkefIayc3usipVvfadpqcS5ERhw== +MIIEowIBAAKCAQEAtjrMt+vOxuqjOU+hwrVAgHjBMs9nMw1ONSpiLOpUQSnqvBwB +0Zba+8e2tGJeQDWEf8KoVt3PfMa34EomLhWZGWEosftV8gZSbneDjUnE+kUTXvZm +MFa2Byc9njsl5N7dOC1pT7qHNJOB9RhK/9ehPl7XczRuIJR7N/ZFk+XZSlFh5wer +q7ME7GdhBPFaO30KZRmRhgwVtAP2kA8sSrqgxXuuyy808UvT3MkzL63Sv/EzQSs8 +YLVAn/BwiQlmWxKFmtzNk9RJdYkxRJN1E9E2sS2i6b55j94hiPwwQ16GsAAWUT+i +jezlHuGZU+OLYtYfoHy+4Ku31doQc0ujvtYOJQIDAQABAoIBACTuccLslXGW6BGb +Y+s0FKh00KLdicq87Za0ykTUENNMDXimLHAvpJ3Wcd7I+NUGg53o83j3Zy+gjm90 +V5yLYAXWvQqlJ1vvkBE3Q4AE7VjTWwOp6DfvuuBkQYap8hoaWLcj7O3tna04H+Ru +UfTb3J/pVLzSaWdM8FP9I0jAEnOPBwwgmQ74G/NmCZpv39boLJQ2sdqY9gSaQUsn +4fwKtQQ6yBLDw8mkNnzxnRxXe7FiKjKtnTUp7GhELoQ37XbwJRJTlBA9jirxadss +4NiHpSq+QhkHdJPMOdE/DTZ0H52mJe92t+EEptJADX4O4b54oNaJ4p99Zk0rlW97 +s3lowdkCgYEAygLJiYBpv/wuJd1peqLoYnOIBriFQi7vSF+Ho4UNnS76IapSZ/qY +nUGrTYNaNhTg39Gsbpdax7bnaWiTDNzdCiiQxMc5BSR3tDF52tIaOqvVFgd5KRu/ +7pOtH8fToR770KviT85G8Wb+ozFY7F0/yklU+OQEtfmuAVs6bEx4m8cCgYEA5u6e +e00l01h/SvM7CoYdoOG9N1vyhMYbUBDR6SUIAaR25/UJPH7kPpAqxXKR1BWJh7q+ +1cBF0ZFnQ3xkRsfETcvLPJLAsIMwd1HN2sJ0/tPSgFSw6RTFswa3SlpxON+8shcR +2617UhFDc/5MfCXiNb8u/4Ng7iacfjVhdLehzrMCgYAX1g5byCgyPBph42dPzisn +esRhLqKitZEMdCE4HToHAwUGtec1V69sVtRUuBwL55jFMCNthTRz/lP97xXy3ZjD +WxgB8BP9VFk/jNr5A/OOWrow+D7Gp/yUtR4ncte42kQSUkXI7ukWEPYY4XjBoxsk +zlRVbepUYpqylEYngzpz/wKBgQCNwSntXDT839T7iATU9/CWAhupMLrUv9qiMkD4 +EXAxuef3iNWLmgS3Vr26iBJ2EmZit8JO6YCyHMQ7i87uF9ArRQ7Tdu3rLAyDIebw +Au/YQOR1PAeAe+zDcTrv3Eal98kXtMuUgpAxl0FFoXMHviV2go3x8I5+gZsMae4R +vGsJuwKBgH1KY+N3629mtdyhb1xfoxFRDq0Izyl/OpeWZHe4wd+nklkRsj0CvLHz +m+F5V4CxlUufLadvg9KFxd48UWTwqjrTysixDN4MUPngFoBSh0i/egq6DJlsEhAL +0Q9fZLTCV7sKG9FAUCWT6nE+k8K9U0hTCUeyVqx85iTUu10zQrTR -----END RSA PRIVATE KEY----- diff --git a/build/test-certs/auth2.pem b/build/test-certs/auth2.pem index 5807b63ba0..4548f9c512 100644 --- a/build/test-certs/auth2.pem +++ b/build/test-certs/auth2.pem @@ -1,6 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHkNtpy3GB0YTCl2VCCd22i0rJwI -GBSazD4QRKvH6rch0IP4igb+02r7t0X//tuj0VbwtJz3cEICP8OGSqrdTSCGj5Y0 -3Oa2gPkx/0c0V8D0eSXS/CUC0qrYHnAGLqko7eW87HW0rh7nnl2bB4Lu+R8fOmQt -5frCJ5eTkzwK5YczAgMBAAE= +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjrMt+vOxuqjOU+hwrVA +gHjBMs9nMw1ONSpiLOpUQSnqvBwB0Zba+8e2tGJeQDWEf8KoVt3PfMa34EomLhWZ +GWEosftV8gZSbneDjUnE+kUTXvZmMFa2Byc9njsl5N7dOC1pT7qHNJOB9RhK/9eh +Pl7XczRuIJR7N/ZFk+XZSlFh5werq7ME7GdhBPFaO30KZRmRhgwVtAP2kA8sSrqg +xXuuyy808UvT3MkzL63Sv/EzQSs8YLVAn/BwiQlmWxKFmtzNk9RJdYkxRJN1E9E2 +sS2i6b55j94hiPwwQ16GsAAWUT+ijezlHuGZU+OLYtYfoHy+4Ku31doQc0ujvtYO +JQIDAQAB -----END PUBLIC KEY----- diff --git a/github_pages/make_site_content.sh b/github_pages/make_site_content.sh index 4805b5a269..0e1e272120 100755 --- a/github_pages/make_site_content.sh +++ b/github_pages/make_site_content.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script generates the content for this repository's GitHub Pages site. It is invoked by the CI and expects the # working folder to contain: # ./monitoring: this repository diff --git a/github_pages/static/index.md b/github_pages/static/index.md index 66e2bbbfd1..2b7fdd0224 100644 --- a/github_pages/static/index.md +++ b/github_pages/static/index.md @@ -10,6 +10,7 @@ These reports were generated during continuous integration for the most recent P * [Sequence view](./artifacts/uss_qualifier/reports/uspace/sequence) * [Tested requirements](./artifacts/uss_qualifier/reports/uspace/requirements) +* [Timing report](./artifacts/uss_qualifier/reports/uspace/timing) * [Demonstrated capabilities](./artifacts/uss_qualifier/reports/uspace/capabilities.html) * [Raw report](./artifacts/uss_qualifier/reports/uspace/report.json) (large) @@ -18,27 +19,32 @@ These reports were generated during continuous integration for the most recent P * [Raw report](./artifacts/uss_qualifier/reports/noop/report.json) (indented to be human-readable) * [Interactive report](./artifacts/uss_qualifier/reports/noop/report.html) * [Sequence view](./artifacts/uss_qualifier/reports/noop/sequence) +* [Timing report](./artifacts/uss_qualifier/reports/noop/timing) ### [ASTM F3548-21 test configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml) * [Sequence view](./artifacts/uss_qualifier/reports/f3548_self_contained/sequence) * [Tested requirements](./artifacts/uss_qualifier/reports/f3548_self_contained/gate3) * [Globally-expanded report](./artifacts/uss_qualifier/reports/f3548_self_contained/globally_expanded/report.html) +* [Timing report](./artifacts/uss_qualifier/reports/f3548_self_contained/timing) -### [US UTM Implementation test configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/utm_implementation_us.yaml) +### [US UTM Implementation test configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/environments/local/test_1.jsonnet) -* [Sequence view](./artifacts/uss_qualifier/reports/utm_implementation_us/sequence) -* [Tested requirements](./artifacts/uss_qualifier/reports/utm_implementation_us/scd) +* [Sequence view](./artifacts/uss_qualifier/reports/test_1/sequence) +* [Tested requirements](./artifacts/uss_qualifier/reports/test_1/scd) +* [Timing report](./artifacts/uss_qualifier/reports/test_1/timing) ### [ASTM F3411-22a test configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml) * [Sequence view](./artifacts/uss_qualifier/reports/netrid_v22a/sequence) * [Tested requirements](./artifacts/uss_qualifier/reports/netrid_v22a/requirements) +* [Timing report](./artifacts/uss_qualifier/reports/netrid_v22a/timing) ### [DSS integration test configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml) * [Sequence view](./artifacts/uss_qualifier/reports/dss_probing/sequence) * [Tested requirements](./artifacts/uss_qualifier/reports/dss_probing/requirements) +* [Timing report](./artifacts/uss_qualifier/reports/dss_probing/timing) ### [General flight authorization configuration](https://github.com/interuss/monitoring/blob/main/monitoring/uss_qualifier/configurations/dev/general_flight_auth.yaml) diff --git a/monitoring/.gitignore b/monitoring/.gitignore index 73f2ddb3e4..497e8e6048 100644 --- a/monitoring/.gitignore +++ b/monitoring/.gitignore @@ -5,3 +5,4 @@ run_locally_scd_deploy_others.sh # Make target placeholders image +image-dev diff --git a/monitoring/Dockerfile b/monitoring/Dockerfile index 3f42810c05..c935488b8b 100644 --- a/monitoring/Dockerfile +++ b/monitoring/Dockerfile @@ -9,15 +9,14 @@ # # This image is intended to be built from the repository root context/folder. -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS base # Not -alpine because: https://stackoverflow.com/a/58028091/651139 # Install system tools # openssl: Provides TLS tools # curl: Useful debugging utility -# gcc: Required to build various packages # ca-certificates: Needed to accurately validate TLS connections -RUN apt-get update --fix-missing && apt-get install -y make openssl curl libgeos-dev gcc g++ && apt-get install ca-certificates +RUN apt-get update --fix-missing && apt-get install -y make openssl curl && apt-get install ca-certificates # Required to build in an ARM environment # gevent: libffi-dev libssl-dev python3-dev build-essential @@ -53,6 +52,7 @@ WORKDIR /app/monitoring # Add core content from repo ADD ./interfaces /app/interfaces +ADD ./schemas /app/schemas ADD ./monitoring /app/monitoring # Add health check to the /app root and make it executable @@ -80,3 +80,12 @@ ENV GIT_COMMIT_HASH=$commit_hash # No entry point maximizes flexibility in the use of this image ENTRYPOINT [] + +FROM base AS with-dev-dependencies + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=./uv.lock,target=/app/uv.lock \ + --mount=type=bind,source=./pyproject.toml,target=/app/pyproject.toml \ + cd /app/ && \ + unset UV_FROZEN && \ + uv sync --locked --no-install-project --compile-bytecode --group dev diff --git a/monitoring/Makefile b/monitoring/Makefile index a8fe36d078..2c2030d8b7 100644 --- a/monitoring/Makefile +++ b/monitoring/Makefile @@ -9,11 +9,14 @@ format: .PHONY: unit-test unit-test: cd uss_qualifier && make unit_test + cd monitorlib && make unit_test -image: ../uv.lock ../pyproject.toml $(shell find . -type f ! -path "*/output/*" ! -name image ! -name *.pyc) $(shell find ../interfaces -type f) - # Building image due to changes in the following files: $? +image: ../uv.lock ../pyproject.toml $(shell find . -type f ! -path "*/output/*" ! -path "*/.*" ! -path "*/__pycache__/*" ! -name image ! -name image-dev ! -name "*.pyc") $(shell find ../interfaces -type f) ./build.sh +image-dev: ../uv.lock ../pyproject.toml $(shell find . -type f ! -path "*/output/*" ! -path "*/.*" ! -path "*/__pycache__/*" ! -name image ! -name image-dev ! -name "*.pyc") $(shell find ../interfaces -type f) + ./build_dev.sh + .PHONY: test test: cd mock_uss && make test diff --git a/monitoring/build.sh b/monitoring/build.sh index 3aa79cd446..136ca42938 100755 --- a/monitoring/build.sh +++ b/monitoring/build.sh @@ -19,6 +19,8 @@ docker image build \ -t "${TAG}" \ --build-arg version="$(scripts/git/version.sh monitoring --long)" \ --build-arg commit_hash="$(git rev-parse HEAD)" \ + --target base \ . \ || exit 1 -echo "File created by monitoring/build.sh to keep track of the latest build run date time." > monitoring/image + +echo "File created by monitoring/build.sh to keep track of the latest normal image build run date time." > monitoring/image diff --git a/monitoring/build_dev.sh b/monitoring/build_dev.sh new file mode 100755 index 0000000000..772c9ee9e2 --- /dev/null +++ b/monitoring/build_dev.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# Find and change to repo root directory +OS=$(uname) +if [[ "$OS" == "Darwin" ]]; then + # OSX uses BSD readlink + BASEDIR="$(dirname "$0")" +else + BASEDIR=$(readlink -e "$(dirname "$0")") +fi +cd "${BASEDIR}/.." || exit 1 + +TAG="${1:-interuss/monitoring}" + +docker image build \ + -f monitoring/Dockerfile \ + -t "${TAG}-dev" \ + --build-arg version="$(scripts/git/version.sh monitoring --long)" \ + --build-arg commit_hash="$(git rev-parse HEAD)" \ + --target with-dev-dependencies \ + . \ + || exit 1 + +echo "File created by monitoring/build_dev.sh to keep track of the latest dev image build run date time." > monitoring/image-dev diff --git a/monitoring/deployment_manager/systems/configuration.py b/monitoring/deployment_manager/systems/configuration.py index bd512166d4..d149630efb 100644 --- a/monitoring/deployment_manager/systems/configuration.py +++ b/monitoring/deployment_manager/systems/configuration.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.deployment_manager.systems.dss.configuration import DSS from monitoring.deployment_manager.systems.test.configuration import Test @@ -14,11 +14,11 @@ class KubernetesCluster(ImplicitDict): class DeploymentSpec(ImplicitDict): - cluster: KubernetesCluster | None + cluster: Optional[KubernetesCluster] """Definition of Kubernetes cluster containing this deployment.""" - test: Test | None + test: Optional[Test] """Test systems in this deployment.""" - dss: DSS | None + dss: Optional[DSS] """DSS instance in this deployment.""" diff --git a/monitoring/deployment_manager/systems/test/configuration.py b/monitoring/deployment_manager/systems/test/configuration.py index 09b861b134..3c2b90bf9a 100644 --- a/monitoring/deployment_manager/systems/test/configuration.py +++ b/monitoring/deployment_manager/systems/test/configuration.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional class TestV1(ImplicitDict): @@ -6,4 +6,4 @@ class TestV1(ImplicitDict): class Test(ImplicitDict): - v1: TestV1 | None + v1: Optional[TestV1] diff --git a/monitoring/loadtest/README.md b/monitoring/loadtest/README.md index 6f6380e28a..3879f23e24 100644 --- a/monitoring/loadtest/README.md +++ b/monitoring/loadtest/README.md @@ -1,39 +1,104 @@ # LoadTest tool ## Introduction -The LoadTest tool is based on [Locust](https://docs.locust.io/en/stable/index.html) which provides a UI for controlling the number of Users to spawn and make random requests. Currently its configured to make the request in the ratio 10 x Create ISA : 5 x Update ISA : 100 x Get ISA : 1 x Delete ISA. This means the User is 10 times likely to Create an ISA vs Deleting an ISA, and 10 times more likely to Get ISA vs Creating an ISA and so on. Subscription workflow is heavier on the Write side with the ratio of 100 x Create Sub : 50 x Update Sub : 20 x Get Sub : 5 x Delete Sub. +The LoadTest tool is based on [Locust](https://docs.locust.io/en/stable/index.html) which provides a UI for controlling the number of Users to spawn and make random requests. + +## Available tests + +### ISA.py + +Create ISA on RID endpoints. + +Currently its configured to make the request in the ratio 10 x Create ISA : 5 x Update ISA : 100 x Get ISA : 1 x Delete ISA. This means the User is 10 times likely to Create an ISA vs Deleting an ISA, and 10 times more likely to Get ISA vs Creating an ISA and so on. + +Parameters: + +* `--uss-base-url`: Base URL of the USS, used to create ISAs. + +### Sub.py + +Create subscriptions on RID endpoints. + +Subscription workflow is heavier on the Write side with the ratio of 100 x Create Sub : 50 x Update Sub : 20 x Get Sub : 5 x Delete Sub. + +Parameters: + +* `--uss-base-url`: Base URL of the USS, used to create subscriptions. + +### SCD.py + +Create operational intents on SCD endpoints. + +Flights will be created based on parameters. + +Parameters: + +* `--uss-base-url`: Base URL of the USS, used to create subscriptions. +* `--area-lat`: Latitude of the center of the area in which to create flights +* `--area-lng`: Longitude of the center of the area in which to create flights +* `--area-radius`: Radius (in meters) of the area in which to create flights +* `--area-lat`: Maximum distance to cover for an individual flight + +### FlightsInSub.py + +Create subscriptions on N area and then create operational intents in thoses subscriptions using SCD endpoints. + +Flights and subscriptions will be created based on parameters. +Clusters are shifted by approimatly 2*Radius on the latitude axe. + +There will be one subscriptions per area per client. + +Parameters: + +* `--uss-base-url`: Base URL of the USS, used to create subscriptions. +* `--cluster-count`: Number of clusters to create +* `--base-lat`: Latitude of the center of the first cluster +* `--base-lng`: Longitude of the center of the first cluster +* `--area-radius`: Radius (in meters) of the area in which to create flights +* `--area-lat`: Maximum distance to cover for an individual flight ## Adjusting workload ratio -In each files every action has a weight declared in the `@task(n)` decorator. You can adjust the value of `n` to suite your needs +For `ISA.py` and `Sub.py`, every action has a weight declared in the `@task(n)` decorator. You can adjust the value of `n` to suite your needs ## Run locally without Docker 1. Go to the repository's root directory. We have to execute from root directory due to our directory structure choice. 1. Install UV: https://docs.astral.sh/uv/getting-started/installation/ 1. Set OAuth Spec with environment variable `AUTH_SPEC`. See [the auth spec documentation](../monitorlib/README.md#Auth_specs) for the format of these values. Omitting this step will result in Client Initialization failure. -1. You have 2 options of load testing the ISA or Subscription workflow - - a. For ISA run: `AUTH_SPEC="" uv run locust -f ./monitoring/loadtest/locust_files/ISA.py -H ` - b. For Subscription run: `AUTH_SPEC="" uv run locust -f ./monitoring/loadtest/locust_files/Sub.py -H ` +1. Run the loadtest: `AUTH_SPEC="" uv run locust -f ./monitoring/loadtest/locust_files/ -H [Parameters]` ## Running in a Container Simply build the Docker container with the Dockerfile from the root directory. All the files are added into the container 1. From the root folder of this repository, build the monitoring image with `make image` -1. Run Docker container; in general: +1. Run Docker container; in general:: `docker run -e AUTH_SPEC="" -p 8089:8089 interuss/monitoring uv run locust -f loadtest/locust_files/ -H [Parameters]` +1. If testing local DSS instance, be sure that the loadtest (monitoring) container has access to the DSS container: `docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 interuss/monitoring uv run locust -f loadtest/locust_files/ -H [Parameters]` + +## Use +1. Navigate to http://127.0.0.1:8089 +1. Start new test with number of Users to spawn and the rate to spawn them. +1. For the Host, provide the DSS root endpoint used for testing. An example of such url is: http://dss1.uss1.localutm/ in case local environment is setup with `make start-locally` - a. For ISA run: `docker run -e AUTH_SPEC="" -p 8089:8089 interuss/monitoring uv run locust -f loadtest/locust_files/ISA.py` +## Examples to run tests locally - b. For Sub run: `docker run -e AUTH_SPEC="" -p 8089:8089 interuss/monitoring uv run locust -f loadtest/locust_files/Sub.py` +Before running all examples: -1. If testing local DSS instance, be sure that the loadtest (monitoring) container has access to the DSS container: +* `make image` +* `make start-locally` - a. For ISA run: `docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 interuss/monitoring uv run locust -f loadtest/locust_files/ISA.py` +### ISA.py - b. For Sub run: `docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 interuss/monitoring uv run locust -f loadtest/locust_files/Sub.py` +`docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 -v .:/app/ interuss/monitoring-dev uv run locust -f loadtest/locust_files/ISA.py -H http://dss1.uss1.localutm -u 10 --uss-base-url http://dss1.uss1.localutm` -## Use -1. Navigate to http://localhost:8089 -1. Start new test with number of Users to spawn and the rate to spawn them. -1. For the Host, provide the DSS Core Service endpoint used for testing. An example of such url is: http://dss.uss1.localutm/v1/dss/ in case local environment is setup with `make start-locally` +### Sub.py + +`docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 -v .:/app/ interuss/monitoring-dev uv run locust -f loadtest/locust_files/Sub.py -H http://dss1.uss1.localutm -u 10 --uss-base-url http://dss1.uss1.localutm` + +### SCD.py + +`docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 -v .:/app/ interuss/monitoring-dev uv run locust -f loadtest/locust_files/SCD.py -H http://dss1.uss1.localutm -u 10 --area-lat -34.93 --area-lng 138.6 --area-radius 1000 --max-flight-distance 12000 --uss-base-url http://dss1.uss1.localutm` + +### FlightsInSub.py + +`docker run -e AUTH_SPEC="DummyOAuth(http://oauth.authority.localutm:8085/token,uss1)" --network="interop_ecosystem_network" -p 8089:8089 -v .:/app/ interuss/monitoring-dev uv run locust -f loadtest/locust_files/FlightsInSub.py -H http://dss1.uss1.localutm -u 10 --cluster-count 3 --base-lat -34.93 --base-lng 138.6 --area-radius 1000 --max-flight-distance 1000 --uss-base-url http://dss1.uss1.localutm` diff --git a/monitoring/loadtest/locust_files/FlightsInSub.py b/monitoring/loadtest/locust_files/FlightsInSub.py new file mode 100644 index 0000000000..cbbed3cd9b --- /dev/null +++ b/monitoring/loadtest/locust_files/FlightsInSub.py @@ -0,0 +1,152 @@ +import argparse +import datetime +import random +import uuid +from collections import namedtuple + +import client +import locust +from geo_utils import create_random_flight_path_volume +from utils import format_time + +from monitoring.monitorlib.testing import make_fake_url + +Cluster = namedtuple("Cluster", ["lng", "lat", "uuid", "version"]) + + +@locust.events.init_command_line_parser.add_listener +def init_parser(parser: argparse.ArgumentParser): + """Setup config params, populated by locust.conf.""" + + parser.add_argument( + "--uss-base-url", + type=str, + help="Base URL of the USS", + required=True, + ) + parser.add_argument( + "--cluster-count", + type=int, + help="Number of clusters to create. One subscription will be created per cluster per client.", + required=True, + ) + parser.add_argument( + "--base-lat", + type=float, + help="Latitude of the center of the first cluster", + required=True, + ) + parser.add_argument( + "--base-lng", + type=float, + help="Longitude of the center of the first cluster", + required=True, + ) + parser.add_argument( + "--area-radius", + type=int, + help="Radius (in meters) of clusters.", + required=True, + ) + parser.add_argument( + "--max-flight-distance", + type=int, + help="Maximum distance to cover for an individual flight", + required=True, + ) + + +class SCD(client.USS): + wait_time = locust.between(0.01, 0.1) + + def on_start(self): + self.uss_base_url = self.environment.parsed_options.uss_base_url + self.radius = self.environment.parsed_options.area_radius + self.max_flight_distance = self.environment.parsed_options.max_flight_distance + + self.clusters = [] + + lat = self.environment.parsed_options.base_lat + lng = self.environment.parsed_options.base_lng + + time_start = datetime.datetime.now(datetime.UTC) + time_end = time_start + datetime.timedelta(minutes=60) + + for _ in range(self.environment.parsed_options.cluster_count): + sub_uuid = str(uuid.uuid4()) + + resp = self.client.put( + f"/dss/v1/subscriptions/{sub_uuid}", + json={ + "extents": { + "volume": { + "outline_circle": { + "center": {"lng": lng, "lat": lat}, + "radius": {"value": self.radius, "units": "M"}, + }, + "altitude_lower": { + "value": 0, + "reference": "W84", + "units": "M", + }, + "altitude_upper": { + "value": 10000, + "reference": "W84", + "units": "M", + }, + }, + "time_start": { + "value": format_time(time_start), + "format": "RFC3339", + }, + "time_end": { + "value": format_time(time_end), + "format": "RFC3339", + }, + }, + "uss_base_url": make_fake_url(), + "notify_for_operational_intents": True, + }, + name="/subscriptions/[sub_uuid]", + ) + + if resp.status_code == 200: + self.clusters.append( + Cluster( + lng=lng, + lat=lat, + uuid=sub_uuid, + version=resp.json()["subscription"]["version"], + ) + ) + + # Move latitude approtimatly + lat += (self.radius * 2) / 111111 + + def on_stop(self): + for cluster in self.clusters: + self.client.delete( + f"/dss/v1/subscriptions/{cluster.uuid}/{cluster.version}" + ) + + @locust.task + def task_put_intent(self): + cluster = random.choice(self.clusters) + + entity_id = uuid.uuid4().hex + + body = { + "state": "Accepted", + "uss_base_url": self.uss_base_url, + "new_subscription": { + "uss_base_url": self.uss_base_url, + }, + "extents": create_random_flight_path_volume( + cluster.lat, cluster.lng, self.radius, self.max_flight_distance + ), + } + self.client.put( + f"/dss/v1/operational_intent_references/{entity_id}", + json=body, + name="/dss/v1/operational_intent_references/[id]", + ) diff --git a/monitoring/loadtest/locust_files/ISA.py b/monitoring/loadtest/locust_files/ISA.py index 7b23e00ec2..9d9acfb8a4 100644 --- a/monitoring/loadtest/locust_files/ISA.py +++ b/monitoring/loadtest/locust_files/ISA.py @@ -1,15 +1,12 @@ -#!env/bin/python3 - +import argparse import datetime import random import threading import uuid import client -from locust import between, task - -from monitoring.monitorlib import rid_v1 -from monitoring.monitorlib.testing import make_fake_url +import locust +from utils import format_time VERTICES = [ {"lng": 130.6205, "lat": -23.6558}, @@ -19,37 +16,65 @@ ] +@locust.events.init_command_line_parser.add_listener +def init_parser(parser: argparse.ArgumentParser): + """Setup config params, populated by locust.conf.""" + + parser.add_argument( + "--uss-base-url", + type=str, + help="Base URL of the USS", + required=True, + ) + + class ISA(client.USS): - wait_time = between(0.01, 1) + wait_time = locust.between(0.01, 1) lock = threading.Lock() - @task(10) + @locust.task(10) def create_isa(self): time_start = datetime.datetime.now(datetime.UTC) time_end = time_start + datetime.timedelta(minutes=60) isa_uuid = str(uuid.uuid4()) resp = self.client.put( - f"/identification_service_areas/{isa_uuid}", + f"/rid/v2/dss/identification_service_areas/{isa_uuid}", json={ "extents": { - "spatial_volume": { - "footprint": { + "volume": { + "outline_polygon": { "vertices": VERTICES, }, - "altitude_lo": 20, - "altitude_hi": 400, + "altitude_lower": { + "value": 20, + "reference": "W84", + "units": "M", + }, + "altitude_upper": { + "value": 400, + "reference": "W84", + "units": "M", + }, + }, + "time_start": { + "value": format_time(time_start), + "format": "RFC3339", + }, + "time_end": { + "value": format_time(time_end), + "format": "RFC3339", }, - "time_start": time_start.strftime(rid_v1.DATE_FORMAT), - "time_end": time_end.strftime(rid_v1.DATE_FORMAT), }, - "flights_url": make_fake_url(), + "uss_base_url": self.uss_base_url, }, + name="/identification_service_areas/[isa_uuid]", ) if resp.status_code == 200: - self.isa_dict[isa_uuid] = resp.json()["service_area"]["version"] + with self.lock: + self.isa_dict[isa_uuid] = resp.json()["service_area"]["version"] - @task(5) + @locust.task(5) def update_isa(self): target_isa, target_version = self.checkout_isa() if not target_isa: @@ -59,57 +84,77 @@ def update_isa(self): time_start = datetime.datetime.now(datetime.UTC) time_end = datetime.datetime.now(datetime.UTC) + datetime.timedelta(minutes=2) resp = self.client.put( - f"/identification_service_areas/{target_isa}/{target_version}", + f"/rid/v2/dss/identification_service_areas/{target_isa}/{target_version}", json={ "extents": { - "spatial_volume": { - "footprint": { + "volume": { + "outline_polygon": { "vertices": VERTICES, }, - "altitude_lo": 20, - "altitude_hi": 400, + "altitude_lower": { + "value": 20, + "reference": "W84", + "units": "M", + }, + "altitude_upper": { + "value": 400, + "reference": "W84", + "units": "M", + }, + }, + "time_start": { + "value": format_time(time_start), + "format": "RFC3339", + }, + "time_end": { + "value": format_time(time_end), + "format": "RFC3339", }, - "time_start": time_start.strftime(rid_v1.DATE_FORMAT), - "time_end": time_end.strftime(rid_v1.DATE_FORMAT), }, - "flights_url": make_fake_url(), + "uss_base_url": self.uss_base_url, }, + name="/identification_service_areas/[target_isa]/[target_version]", ) if resp.status_code == 200: - self.isa_dict[target_isa] = resp.json()["service_area"]["version"] + with self.lock: + self.isa_dict[target_isa] = resp.json()["service_area"]["version"] - @task(100) + @locust.task(100) def get_isa(self): - target_isa = ( - random.choice(list(self.isa_dict.keys())) if self.isa_dict else None - ) - if not target_isa: + if not self.isa_dict: print("Nothing to pick from isa_dict for GET") return - self.client.get(f"/identification_service_areas/{target_isa}") + with self.lock: + target_isa = random.choice(list(self.isa_dict.keys())) + self.client.get( + f"/rid/v2/dss/identification_service_areas/{target_isa}", + name="/identification_service_areas/[target_isa]", + ) - @task(1) + @locust.task(1) def delete_isa(self): target_isa, target_version = self.checkout_isa() if not target_isa: print("Nothing to pick from isa_dict for DELETE") return self.client.delete( - f"/identification_service_areas/{target_isa}/{target_version}" + f"/rid/v2/dss/identification_service_areas/{target_isa}/{target_version}", + name="/identification_service_areas/[target_isa]/[target_version]", ) def checkout_isa(self): - self.lock.acquire() - target_isa = ( - random.choice(list(self.isa_dict.keys())) if self.isa_dict else None - ) - target_version = self.isa_dict.pop(target_isa, None) - self.lock.release() + with self.lock: + if not self.isa_dict: + return None, None + target_isa = random.choice(list(self.isa_dict.keys())) + target_version = self.isa_dict.pop(target_isa, None) return target_isa, target_version def on_start(self): + self.uss_base_url = self.environment.parsed_options.uss_base_url # insert atleast 1 ISA for update to not fail self.create_isa() def on_stop(self): - self.isa_dict = {} + while self.isa_dict: # Drain ISAs + self.delete_isa() diff --git a/monitoring/loadtest/locust_files/SCD.py b/monitoring/loadtest/locust_files/SCD.py new file mode 100644 index 0000000000..0f7e9b2dc7 --- /dev/null +++ b/monitoring/loadtest/locust_files/SCD.py @@ -0,0 +1,73 @@ +import argparse +import uuid + +import client +import locust +from geo_utils import create_random_flight_path_volume + + +@locust.events.init_command_line_parser.add_listener +def init_parser(parser: argparse.ArgumentParser): + """Setup config params, populated by locust.conf.""" + + parser.add_argument( + "--uss-base-url", + type=str, + help="Base URL of the USS", + required=True, + ) + parser.add_argument( + "--area-lat", + type=float, + help="Latitude of the center of the area in which to create flights", + required=True, + ) + parser.add_argument( + "--area-lng", + type=float, + help="Longitude of the center of the area in which to create flights", + required=True, + ) + parser.add_argument( + "--area-radius", + type=int, + help="Radius (in meters) of the area in which to create flights", + required=True, + ) + parser.add_argument( + "--max-flight-distance", + type=int, + help="Maximum distance to cover for an individual flight", + required=True, + ) + + +class SCD(client.USS): + wait_time = locust.between(0.01, 0.1) + + def on_start(self): + self.uss_base_url = self.environment.parsed_options.uss_base_url + self.lat = self.environment.parsed_options.area_lat + self.lng = self.environment.parsed_options.area_lng + self.radius = self.environment.parsed_options.area_radius + self.max_flight_distance = self.environment.parsed_options.max_flight_distance + + @locust.task + def task_put_intent(self): + entity_id = uuid.uuid4().hex + + body = { + "state": "Accepted", + "uss_base_url": self.uss_base_url, + "new_subscription": { + "uss_base_url": self.uss_base_url, + }, + "extents": create_random_flight_path_volume( + self.lat, self.lng, self.radius, self.max_flight_distance + ), + } + self.client.put( + f"/dss/v1/operational_intent_references/{entity_id}", + json=body, + name="/dss/v1/operational_intent_references/[id]", + ) diff --git a/monitoring/loadtest/locust_files/Sub.py b/monitoring/loadtest/locust_files/Sub.py index d2582f7682..e693a5cc75 100644 --- a/monitoring/loadtest/locust_files/Sub.py +++ b/monitoring/loadtest/locust_files/Sub.py @@ -1,18 +1,28 @@ -#!env/bin/python3 +import argparse import datetime import random import threading import uuid import client -from locust import between, task +import locust +from utils import format_time -from monitoring.monitorlib import rid_v1 -from monitoring.monitorlib.testing import make_fake_url + +@locust.events.init_command_line_parser.add_listener +def init_parser(parser: argparse.ArgumentParser): + """Setup config params, populated by locust.conf.""" + + parser.add_argument( + "--uss-base-url", + type=str, + help="Base URL of the USS", + required=True, + ) class Sub(client.USS): - wait_time = between(0.01, 1) + wait_time = locust.between(0.01, 1) lock = threading.Lock() def gen_vertices(self): @@ -25,43 +35,61 @@ def gen_vertices(self): {"lng": base_lng + 0.6466, "lat": base_lat + 0.6407}, ] - @task(100) + @locust.task(100) def create_sub(self): time_start = datetime.datetime.now(datetime.UTC) time_end = time_start + datetime.timedelta(minutes=60) sub_uuid = str(uuid.uuid4()) resp = self.client.put( - f"/subscriptions/{sub_uuid}", + f"/rid/v2/dss/subscriptions/{sub_uuid}", json={ "extents": { - "spatial_volume": { - "footprint": { + "volume": { + "outline_polygon": { "vertices": self.gen_vertices(), }, - "altitude_lo": 20, - "altitude_hi": 400, + "altitude_lower": { + "value": 20, + "reference": "W84", + "units": "M", + }, + "altitude_upper": { + "value": 400, + "reference": "W84", + "units": "M", + }, + }, + "time_start": { + "value": format_time(time_start), + "format": "RFC3339", + }, + "time_end": { + "value": format_time(time_end), + "format": "RFC3339", }, - "time_start": time_start.strftime(rid_v1.DATE_FORMAT), - "time_end": time_end.strftime(rid_v1.DATE_FORMAT), }, - "callbacks": {"identification_service_area_url": make_fake_url()}, + "uss_base_url": self.uss_base_url, }, + name="/subscriptions/[sub_uuid]", ) if resp.status_code == 200: - self.sub_dict[sub_uuid] = resp.json()["subscription"]["version"] + with self.lock: + self.sub_dict[sub_uuid] = resp.json()["subscription"]["version"] - @task(20) + @locust.task(20) def get_sub(self): - target_sub = ( - random.choice(list(self.sub_dict.keys())) if self.sub_dict else None + with self.lock: + if not self.sub_dict: + print("Nothing to pick from sub_dict for GET") + return + target_sub = random.choice(list(self.sub_dict.keys())) + self.client.get( + f"/rid/v2/dss/subscriptions/{target_sub}", + name="/subscriptions/[target_sub]", ) - if not target_sub: - print("Nothing to pick from sub_dict for GET") - return - self.client.get(f"/subscriptions/{target_sub}") - @task(50) + @locust.task(50) def update_sub(self): target_sub, target_version = self.checkout_sub() if not target_sub: @@ -71,45 +99,65 @@ def update_sub(self): time_start = datetime.datetime.now(datetime.UTC) time_end = datetime.datetime.now(datetime.UTC) + datetime.timedelta(minutes=2) resp = self.client.put( - f"/subscriptions/{target_sub}/{target_version}", + f"/rid/v2/dss/subscriptions/{target_sub}/{target_version}", json={ "extents": { - "spatial_volume": { - "footprint": { + "volume": { + "outline_polygon": { "vertices": self.gen_vertices(), }, - "altitude_lo": 20, - "altitude_hi": 400, + "altitude_lower": { + "value": 20, + "reference": "W84", + "units": "M", + }, + "altitude_upper": { + "value": 400, + "reference": "W84", + "units": "M", + }, + }, + "time_start": { + "value": format_time(time_start), + "format": "RFC3339", + }, + "time_end": { + "value": format_time(time_end), + "format": "RFC3339", }, - "time_start": time_start.strftime(rid_v1.DATE_FORMAT), - "time_end": time_end.strftime(rid_v1.DATE_FORMAT), }, - "callbacks": {"identification_service_area_url": make_fake_url()}, + "uss_base_url": self.uss_base_url, }, + name="/subscriptions/[target_sub]/[target_version]", ) if resp.status_code == 200: - self.sub_dict[target_sub] = resp.json()["subscription"]["version"] + with self.lock: + self.sub_dict[target_sub] = resp.json()["subscription"]["version"] - @task(5) + @locust.task(5) def delete_sub(self): target_sub, target_version = self.checkout_sub() if not target_sub: print("Nothing to pick from sub_dict for DELETE") return - self.client.delete(f"/subscriptions/{target_sub}/{target_version}") + self.client.delete( + f"/rid/v2/dss/subscriptions/{target_sub}/{target_version}", + name="/subscriptions/[target_sub]/[target_version]", + ) def checkout_sub(self): - self.lock.acquire() - target_sub = ( - random.choice(list(self.sub_dict.keys())) if self.sub_dict else None - ) - target_version = self.sub_dict.pop(target_sub, None) - self.lock.release() + with self.lock: + if not self.sub_dict: + return None, None + target_sub = random.choice(list(self.sub_dict.keys())) + target_version = self.sub_dict.pop(target_sub, None) return target_sub, target_version def on_start(self): + self.uss_base_url = self.environment.parsed_options.uss_base_url # Insert atleast 1 Sub for update to not fail self.create_sub() def on_stop(self): - self.sub_dict = {} + while self.sub_dict: # Drain subscriptions + self.delete_sub() diff --git a/monitoring/loadtest/locust_files/client.py b/monitoring/loadtest/locust_files/client.py index e94d6f66ad..93c7702154 100644 --- a/monitoring/loadtest/locust_files/client.py +++ b/monitoring/loadtest/locust_files/client.py @@ -1,60 +1,16 @@ #!env/bin/python3 import os -import time -from locust import User -from uas_standards.astm.f3411.v19.constants import Scope +import requests +from locust import HttpUser +from uas_standards.astm.f3411.v19.constants import Scope as f3411_scope +from uas_standards.astm.f3548.v21.constants import Scope as f3548_scope -from monitoring.monitorlib import auth, infrastructure +from monitoring.monitorlib import auth -class DSSClient(infrastructure.UTMClientSession): - _locust_environment = None - - def request(self, method: str, url: str, **kwargs): - if (method == "PUT" and len(url.split("/")) > 3) or method == "PATCH": - real_method = "UPDATE" - else: - real_method = method - name = url.split("/")[1] - start_time = time.time() - result = None - try: - result = super().request(method, url, **kwargs) - except Exception as e: - self.log_exception(real_method, name, start_time, e) - else: - if result is None or result.status_code != 200: - if result is None: - msg = "Got None for Response" - else: - msg = result.text - self.log_exception(real_method, name, start_time, Exception(msg)) - else: - total_time = int((time.time() - start_time) * 1000) - self._locust_environment.events.request_success.fire( - request_type=real_method, - name=name, - response_time=total_time, - response_length=0, - ) - return result - - def log_exception( - self, real_method: str, name: str, start_time: float, e: Exception - ): - total_time = int((time.time() - start_time) * 1000) - self._locust_environment.events.request_failure.fire( - request_type=real_method, - name=name, - response_time=total_time, - exception=e, - response_length=0, - ) - - -class USS(User): +class USS(HttpUser): # Suggested by Locust 1.2.2 API Docs https://docs.locust.io/en/stable/api.html#locust.User.abstract abstract = True isa_dict: dict[str, str] = {} @@ -63,17 +19,27 @@ class USS(User): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) auth_spec = os.environ.get("AUTH_SPEC") - oauth_adapter = auth.make_auth_adapter(auth_spec) if auth_spec else None - self.client = DSSClient(self.host, oauth_adapter) - self.client._locust_environment = self.environment + if not auth_spec: - # logging after creation of client so that we surface the error in the UI - e = Exception("Missing AUTH_SPEC environment variable, please check README") - self.client.log_exception( - "Initialization", "Create DSS Client", time.time(), e + raise Exception( + "Missing AUTH_SPEC environment variable, please check README" ) - # raising exception to not allow things to proceed further - raise e + # This is a load tester its acceptable to have all the scopes required to operate anything. # We are not testing if the scope is incorrect. We are testing if it can handle the load. - self.client.default_scopes = [Scope.Write, Scope.Read] + scopes = [ + f3411_scope.Read, + f3411_scope.Write, + f3548_scope.StrategicCoordination, + "rid.display_provider", + "rid.service_provider", + ] + oauth_adapter = auth.make_auth_adapter(auth_spec) + + def _auth( + prepared_request: requests.PreparedRequest, + ) -> requests.PreparedRequest: + oauth_adapter.add_headers(prepared_request, scopes) + return prepared_request + + self.client.auth = _auth diff --git a/monitoring/loadtest/locust_files/geo_utils.py b/monitoring/loadtest/locust_files/geo_utils.py new file mode 100644 index 0000000000..c2872a38bb --- /dev/null +++ b/monitoring/loadtest/locust_files/geo_utils.py @@ -0,0 +1,151 @@ +import datetime +import math +import random + +import shapely +from utils import format_time + + +def _create_rectangle( + center: shapely.Point, + width: float, + height: float, + rotation_angle_deg: float, +) -> shapely.Polygon: + rect = shapely.geometry.box( + center.x - width / 2, + center.y - height / 2, + center.x + width / 2, + center.y + height / 2, + ) + rotated_rect = shapely.affinity.rotate(rect, rotation_angle_deg) + return rotated_rect + + +def _create_rectangles_on_path( + start_point: shapely.geometry.Point, + bearing_deg: float, + distance: float, + rect_width: float, + rect_height: float, +) -> shapely.MultiPolygon: + bearing_rad = math.radians(bearing_deg) + + # Calculate the cartesian step between each rectangle. + dx = rect_width * math.cos(bearing_rad) + dy = rect_width * math.sin(bearing_rad) + + # Add one so that we always have at least one rect. + num_rects = 1 + math.floor(distance / rect_width) + rectangles = [] + + for _ in range(num_rects): + rectangles.append( + _create_rectangle( + start_point, + rect_width, + rect_height, + bearing_deg, + ) + ) + start_point = shapely.affinity.translate(start_point, dx, dy) + + return shapely.geometry.MultiPolygon(rectangles) + + +def _random_point_within_circle( + center: shapely.Point, + radius: float, +) -> shapely.Point: + # Take sqrt of random to ensure uniform distribution of points throughout + # circular area: + random_radius = radius * math.sqrt(random.random()) + random_angle = 2 * math.pi * random.random() + + x = random_radius * math.cos(random_angle) + center.x + y = random_radius * math.sin(random_angle) + center.y + + return shapely.geometry.Point(x, y) + + +def _meters_to_angle(distance: float) -> float: + # Rough lat/lng angle of one meter at the equator - longitude gets distorted + # towards the poles. + return distance / 111320 + + +def create_random_flight_path( + lat: float, lng: float, radius: int, max_flight_distance_meters: float +) -> shapely.geometry.MultiPolygon: + bearing_deg = random.random() * 360 + distance_meters = random.random() * max_flight_distance_meters + + # Roughly scale all distance measurements to deg lat/lng: + radius_angle = _meters_to_angle(radius) + distance_angle = _meters_to_angle(distance_meters) + rect_width = _meters_to_angle(100) + rect_height = _meters_to_angle(10) + + # Create a random start point within the circle of given radius and center: + center = shapely.geometry.Point(lng, lat) + start_point = _random_point_within_circle(center, radius_angle) + + return _create_rectangles_on_path( + start_point, + bearing_deg, + distance_angle, + rect_width, + rect_height, + ) + + +def create_random_flight_path_volume( + lat: float, lng: float, radius: int, max_flight_distance_meters: int +): + altitude_lower = random.randint(0, 10000) + altitude_upper = altitude_lower + 1 + + start_time = datetime.datetime.now() + end_time = start_time + datetime.timedelta(seconds=10) + + rects = create_random_flight_path(lat, lng, radius, max_flight_distance_meters) + return [ + create_volume(r, altitude_lower, altitude_upper, start_time, end_time) + for r in rects.geoms + ] + + +def create_volume( + polygon: shapely.Polygon, + altitude_lower: float, + altitude_upper: float, + time_start: datetime.datetime, + time_end: datetime.datetime, +): + return { + "volume": { + "outline_polygon": { + "vertices": [ + {"lat": v[1], "lng": v[0]} for v in polygon.exterior.coords[:-1] + ] + }, + "altitude_lower": { + "value": altitude_lower, + "reference": "W84", + "units": "M", + }, + "altitude_upper": { + "value": altitude_upper, + "reference": "W84", + "units": "M", + }, + }, + "time_start": { + "value": format_time(time_start), + "format": "RFC3339", + }, + "time_end": { + "value": format_time(time_end), + "format": "RFC3339", + }, + } diff --git a/monitoring/loadtest/locust_files/utils.py b/monitoring/loadtest/locust_files/utils.py new file mode 100644 index 0000000000..a98f82612d --- /dev/null +++ b/monitoring/loadtest/locust_files/utils.py @@ -0,0 +1,7 @@ +import datetime + +from monitoring.monitorlib import rid_v1 + + +def format_time(time: datetime.datetime) -> str: + return time.astimezone(datetime.UTC).strftime(rid_v1.DATE_FORMAT) diff --git a/monitoring/mock_uss/__init__.py b/monitoring/mock_uss/__init__.py index 6ab5a823a3..e69de29bb2 100644 --- a/monitoring/mock_uss/__init__.py +++ b/monitoring/mock_uss/__init__.py @@ -1,142 +0,0 @@ -import inspect -import os -from collections.abc import Callable -from typing import Any - -# Because mock_uss uses gevent, we need to monkey-patch before anything else is loaded. -# https://www.gevent.org/intro.html#monkey-patching -from gevent import monkey - -monkey.patch_all() - -from loguru import logger # noqa E402 -from werkzeug.middleware.proxy_fix import ProxyFix # noqa E402 - -from monitoring.mock_uss.server import MockUSS # noqa E402 - -SERVICE_GEOAWARENESS = "geoawareness" -SERVICE_RIDSP = "ridsp" -SERVICE_RIDDP = "riddp" -SERVICE_SCDSC = "scdsc" -SERVICE_MESSAGESIGNING = "msgsigning" -SERVICE_TRACER = "tracer" -SERVICE_INTERACTION_LOGGING = "interaction_logging" -SERVICE_VERSIONING = "versioning" -SERVICE_FLIGHT_PLANNING = "flight_planning" - -webapp = MockUSS(__name__) -webapp.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24) -if os.environ.get("MOCK_USS_PROXY_VALUES"): - values = os.environ.get("MOCK_USS_PROXY_VALUES").split(",") - kwargs = {v.split("=")[0].strip(): int(v.split("=")[1]) for v in values} - webapp.wsgi_app = ProxyFix(webapp.wsgi_app, **kwargs) -enabled_services = set() - - -def import_environment_variable( - var_name: str, - required: bool = True, - default: str | None = None, - mutator: Callable[[str], Any] | None = None, -) -> None: - """Import a value from a named environment variable into the webapp configuration. - - Args: - var_name: Environment variable name (key). Also used as the webapp configuration key for that variable. - required: Whether the variable must be specified by the user. If True, a ValueError will be raised if the - variable is not specified by the user. If False, the webapp configuration will not be populated if no - default is provided. If default is specified, the default value is treated as specification by the user. - default: If the variable is not required, then use this value when it is not specified by the user. The default - value should be the string from the environment variable rather than the output of the mutator, if present. - mutator: If specified, apply this function to the string value of the environment variable to obtain the - variable to actually store in the configuration. - """ - if var_name in os.environ: - str_value = os.environ[var_name] - elif default is not None: - str_value = default - elif required: - stack = inspect.stack() - raise ValueError( - f"System cannot proceed because required environment variable '{var_name}' was not found. Required from {stack[1].filename}:{stack[1].lineno}" - ) - else: - str_value = None - - if str_value is not None: - webapp.config[var_name] = str_value if mutator is None else mutator(str_value) - - -def require_config_value(config_key: str) -> None: - if config_key not in webapp.config: - stack = inspect.stack() - raise ValueError( - f"System cannot proceed because required configuration key '{config_key}' was not found. Required from {stack[1].filename}:{stack[1].lineno}" - ) - - -from monitoring.mock_uss import config # noqa E402 -from monitoring.mock_uss import logging as logging # noqa E402 -from monitoring.mock_uss import routes as basic_routes # noqa F401,F402 - -if SERVICE_GEOAWARENESS in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_GEOAWARENESS) - from monitoring.mock_uss import geoawareness as geoawareness - from monitoring.mock_uss.geoawareness import ( - routes as geoawareness_routes, # noqa F401 - ) - -if SERVICE_RIDSP in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_RIDSP) - from monitoring.mock_uss import ridsp as ridsp - from monitoring.mock_uss.ridsp import routes as ridsp_routes # noqa F401 - -if SERVICE_RIDDP in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_RIDDP) - from monitoring.mock_uss import riddp as riddp - from monitoring.mock_uss.riddp import routes as riddp_routes # noqa F401 - -if SERVICE_SCDSC in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_SCDSC) - from monitoring.mock_uss.f3548v21 import routes_scd as routes_scd - from monitoring.mock_uss.scd_injection import ( - routes as scd_injection_routes, # noqa F401 - ) - -if SERVICE_MESSAGESIGNING in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_MESSAGESIGNING) - from monitoring.mock_uss import msgsigning as msgsigning # noqa F401 - from monitoring.mock_uss.msgsigning import routes as msgsigning_routes # noqa F401 - -if SERVICE_INTERACTION_LOGGING in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_INTERACTION_LOGGING) - from monitoring.mock_uss.interaction_logging import logger as interactions_logger # noqa F401 - from monitoring.mock_uss.interaction_logging import ( - routes_interactions_log as routes_interactions_log, - ) - - logger.info("Interaction logging enabled") -else: - logger.info("Interaction logging disabled") - -if SERVICE_TRACER in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_TRACER) - from monitoring.mock_uss import tracer as tracer - from monitoring.mock_uss.tracer import routes as tracer_routes # noqa F401 - -if SERVICE_VERSIONING in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_VERSIONING) - from monitoring.mock_uss.versioning import routes as versioning_routes # noqa F401 - -if SERVICE_FLIGHT_PLANNING in webapp.config[config.KEY_SERVICES]: - enabled_services.add(SERVICE_FLIGHT_PLANNING) - from monitoring.mock_uss.flight_planning import routes as flight_planning_routes # noqa F401 - -msg = ( - "################################################################################\n" - + "################################ Configuration ################################\n" - + "\n".join(f"## {key}: {webapp.config[key]}" for key in webapp.config) - + "\n" - + "################################################################################" -) -logger.info("Configuration:\n" + msg) diff --git a/monitoring/mock_uss/app.py b/monitoring/mock_uss/app.py new file mode 100644 index 0000000000..8e7f32379c --- /dev/null +++ b/monitoring/mock_uss/app.py @@ -0,0 +1,142 @@ +import inspect +import os +from collections.abc import Callable +from typing import Any + +# Because mock_uss uses gevent, we need to monkey-patch before anything else is loaded. +# https://www.gevent.org/intro.html#monkey-patching +from gevent import monkey + +monkey.patch_all() + +from loguru import logger # noqa E402 +from werkzeug.middleware.proxy_fix import ProxyFix # noqa E402 + +from monitoring.mock_uss.server import MockUSS # noqa E402 + +SERVICE_GEOAWARENESS = "geoawareness" +SERVICE_RIDSP = "ridsp" +SERVICE_RIDDP = "riddp" +SERVICE_SCDSC = "scdsc" +SERVICE_MESSAGESIGNING = "msgsigning" +SERVICE_TRACER = "tracer" +SERVICE_INTERACTION_LOGGING = "interaction_logging" +SERVICE_VERSIONING = "versioning" +SERVICE_FLIGHT_PLANNING = "flight_planning" + +webapp = MockUSS(__name__) +webapp.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24) +if os.environ.get("MOCK_USS_PROXY_VALUES"): + values = os.environ.get("MOCK_USS_PROXY_VALUES", "").split(",") + kwargs = {v.split("=")[0].strip(): int(v.split("=")[1]) for v in values} + webapp.wsgi_app = ProxyFix(webapp.wsgi_app, **kwargs) +enabled_services = set() + + +def import_environment_variable( + var_name: str, + required: bool = True, + default: str | None = None, + mutator: Callable[[str], Any] | None = None, +) -> None: + """Import a value from a named environment variable into the webapp configuration. + + Args: + var_name: Environment variable name (key). Also used as the webapp configuration key for that variable. + required: Whether the variable must be specified by the user. If True, a ValueError will be raised if the + variable is not specified by the user. If False, the webapp configuration will not be populated if no + default is provided. If default is specified, the default value is treated as specification by the user. + default: If the variable is not required, then use this value when it is not specified by the user. The default + value should be the string from the environment variable rather than the output of the mutator, if present. + mutator: If specified, apply this function to the string value of the environment variable to obtain the + variable to actually store in the configuration. + """ + if var_name in os.environ: + str_value = os.environ[var_name] + elif default is not None: + str_value = default + elif required: + stack = inspect.stack() + raise ValueError( + f"System cannot proceed because required environment variable '{var_name}' was not found. Required from {stack[1].filename}:{stack[1].lineno}" + ) + else: + str_value = None + + if str_value is not None: + webapp.config[var_name] = str_value if mutator is None else mutator(str_value) + + +def require_config_value(config_key: str) -> None: + if config_key not in webapp.config: + stack = inspect.stack() + raise ValueError( + f"System cannot proceed because required configuration key '{config_key}' was not found. Required from {stack[1].filename}:{stack[1].lineno}" + ) + + +from monitoring.mock_uss import config # noqa E402 +from monitoring.mock_uss import logging as logging # noqa E402 +from monitoring.mock_uss import routes as basic_routes # noqa F401,F402 + +if SERVICE_GEOAWARENESS in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_GEOAWARENESS) + from monitoring.mock_uss import geoawareness as geoawareness + from monitoring.mock_uss.geoawareness import ( + routes as geoawareness_routes, # noqa F401 + ) + +if SERVICE_RIDSP in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_RIDSP) + from monitoring.mock_uss import ridsp as ridsp + from monitoring.mock_uss.ridsp import routes as ridsp_routes # noqa F401 + +if SERVICE_RIDDP in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_RIDDP) + from monitoring.mock_uss import riddp as riddp + from monitoring.mock_uss.riddp import routes as riddp_routes # noqa F401 + +if SERVICE_SCDSC in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_SCDSC) + from monitoring.mock_uss.f3548v21 import routes_scd as routes_scd + from monitoring.mock_uss.scd_injection import ( + routes as scd_injection_routes, # noqa F401 + ) + +if SERVICE_MESSAGESIGNING in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_MESSAGESIGNING) + from monitoring.mock_uss import msgsigning as msgsigning # noqa F401 + from monitoring.mock_uss.msgsigning import routes as msgsigning_routes # noqa F401 + +if SERVICE_INTERACTION_LOGGING in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_INTERACTION_LOGGING) + from monitoring.mock_uss.interaction_logging import logger as interactions_logger # noqa F401 + from monitoring.mock_uss.interaction_logging import ( + routes_interactions_log as routes_interactions_log, + ) + + logger.info("Interaction logging enabled") +else: + logger.info("Interaction logging disabled") + +if SERVICE_TRACER in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_TRACER) + from monitoring.mock_uss import tracer as tracer + from monitoring.mock_uss.tracer import routes as tracer_routes # noqa F401 + +if SERVICE_VERSIONING in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_VERSIONING) + from monitoring.mock_uss.versioning import routes as versioning_routes # noqa F401 + +if SERVICE_FLIGHT_PLANNING in webapp.config[config.KEY_SERVICES]: + enabled_services.add(SERVICE_FLIGHT_PLANNING) + from monitoring.mock_uss.flight_planning import routes as flight_planning_routes # noqa F401 + +msg = ( + "################################################################################\n" + + "################################ Configuration ################################\n" + + "\n".join(f"## {key}: {webapp.config[key]}" for key in webapp.config) + + "\n" + + "################################################################################" +) +logger.info("Configuration:\n" + msg) diff --git a/monitoring/mock_uss/auth.py b/monitoring/mock_uss/auth.py index 8a076a842b..9ccd51d4c9 100644 --- a/monitoring/mock_uss/auth.py +++ b/monitoring/mock_uss/auth.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.monitorlib import auth_validation from . import config diff --git a/monitoring/mock_uss/config.py b/monitoring/mock_uss/config.py index f394583da4..cc18d5592b 100644 --- a/monitoring/mock_uss/config.py +++ b/monitoring/mock_uss/config.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import import_environment_variable +from monitoring.mock_uss.app import import_environment_variable from monitoring.monitorlib import auth_validation KEY_TOKEN_PUBLIC_KEY = "MOCK_USS_PUBLIC_KEY" diff --git a/monitoring/mock_uss/database.py b/monitoring/mock_uss/database.py index 575e27360f..60945ed8eb 100644 --- a/monitoring/mock_uss/database.py +++ b/monitoring/mock_uss/database.py @@ -1,14 +1,19 @@ import json -from implicitdict import ImplicitDict, StringBasedDateTime, StringBasedTimeDelta +from implicitdict import ( + ImplicitDict, + Optional, + StringBasedDateTime, + StringBasedTimeDelta, +) from monitoring.monitorlib.errors import stacktrace_string from monitoring.monitorlib.multiprocessing import SynchronizedValue class PeriodicTaskStatus(ImplicitDict): - last_execution_time: StringBasedDateTime | None = None - period: StringBasedTimeDelta | None = None + last_execution_time: Optional[StringBasedDateTime] = None + period: Optional[StringBasedTimeDelta] = None executing: bool = False @@ -43,11 +48,11 @@ class Database(ImplicitDict): periodic_tasks: dict[str, PeriodicTaskStatus] """Tasks to perform periodically, by name""" - most_recent_periodic_check: StringBasedDateTime | None + most_recent_periodic_check: Optional[StringBasedDateTime] """Timestamp of most recent time periodic task loop iterated""" -db = SynchronizedValue( +db = SynchronizedValue[Database]( Database( one_time_tasks=[], task_errors=[], diff --git a/monitoring/mock_uss/docker-compose.yaml b/monitoring/mock_uss/docker-compose.yaml index 3ac506963f..3cd3a713c9 100644 --- a/monitoring/mock_uss/docker-compose.yaml +++ b/monitoring/mock_uss/docker-compose.yaml @@ -12,7 +12,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss1) - - MOCK_USS_DSS_URL=http://dss.uss1.localutm + - MOCK_USS_DSS_URL=http://dss1.uss1.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=scdsc.uss1.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://scdsc.uss1.localutm @@ -34,6 +34,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - scd @@ -45,7 +47,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss2) - - MOCK_USS_DSS_URL=http://dss.uss2.localutm + - MOCK_USS_DSS_URL=http://dss1.uss2.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=scdsc.uss2.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://scdsc.uss2.localutm @@ -63,6 +65,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - scd @@ -89,6 +93,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - geoawareness @@ -100,7 +106,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss1) - - MOCK_USS_DSS_URL=http://dss.uss1.localutm + - MOCK_USS_DSS_URL=http://dss1.uss1.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=v22a.ridsp.uss1.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://v22a.ridsp.uss1.localutm @@ -121,6 +127,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - rid @@ -132,7 +140,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss1) - - MOCK_USS_DSS_URL=http://dss.uss1.localutm + - MOCK_USS_DSS_URL=http://dss1.uss1.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=v22a.riddp.uss1.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://v22a.riddp.uss1.localutm @@ -153,6 +161,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - rid @@ -164,7 +174,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss2) - - MOCK_USS_DSS_URL=http://dss.uss2.localutm + - MOCK_USS_DSS_URL=http://dss1.uss2.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=v19.ridsp.uss2.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://v19.ridsp.uss2.localutm @@ -185,6 +195,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - rid_v19 @@ -196,7 +208,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss3) - - MOCK_USS_DSS_URL=http://dss.uss1.localutm + - MOCK_USS_DSS_URL=http://dss1.uss1.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=v19.riddp.uss3.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://v19.riddp.uss3.localutm @@ -217,6 +229,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - rid_v19 @@ -229,7 +243,7 @@ services: command: ./start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss4) - - MOCK_USS_DSS_URL=http://dss.uss1.localutm + - MOCK_USS_DSS_URL=http://dss1.uss1.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=tracer.uss4.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://tracer.uss4.localutm @@ -253,6 +267,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - scd @@ -266,7 +282,7 @@ services: command: mock_uss/start.sh environment: - MOCK_USS_AUTH_SPEC=DummyOAuth(http://oauth.authority.localutm:8085/token,uss6) - - MOCK_USS_DSS_URL=http://dss.uss1.localutm + - MOCK_USS_DSS_URL=http://dss1.uss1.localutm - MOCK_USS_PUBLIC_KEY=/var/test-certs/auth2.pem - MOCK_USS_TOKEN_AUDIENCE=scdsc.log.uss6.localutm,localhost,host.docker.internal - MOCK_USS_BASE_URL=http://scdsc.log.uss6.localutm @@ -287,6 +303,8 @@ services: - interop_ecosystem_network extra_hosts: - host.docker.internal:host-gateway + healthcheck: + start_period: 30s profiles: - '' # starts when no profile is provided - scd @@ -313,4 +331,3 @@ services: networks: interop_ecosystem_network: external: true - diff --git a/monitoring/mock_uss/dynamic_configuration/configuration.py b/monitoring/mock_uss/dynamic_configuration/configuration.py index e444c7fe76..17268a8197 100644 --- a/monitoring/mock_uss/dynamic_configuration/configuration.py +++ b/monitoring/mock_uss/dynamic_configuration/configuration.py @@ -2,7 +2,7 @@ from implicitdict import ImplicitDict -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.config import KEY_BEHAVIOR_LOCALITY from monitoring.monitorlib.locality import Locality, LocalityCode from monitoring.monitorlib.multiprocessing import SynchronizedValue @@ -14,7 +14,7 @@ class DynamicConfiguration(ImplicitDict): locale: LocalityCode -db = SynchronizedValue( +db = SynchronizedValue[DynamicConfiguration]( DynamicConfiguration(locale=LocalityCode(webapp.config[KEY_BEHAVIOR_LOCALITY])), decoder=lambda b: ImplicitDict.parse( json.loads(b.decode("utf-8")), DynamicConfiguration @@ -24,6 +24,6 @@ class DynamicConfiguration(ImplicitDict): def get_locality() -> Locality: - with db as tx: - code = tx.locale + with db.transact() as tx: + code = tx.value.locale return Locality.from_locale(code) diff --git a/monitoring/mock_uss/dynamic_configuration/routes.py b/monitoring/mock_uss/dynamic_configuration/routes.py index 80e92e756e..b41e0a32f1 100644 --- a/monitoring/mock_uss/dynamic_configuration/routes.py +++ b/monitoring/mock_uss/dynamic_configuration/routes.py @@ -1,7 +1,7 @@ import flask from implicitdict import ImplicitDict -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import MOCK_USS_CONFIG_SCOPE, requires_scope from monitoring.mock_uss.dynamic_configuration.configuration import db, get_locality from monitoring.monitorlib.clients.mock_uss.locality import ( @@ -12,7 +12,7 @@ @webapp.route("/configuration/locality", methods=["GET"]) -def locality_get() -> tuple[str, int]: +def locality_get() -> flask.Response: return flask.jsonify( GetLocalityResponse(locality_code=get_locality().locality_code()) ) @@ -20,7 +20,7 @@ def locality_get() -> tuple[str, int]: @webapp.route("/configuration/locality", methods=["PUT"]) @requires_scope(MOCK_USS_CONFIG_SCOPE) # TODO: use separate public key for this -def locality_set() -> tuple[str, int]: +def locality_set() -> tuple[str, int] | flask.Response: """Set the locality of the mock_uss.""" try: json = flask.request.json @@ -38,8 +38,8 @@ def locality_set() -> tuple[str, int]: msg = f"Invalid locality_code: {str(e)}" return msg, 400 - with db as tx: - tx.locale = req.locality_code + with db.transact() as tx: + tx.value.locale = req.locality_code return flask.jsonify( GetLocalityResponse(locality_code=get_locality().locality_code()) diff --git a/monitoring/mock_uss/f3548v21/__init__.py b/monitoring/mock_uss/f3548v21/__init__.py index 1aae3c3bcc..6d5744dbc8 100644 --- a/monitoring/mock_uss/f3548v21/__init__.py +++ b/monitoring/mock_uss/f3548v21/__init__.py @@ -1,12 +1,12 @@ -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.config import KEY_AUTH_SPEC, KEY_DSS_URL from monitoring.monitorlib import auth -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import utm_client_session_factory require_config_value(KEY_DSS_URL) require_config_value(KEY_AUTH_SPEC) -utm_client = UTMClientSession( +utm_client = utm_client_session_factory.get_session( webapp.config[KEY_DSS_URL], auth.make_auth_adapter(webapp.config[KEY_AUTH_SPEC]), ) diff --git a/monitoring/mock_uss/f3548v21/flight_planning.py b/monitoring/mock_uss/f3548v21/flight_planning.py index 2b35a6afd1..451e0fc89b 100644 --- a/monitoring/mock_uss/f3548v21/flight_planning.py +++ b/monitoring/mock_uss/f3548v21/flight_planning.py @@ -8,10 +8,10 @@ from uas_standards.astm.f3548.v21.constants import OiMaxPlanHorizonDays, OiMaxVertices from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.config import KEY_BASE_URL from monitoring.mock_uss.f3548v21 import utm_client -from monitoring.mock_uss.flights.database import FlightRecord, db +from monitoring.mock_uss.flights.database import Database, FlightRecord, db from monitoring.monitorlib.clients import scd as scd_client from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo from monitoring.monitorlib.fetch import QueryError @@ -104,9 +104,10 @@ def conflicts_with_flightrecords( ) for other_flight in flights: - if not other_flight: + if not other_flight or not other_flight.op_intent: continue + # TODO(mock_uss_flight_id): Use flight ID that is independent of op_intent if other_flight.op_intent.reference.id == op_intent.reference.id: # Same flight continue @@ -158,6 +159,7 @@ def log(msg): for op_intent in op_intents: if ( existing_flight + and existing_flight.op_intent and existing_flight.op_intent.reference.id == op_intent.reference.id ): log( @@ -189,11 +191,12 @@ def log(msg): modifying_activated = ( existing_flight + and existing_flight.op_intent and existing_flight.op_intent.reference.state == scd_api.OperationalIntentState.Activated and op_intent.reference.state == scd_api.OperationalIntentState.Activated ) - if modifying_activated: + if modifying_activated and existing_flight and existing_flight.op_intent: preexisting_conflict = Volume4DCollection.from_interuss_scd_api( existing_flight.op_intent.details.volumes ).intersects_vol4s(v2) @@ -216,22 +219,24 @@ def op_intent_transition_valid( transition_to: scd_api.OperationalIntentState | None, ) -> bool: valid_states = { + None, scd_api.OperationalIntentState.Accepted, scd_api.OperationalIntentState.Activated, scd_api.OperationalIntentState.Nonconforming, scd_api.OperationalIntentState.Contingent, } - if transition_from is not None and transition_from not in valid_states: + if transition_from not in valid_states: raise ValueError( f"Cannot transition from state {transition_from} as it is an invalid operational intent state" ) - if transition_to is not None and transition_to not in valid_states: + if transition_to not in valid_states: raise ValueError( f"Cannot transition to state {transition_to} as it is an invalid operational intent state" ) if transition_from is None: return transition_to in { + None, scd_api.OperationalIntentState.Accepted, scd_api.OperationalIntentState.Activated, } @@ -385,6 +390,10 @@ def op_intent_from_flightinfo( def op_intent_from_flightrecord( flight: FlightRecord, method: str ) -> f3548_v21.OperationalIntent: + if not flight.op_intent: + raise RuntimeError( + "op_intent_from_flightrecord was called with a FlightRecord containing no op intent" + ) ref = flight.op_intent.reference details = f3548_v21.OperationalIntentDetails( volumes=flight.op_intent.details.volumes, @@ -423,9 +432,13 @@ def query_operational_intents( op_intent_refs = scd_client.query_operational_intent_references( utm_client, area_of_interest ) - tx = db.value + dbcontent: Database = db.value get_details_for = [] - own_flights = {f.op_intent.reference.id: f for f in tx.flights.values() if f} + own_flights = { + f.op_intent.reference.id: f + for f in dbcontent.flights.values() + if f and f.op_intent + } result = [] for op_intent_ref in op_intent_refs: if op_intent_ref.id in own_flights: @@ -434,12 +447,12 @@ def query_operational_intents( op_intent_from_flightrecord(own_flights[op_intent_ref.id], "GET") ) elif ( - op_intent_ref.id in tx.cached_operations - and tx.cached_operations[op_intent_ref.id].reference.version + op_intent_ref.id in dbcontent.cached_operations + and dbcontent.cached_operations[op_intent_ref.id].reference.version == op_intent_ref.version ): # We have a current version of this op intent cached - result.append(tx.cached_operations[op_intent_ref.id]) + result.append(dbcontent.cached_operations[op_intent_ref.id]) else: # We need to get the details for this op intent get_details_for.append(op_intent_ref) @@ -464,9 +477,9 @@ def query_operational_intents( raise e result.extend(updated_op_intents) - with db as tx: + with db.transact() as tx: for op_intent in updated_op_intents: - tx.cached_operations[op_intent.reference.id] = op_intent + tx.value.cached_operations[op_intent.reference.id] = op_intent return result @@ -537,57 +550,63 @@ def check_op_intent( # Check the transition is valid state_transition_from = ( f3548_v21.OperationalIntentState(existing_flight.op_intent.reference.state) - if existing_flight + if existing_flight and existing_flight.op_intent else None ) state_transition_to = f3548_v21.OperationalIntentState( - new_flight.op_intent.reference.state + new_flight.op_intent.reference.state if new_flight.op_intent else None ) if not op_intent_transition_valid(state_transition_from, state_transition_to): raise PlanningError( f"Operational intent state transition from {state_transition_from} to {state_transition_to} is invalid" ) - # Check the priority is allowed in the locality - priority = priority_of(new_flight.op_intent.details) - if ( - priority > locality.highest_priority() - or priority <= locality.lowest_bound_priority() - ): - raise PlanningError( - f"Operational intent priority {priority} is outside the bounds of the locality priority range (]{locality.lowest_bound_priority()},{locality.highest_priority()}])" - ) + if new_flight.op_intent: + # Check the priority is allowed in the locality + priority = priority_of(new_flight.op_intent.details) + if ( + priority > locality.highest_priority() + or priority <= locality.lowest_bound_priority() + ): + raise PlanningError( + f"Operational intent priority {priority} is outside the bounds of the locality priority range (]{locality.lowest_bound_priority()},{locality.highest_priority()}])" + ) - if new_flight.op_intent.reference.state in ( - f3548_v21.OperationalIntentState.Accepted, - f3548_v21.OperationalIntentState.Activated, - ): - # Check for intersections if the flight is nominal + if new_flight.op_intent.reference.state in ( + f3548_v21.OperationalIntentState.Accepted, + f3548_v21.OperationalIntentState.Activated, + ): + # Check for intersections if the flight is nominal - # Check for operational intents in the DSS - log("Obtaining latest operational intent information") - v1 = Volume4DCollection.from_interuss_scd_api( - new_flight.op_intent.details.volumes - + new_flight.op_intent.details.off_nominal_volumes - ) - vol4 = v1.bounding_volume.to_f3548v21() - op_intents = query_operational_intents(locality, vol4) + # Check for operational intents in the DSS + log("Obtaining latest operational intent information") + v1 = Volume4DCollection.from_f3548v21( + (new_flight.op_intent.details.volumes or []) + + (new_flight.op_intent.details.off_nominal_volumes or []) + ) + vol4 = v1.bounding_volume.to_f3548v21() + op_intents = query_operational_intents(locality, vol4) - # Check for intersections - log( - f"Checking for intersections with {', '.join(op_intent.reference.id for op_intent in op_intents)}" - ) - has_conflicts = check_for_conflicts( - new_flight.op_intent, existing_flight, op_intents, locality, log - ) + # Check for intersections + log( + f"Checking for intersections with {', '.join(op_intent.reference.id for op_intent in op_intents)}" + ) + has_conflicts = check_for_conflicts( + new_flight.op_intent, existing_flight, op_intents, locality, log + ) + + key = [ + f3548_v21.EntityOVN(op.reference.ovn) + for op in op_intents + if op.reference.ovn is not None + ] + else: + # Flight is not nominal and therefore doesn't need to check intersections + key = [] + has_conflicts = False - key = [ - f3548_v21.EntityOVN(op.reference.ovn) - for op in op_intents - if op.reference.ovn is not None - ] else: - # Flight is not nominal and therefore doesn't need to check intersections + # Flight does not have an op intent key = [] has_conflicts = False @@ -611,6 +630,10 @@ def share_op_intent( * ConnectionError * requests.exceptions.ConnectionError """ + if not new_flight.op_intent: + raise RuntimeError( + "share_op_intent called with new_flight that is missing an op_intent" + ) # Create operational intent in DSS log("Sharing operational intent with DSS") base_url = new_flight.op_intent.reference.uss_base_url @@ -624,7 +647,7 @@ def share_op_intent( uss_base_url=base_url ), ) - if existing_flight: + if existing_flight and existing_flight.op_intent: id = existing_flight.op_intent.reference.id log(f"Updating existing operational intent {id} in DSS") result = scd_client.update_operational_intent_reference( diff --git a/monitoring/mock_uss/f3548v21/routes_scd.py b/monitoring/mock_uss/f3548v21/routes_scd.py index 541a92516b..24da0d4cae 100644 --- a/monitoring/mock_uss/f3548v21/routes_scd.py +++ b/monitoring/mock_uss/f3548v21/routes_scd.py @@ -13,22 +13,25 @@ PutOperationalIntentDetailsParameters, ) -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.f3548v21.flight_planning import ( conflicts_with_flightrecords, op_intent_from_flightrecord, ) from monitoring.mock_uss.flights.database import FlightRecord, db +from monitoring.mock_uss.logging import query_type from monitoring.mock_uss.user_interactions.notifications import ( UserNotification, UserNotificationType, ) from monitoring.monitorlib import scd from monitoring.monitorlib.clients.flight_planning.planning import Conflict +from monitoring.monitorlib.fetch import QueryType @webapp.route("/mock/scd/uss/v1/operational_intents/", methods=["GET"]) +@query_type(QueryType.F3548v21USSGetOperationalIntentDetails) @requires_scope(scd.SCOPE_SC) def scdsc_get_operational_intent_details(entityid: str): """Implements getOperationalIntentDetails in ASTM SCD API.""" @@ -37,12 +40,12 @@ def scdsc_get_operational_intent_details(entityid: str): tx = db.value flight = None for f in tx.flights.values(): - if f and f.op_intent.reference.id == entityid: + if f and f.op_intent and f.op_intent.reference.id == entityid: flight = f break # If requested operational intent doesn't exist, return 404 - if flight is None: + if flight is None or flight.op_intent is None: return ( flask.jsonify( ErrorResponse( @@ -62,6 +65,7 @@ def scdsc_get_operational_intent_details(entityid: str): @webapp.route( "/mock/scd/uss/v1/operational_intents//telemetry", methods=["GET"] ) +@query_type(QueryType.F3548v21USSGetOperationalIntentTelemetry) @requires_scope(scd.SCOPE_CM_SA) def scdsc_get_operational_intent_telemetry(entityid: str): """Implements getOperationalIntentTelemetry in ASTM SCD API.""" @@ -70,7 +74,7 @@ def scdsc_get_operational_intent_telemetry(entityid: str): tx = db.value flight: FlightRecord | None = None for f in tx.flights.values(): - if f and f.op_intent.reference.id == entityid: + if f and f.op_intent and f.op_intent.reference.id == entityid: flight = f break @@ -85,7 +89,7 @@ def scdsc_get_operational_intent_telemetry(entityid: str): 404, ) - elif flight.op_intent.reference.state not in { + elif flight.op_intent and flight.op_intent.reference.state not in { OperationalIntentState.Contingent, OperationalIntentState.Nonconforming, }: @@ -110,6 +114,7 @@ def scdsc_get_operational_intent_telemetry(entityid: str): @webapp.route("/mock/scd/uss/v1/operational_intents", methods=["POST"]) +@query_type(QueryType.F3548v21USSNotifyOperationalIntentDetailsChanged) @requires_scope(scd.SCOPE_SC) def scdsc_notify_operational_intent_details_changed(): """Implements notifyOperationalIntentDetailsChanged in ASTM SCD API.""" @@ -127,12 +132,12 @@ def scdsc_notify_operational_intent_details_changed(): if "operational_intent" in op_intent_data and op_intent_data.operational_intent: # An op intent is being created or modified; check if it conflicts with any flights we're managing - with db as tx: + with db.transact() as tx: if conflicts_with_flightrecords( - op_intent_data.operational_intent, tx.flights.values() + op_intent_data.operational_intent, list(tx.value.flights.values()) ): # Virtually notify user that another op intent conflicts with their flight - tx.flight_planning_notifications.append( + tx.value.flight_planning_notifications.append( UserNotification( type=UserNotificationType.DetectedConflict, observed_at=StringBasedDateTime(arrow.utcnow().datetime), @@ -146,6 +151,7 @@ def scdsc_notify_operational_intent_details_changed(): @webapp.route("/mock/scd/uss/v1/reports", methods=["POST"]) +@query_type(QueryType.F3548v21USSMakeUssReport) @requires_scope( [scd.SCOPE_SC, scd.SCOPE_CP, scd.SCOPE_CM, scd.SCOPE_CM_SA, scd.SCOPE_AA] ) diff --git a/monitoring/mock_uss/flight_planning/routes.py b/monitoring/mock_uss/flight_planning/routes.py index ea24f5fee0..e348c82b3a 100644 --- a/monitoring/mock_uss/flight_planning/routes.py +++ b/monitoring/mock_uss/flight_planning/routes.py @@ -1,5 +1,4 @@ import os -import uuid from datetime import timedelta import arrow @@ -9,11 +8,10 @@ from uas_standards.interuss.automated_testing.flight_planning.v1 import api from uas_standards.interuss.automated_testing.flight_planning.v1.constants import Scope -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.config import KEY_BASE_URL -from monitoring.mock_uss.f3548v21.flight_planning import op_intent_from_flightinfo -from monitoring.mock_uss.flights.database import FlightRecord, db +from monitoring.mock_uss.flights.database import MockUSSFlightID, db from monitoring.mock_uss.scd_injection.routes_injection import ( clear_area, delete_flight, @@ -54,7 +52,9 @@ def injection_status() -> tuple[dict, int]: @webapp.route("/flight_planning/v1/flight_plans/", methods=["PUT"]) @requires_scope(Scope.Plan) @idempotent_request() -def flight_planning_v1_upsert_flight_plan(flight_plan_id: str) -> tuple[str, int]: +def flight_planning_v1_upsert_flight_plan( + flight_plan_id: str, +) -> tuple[str | flask.Response, int]: def log(msg: str) -> None: logger.debug(f"[upsert_plan/{os.getpid()}:{flight_plan_id}] {msg}") @@ -70,40 +70,38 @@ def log(msg: str) -> None: msg = f"Create flight {flight_plan_id} unable to parse JSON: {e}" return msg, 400 - existing_flight = lock_flight(flight_plan_id, log) + flight_id = MockUSSFlightID(flight_plan_id) + existing_flight = lock_flight(flight_id, log) try: info = FlightInfo.from_flight_plan(req_body.flight_plan) - op_intent = op_intent_from_flightinfo(info, str(uuid.uuid4())) - new_flight = FlightRecord( - flight_info=info, - op_intent=op_intent, - mod_op_sharing_behavior=( - req_body.behavior - if "behavior" in req_body and req_body.behavior - else None - ), + inject_resp = inject_flight( + flight_id, + info, + req_body.behavior if "behavior" in req_body and req_body.behavior else None, + existing_flight, ) - inject_resp = inject_flight(flight_plan_id, new_flight, existing_flight) - finally: - release_flight_lock(flight_plan_id, log) + release_flight_lock(flight_id, log) resp = api.UpsertFlightPlanResponse( planning_result=api.PlanningActivityResult(inject_resp.activity_result), flight_plan_status=api.FlightPlanStatus(inject_resp.flight_plan_status), ) + if "as_planned" in inject_resp and inject_resp.as_planned: + resp.as_planned = inject_resp.as_planned.to_flight_plan() for k, v in inject_resp.items(): - if k not in {"planning_result", "flight_plan_status", "has_conflict"}: + if k not in {"planning_result", "flight_plan_status", "as_planned"}: resp[k] = v return flask.jsonify(resp), 200 @webapp.route("/flight_planning/v1/flight_plans/", methods=["DELETE"]) @requires_scope(Scope.Plan) -def flight_planning_v1_delete_flight(flight_plan_id: str) -> tuple[str, int]: +def flight_planning_v1_delete_flight(flight_plan_id: str) -> tuple[flask.Response, int]: """Implements flight deletion in SCD automated testing injection API.""" - del_resp, status_code = delete_flight(flight_plan_id) + flight_id = MockUSSFlightID(flight_plan_id) + del_resp, status_code = delete_flight(flight_id) resp = api.DeleteFlightPlanResponse( planning_result=api.PlanningActivityResult(del_resp.activity_result), @@ -174,6 +172,10 @@ def flight_planning_v1_user_notifications() -> tuple[str, int]: 400, ) + before = min( + arrow.utcnow(), before + ) # Ensure we don't return notifications from the future + final_list: list[api.UserNotification] = [] for user_notification in db.value.flight_planning_notifications: diff --git a/monitoring/mock_uss/flights/database.py b/monitoring/mock_uss/flights/database.py index 0a8bbc42e6..4d81736f9b 100644 --- a/monitoring/mock_uss/flights/database.py +++ b/monitoring/mock_uss/flights/database.py @@ -1,9 +1,11 @@ import json from datetime import timedelta -from implicitdict import ImplicitDict -from uas_standards.astm.f3548.v21.api import OperationalIntent +import arrow +from implicitdict import ImplicitDict, Optional +from uas_standards.astm.f3548.v21.api import EntityID, OperationalIntent +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.user_interactions.notifications import UserNotification from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo from monitoring.monitorlib.clients.mock_uss.mock_uss_scd_injection_api import ( @@ -13,27 +15,101 @@ DEADLOCK_TIMEOUT = timedelta(seconds=5) +NOTIFICATIONS_LIMIT = timedelta(hours=1) +"""Automatically remove notifications after this long beyond their observation time.""" + +DB_CLEANUP_INTERVAL = timedelta(hours=1) +"""Clean database this often.""" + +FLIGHTS_LIMIT = timedelta(hours=1) +"""Automatically remove flights we manage after this long beyond their end time.""" + +OPERATIONAL_INTENTS_LIMIT = timedelta(hours=1) +"""Automatically remove cached operational intents obtained from others after this long beyond their end time.""" + + +class MockUSSFlightID(str): + """The identity of a flight, as tracked/managed by mock_uss""" + + pass + class FlightRecord(ImplicitDict): - """Representation of a flight in a USS""" + """Representation of a flight in mock_uss""" + # TODO(mock_uss_flight_id): Add flight ID that is independent of op_intent flight_info: FlightInfo - op_intent: OperationalIntent - mod_op_sharing_behavior: MockUssFlightBehavior | None = None + op_intent: Optional[OperationalIntent] = None + mod_op_sharing_behavior: Optional[MockUssFlightBehavior] = None locked: bool = False class Database(ImplicitDict): """Simple in-memory pseudo-database tracking the state of the mock system""" - flights: dict[str, FlightRecord | None] = {} - cached_operations: dict[str, OperationalIntent] = {} + flights: dict[MockUSSFlightID, FlightRecord | None] = {} + """Collection of flights managed by mock_uss, referenced by flight ID. + + When the value is None, this indicates that the flight of the specified ID is currently in the process of being + created and should be treated as locked.""" + + cached_operations: dict[EntityID, OperationalIntent] = {} flight_planning_notifications: list[UserNotification] = [] """List of notifications sent during flight planning operations""" + def cleanup_notifications(self): + self.flight_planning_notifications = [ + notif + for notif in self.flight_planning_notifications + if notif.observed_at.datetime + NOTIFICATIONS_LIMIT + > arrow.utcnow().datetime + ] + + def cleanup_flights(self): + to_cleanup = [] + + for flight_id, flight in self.flights.items(): + if ( + flight + and not flight.locked + and flight.op_intent + and flight.op_intent.reference.time_end.value.datetime + FLIGHTS_LIMIT + < arrow.utcnow().datetime + ): + to_cleanup.append(flight_id) -db = SynchronizedValue( + for flight_id in to_cleanup: + del self.flights[flight_id] + + def cleanup_operational_intents(self): + to_cleanup = [] + + for op_id, op_intent in self.cached_operations.items(): + if ( + op_intent.reference.time_end.value.datetime + OPERATIONAL_INTENTS_LIMIT + < arrow.utcnow().datetime + ): + to_cleanup.append(op_id) + + for op_id in to_cleanup: + del self.cached_operations[op_id] + + +db = SynchronizedValue[Database]( Database(), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), Database), ) + +TASK_DATABASE_CLEANUP = "flights database cleanup" + + +@webapp.periodic_task(TASK_DATABASE_CLEANUP) +def database_cleanup() -> None: + with db.transact() as tx: + tx.value.cleanup_notifications() + tx.value.cleanup_flights() + tx.value.cleanup_operational_intents() + + +webapp.set_task_period(TASK_DATABASE_CLEANUP, DB_CLEANUP_INTERVAL) diff --git a/monitoring/mock_uss/flights/planning.py b/monitoring/mock_uss/flights/planning.py index 0213c8062e..93aff54696 100644 --- a/monitoring/mock_uss/flights/planning.py +++ b/monitoring/mock_uss/flights/planning.py @@ -1,26 +1,67 @@ +import json from collections.abc import Callable from datetime import UTC, datetime -from monitoring.mock_uss.flights.database import DEADLOCK_TIMEOUT, FlightRecord, db +import arrow +from implicitdict import ImplicitDict + +from monitoring.mock_uss.flights.database import ( + DEADLOCK_TIMEOUT, + FlightRecord, + MockUSSFlightID, + db, +) +from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo from monitoring.monitorlib.delay import sleep +from monitoring.monitorlib.temporal import Time + + +def adjust_flight_info(info: FlightInfo) -> FlightInfo: + result: FlightInfo = ImplicitDict.parse(json.loads(json.dumps(info)), FlightInfo) + + now = arrow.utcnow() + + for v4d in result.basic_information.area: + # Fill in empty start times with now + if "time_start" not in v4d or not v4d.time_start: + v4d.time_start = Time(now) + + # Truncate volume start times to current + elif v4d.time_start.datetime < now: + v4d.time_start = Time(now) + + # Validate volume times + for i, v4d in enumerate(result.basic_information.area): + if ( + "time_start" in v4d + and v4d.time_start + and "time_end" in v4d + and v4d.time_end + and v4d.time_start >= v4d.time_end + ): + raise ValueError( + f"Volume {i} start time {v4d.time_start} (originally {info.basic_information.area[i].time_start}) is at or after end time {v4d.time_end}" + ) + + return result -def lock_flight(flight_id: str, log: Callable[[str], None]) -> FlightRecord: +def lock_flight(flight_id: MockUSSFlightID, log: Callable[[str], None]) -> FlightRecord: # If this is a change to an existing flight, acquire lock to that flight log(f"Acquiring lock for flight {flight_id}") deadline = datetime.now(UTC) + DEADLOCK_TIMEOUT while True: - with db as tx: - if flight_id in tx.flights: + with db.transact() as tx: + if flight_id in tx.value.flights: # This is an existing flight being modified - existing_flight = tx.flights[flight_id] + existing_flight = tx.value.flights[flight_id] if existing_flight and not existing_flight.locked: log("Existing flight locked for update") existing_flight.locked = True break else: log("Request is for a new flight (lock established)") - tx.flights[flight_id] = None + tx.value.flights[flight_id] = None existing_flight = None break # We found an existing flight but it was locked; wait for it to become @@ -34,28 +75,29 @@ def lock_flight(flight_id: str, log: Callable[[str], None]) -> FlightRecord: return existing_flight -def release_flight_lock(flight_id: str, log: Callable[[str], None]) -> None: - with db as tx: - if flight_id in tx.flights: - if tx.flights[flight_id]: +def release_flight_lock(flight_id: MockUSSFlightID, log: Callable[[str], None]) -> None: + with db.transact() as tx: + if flight_id in tx.value.flights: + flight = tx.value.flights[flight_id] + if flight: # FlightRecord was a true existing flight log(f"Releasing lock on existing flight_id {flight_id}") - tx.flights[flight_id].locked = False + flight.locked = False else: # FlightRecord was just a placeholder for a new flight log(f"Releasing placeholder for existing flight_id {flight_id}") - del tx.flights[flight_id] + del tx.value.flights[flight_id] -def delete_flight_record(flight_id: str) -> FlightRecord | None: +def delete_flight_record(flight_id: MockUSSFlightID) -> FlightRecord | None: deadline = datetime.now(UTC) + DEADLOCK_TIMEOUT while True: - with db as tx: - if flight_id in tx.flights: - flight = tx.flights[flight_id] + with db.transact() as tx: + if flight_id in tx.value.flights: + flight = tx.value.flights[flight_id] if flight and not flight.locked: # FlightRecord was a true existing flight not being mutated anywhere else - del tx.flights[flight_id] + del tx.value.flights[flight_id] return flight else: # No FlightRecord found diff --git a/monitoring/mock_uss/geoawareness/check.py b/monitoring/mock_uss/geoawareness/check.py index c278d3bd52..97ca2d4e2b 100644 --- a/monitoring/mock_uss/geoawareness/check.py +++ b/monitoring/mock_uss/geoawareness/check.py @@ -7,7 +7,8 @@ GeozoneSourceResponseResult, ) -from monitoring.mock_uss.geoawareness.database import Database, SourceRecord, db +from monitoring.mock_uss.geoawareness import database +from monitoring.mock_uss.geoawareness.database import SourceRecord, db from monitoring.mock_uss.geoawareness.ed269 import evaluate_source logger = logging.getLogger(__name__) @@ -27,7 +28,7 @@ def combine_results( def check_geozones(req: GeozonesCheckRequest) -> list[GeozonesCheckResultGeozone]: - sources: dict[str, SourceRecord] = Database.get_sources(db) + sources: dict[str, SourceRecord] = database.get_sources(db) results: list[GeozonesCheckResultGeozone] = [ GeozonesCheckResultGeozone.Absent @@ -44,7 +45,11 @@ def check_geozones(req: GeozonesCheckRequest) -> list[GeozonesCheckResultGeozone ) continue - fmt = source.definition.https_source.format + fmt = ( + source.definition.https_source.format + if source.definition.https_source + else None + ) if fmt == GeozoneHttpsSourceFormat.ED_269: logger.debug(f" {j + 1}. ED269 source {source_id} ready.") result = combine_results( diff --git a/monitoring/mock_uss/geoawareness/database.py b/monitoring/mock_uss/geoawareness/database.py index 8c4b233579..49e3b6d80b 100644 --- a/monitoring/mock_uss/geoawareness/database.py +++ b/monitoring/mock_uss/geoawareness/database.py @@ -1,6 +1,6 @@ import json -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.eurocae_ed269 import ED269Schema from uas_standards.interuss.automated_testing.geo_awareness.v1.api import ( CreateGeozoneSourceRequest, @@ -17,8 +17,8 @@ class ExistingRecordException(ValueError): class SourceRecord(ImplicitDict): definition: CreateGeozoneSourceRequest state: GeozoneSourceResponseResult - message: str | None - geozone_ed269: ED269Schema | None + message: Optional[str] + geozone_ed269: Optional[ED269Schema] class Database(ImplicitDict): @@ -26,60 +26,62 @@ class Database(ImplicitDict): sources: dict[str, SourceRecord] = {} - @staticmethod - def get_source(db: SynchronizedValue, id: str) -> SourceRecord: - return db.value.sources.get(id, None) - - @staticmethod - def get_sources(db: SynchronizedValue) -> SourceRecord: - return db.value.sources - - @staticmethod - def insert_source( - db: SynchronizedValue, - id: str, - definition: CreateGeozoneSourceRequest, - state: GeozoneSourceResponseResult, - message: str | None = None, - ) -> SourceRecord: - with db as tx: - if id in tx.sources.keys(): - raise ExistingRecordException() - tx.sources[id] = SourceRecord( - definition=definition, state=state, message=message - ) - result = tx.sources[id] - return result - - @staticmethod - def update_source_state( - db: SynchronizedValue, - id: str, - state: GeozoneSourceResponseResult, - message: str | None = None, - ): - with db as tx: - tx.sources[id]["state"] = state - tx.sources[id]["message"] = message - result = tx.sources[id] - return result - - @staticmethod - def update_source_geozone_ed269( - db: SynchronizedValue, id: str, geozone: ED269Schema - ): - with db as tx: - tx.sources[id]["geozone_ed269"] = geozone - result = tx.sources[id] - return result - - @staticmethod - def delete_source(db: SynchronizedValue, id: str): - with db as tx: - return tx.sources.pop(id, None) - - -db = SynchronizedValue( + +def get_source( + geo_db: SynchronizedValue[Database], source_id: str +) -> SourceRecord | None: + return geo_db.value.sources.get(source_id, None) + + +def get_sources(geo_db: SynchronizedValue[Database]) -> dict[str, SourceRecord]: + return geo_db.value.sources + + +def insert_source( + geo_db: SynchronizedValue[Database], + source_id: str, + definition: CreateGeozoneSourceRequest, + state: GeozoneSourceResponseResult, + message: str | None = None, +) -> SourceRecord: + with geo_db.transact() as tx: + if source_id in tx.value.sources.keys(): + raise ExistingRecordException() + tx.value.sources[source_id] = SourceRecord( + definition=definition, state=state, message=message + ) + result = tx.value.sources[source_id] + return result + + +def update_source_state( + geo_db: SynchronizedValue[Database], + source_id: str, + state: GeozoneSourceResponseResult, + message: str | None = None, +): + with geo_db.transact() as tx: + tx.value.sources[source_id]["state"] = state + tx.value.sources[source_id]["message"] = message + result = tx.value.sources[source_id] + return result + + +def update_source_geozone_ed269( + geo_db: SynchronizedValue[Database], source_id: str, geozone: ED269Schema +): + with geo_db.transact() as tx: + tx.value.sources[source_id]["geozone_ed269"] = geozone + result = tx.value.sources[source_id] + return result + + +def delete_source(geo_db: SynchronizedValue[Database], source_id: str): + with geo_db.transact() as tx: + return tx.value.sources.pop(source_id, None) + + +db = SynchronizedValue[Database]( Database(), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), Database), ) diff --git a/monitoring/mock_uss/geoawareness/geozone_sources.py b/monitoring/mock_uss/geoawareness/geozone_sources.py index e363f23e8f..11d3eb718c 100644 --- a/monitoring/mock_uss/geoawareness/geozone_sources.py +++ b/monitoring/mock_uss/geoawareness/geozone_sources.py @@ -1,3 +1,4 @@ +import flask import requests from uas_standards.eurocae_ed269 import ED269Schema from uas_standards.interuss.automated_testing.geo_awareness.v1.api import ( @@ -7,30 +8,32 @@ GeozoneSourceResponseResult, ) +from monitoring.mock_uss.geoawareness import database from monitoring.mock_uss.geoawareness.database import ( - Database, ExistingRecordException, db, ) -def get_geozone_source(geozone_source_id: str): +def get_geozone_source(geozone_source_id: str) -> tuple[flask.Response | str, int]: """This handler returns the state of a geozone source""" - source = Database.get_source(db, geozone_source_id) + source = database.get_source(db, geozone_source_id) if source is None: return f"source {geozone_source_id} not found or deleted", 404 return ( - GeozoneSourceResponse(result=GeozoneSourceResponseResult.Ready), + flask.jsonify(GeozoneSourceResponse(result=GeozoneSourceResponseResult.Ready)), 200, ) -def create_geozone_source(id, source_definition: CreateGeozoneSourceRequest): +def create_geozone_source( + id, source_definition: CreateGeozoneSourceRequest +) -> tuple[flask.Response | str, int]: """This handler creates and activates a geozone source""" try: - source = Database.insert_source( + source = database.insert_source( db, id, source_definition, GeozoneSourceResponseResult.Activating ) except ExistingRecordException: @@ -41,12 +44,12 @@ def create_geozone_source(id, source_definition: CreateGeozoneSourceRequest): raw_data = requests.get(source.definition.https_source.url).json() if source.definition.https_source.format == GeozoneHttpsSourceFormat.ED_269: geozones = ED269Schema.from_dict(raw_data) - Database.update_source_geozone_ed269(db, id, geozones) - source = Database.update_source_state( + database.update_source_geozone_ed269(db, id, geozones) + source = database.update_source_state( db, id, GeozoneSourceResponseResult.Ready ) except ValueError as e: - source = Database.update_source_state( + source = database.update_source_state( db, id, GeozoneSourceResponseResult.Error, @@ -54,28 +57,32 @@ def create_geozone_source(id, source_definition: CreateGeozoneSourceRequest): ) else: - source = Database.update_source_state( + source = database.update_source_state( db, id, GeozoneSourceResponseResult.Error, "Unsupported source definition. https_source only", ) - return GeozoneSourceResponse(result=source.state, message=source.message), 400 + return flask.jsonify( + GeozoneSourceResponse(result=source.state, message=source.message) + ), 400 - return GeozoneSourceResponse( - result=source.state, message=source.get("message", None) - ) + return flask.jsonify( + GeozoneSourceResponse(result=source.state, message=source.get("message", None)) + ), 200 -def delete_geozone_source(geozone_source_id): +def delete_geozone_source(geozone_source_id) -> tuple[flask.Response | str, int]: """This handler deactivates and deletes a geozone source""" - deleted_id = Database.delete_source(db, geozone_source_id) + deleted_id = database.delete_source(db, geozone_source_id) if deleted_id is None: return f"source {geozone_source_id} not found", 404 return ( - GeozoneSourceResponse(result=GeozoneSourceResponseResult.Deactivating), + flask.jsonify( + GeozoneSourceResponse(result=GeozoneSourceResponseResult.Deactivating) + ), 200, ) diff --git a/monitoring/mock_uss/geoawareness/routes.py b/monitoring/mock_uss/geoawareness/routes.py index 515e0a16b1..5bd1468a02 100644 --- a/monitoring/mock_uss/geoawareness/routes.py +++ b/monitoring/mock_uss/geoawareness/routes.py @@ -3,7 +3,7 @@ StatusResponseStatus, ) -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope from monitoring.monitorlib import versioning from monitoring.monitorlib.geoawareness_automated_testing.api import ( diff --git a/monitoring/mock_uss/geoawareness/routes_geoawareness.py b/monitoring/mock_uss/geoawareness/routes_geoawareness.py index 8bdb60f9de..f6c3e4ad7a 100644 --- a/monitoring/mock_uss/geoawareness/routes_geoawareness.py +++ b/monitoring/mock_uss/geoawareness/routes_geoawareness.py @@ -6,7 +6,7 @@ GeozonesCheckRequest, ) -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.geoawareness.check import check_geozones from monitoring.mock_uss.geoawareness.geozone_sources import ( @@ -24,7 +24,9 @@ methods=["GET"], ) @requires_scope(SCOPE_GEOAWARENESS_TEST) -def geoawareness_get_geozone_sources(geozone_source_id: str) -> tuple[str, int]: +def geoawareness_get_geozone_sources( + geozone_source_id: str, +) -> tuple[flask.Response | str, int]: return get_geozone_source(geozone_source_id) @@ -33,7 +35,9 @@ def geoawareness_get_geozone_sources(geozone_source_id: str) -> tuple[str, int]: methods=["PUT"], ) @requires_scope(SCOPE_GEOAWARENESS_TEST) -def geoawareness_put_geozone_sources(geozone_source_id: str) -> tuple[str, int]: +def geoawareness_put_geozone_sources( + geozone_source_id: str, +) -> tuple[flask.Response | str, int]: try: json = flask.request.json if json is None: @@ -53,7 +57,9 @@ def geoawareness_put_geozone_sources(geozone_source_id: str) -> tuple[str, int]: methods=["DELETE"], ) @requires_scope(SCOPE_GEOAWARENESS_TEST) -def geoawareness_delete_geozone_sources(geozone_source_id: str) -> tuple[str, int]: +def geoawareness_delete_geozone_sources( + geozone_source_id: str, +) -> tuple[flask.Response | str, int]: return delete_geozone_source(geozone_source_id) diff --git a/monitoring/mock_uss/geoawareness/routes_geoawareness_test.py b/monitoring/mock_uss/geoawareness/routes_geoawareness_test.py index bdbbf2f410..cb8febdd77 100644 --- a/monitoring/mock_uss/geoawareness/routes_geoawareness_test.py +++ b/monitoring/mock_uss/geoawareness/routes_geoawareness_test.py @@ -3,7 +3,7 @@ import pytest from flask import json -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.monitorlib.auth import NoAuth TEST_DATASET_URL = "https://raw.githubusercontent.com/interuss/dss/517595ad4074bdb621feb4ab81c2d2f4fc11eff1/monitoring/uss_qualifier/scenarios/uspace/geo_awareness/design/CHE/geo-awareness-che-1.json" diff --git a/monitoring/mock_uss/geoawareness/routes_geospatial_map.py b/monitoring/mock_uss/geoawareness/routes_geospatial_map.py index 6983c6cbd2..0648a28f86 100644 --- a/monitoring/mock_uss/geoawareness/routes_geospatial_map.py +++ b/monitoring/mock_uss/geoawareness/routes_geospatial_map.py @@ -10,7 +10,7 @@ ) from uas_standards.interuss.automated_testing.geospatial_map.v1.constants import Scope -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope diff --git a/monitoring/mock_uss/gunicorn.conf.py b/monitoring/mock_uss/gunicorn.conf.py index 15b424bc53..ca3685bb09 100644 --- a/monitoring/mock_uss/gunicorn.conf.py +++ b/monitoring/mock_uss/gunicorn.conf.py @@ -6,7 +6,7 @@ from gunicorn.workers.base import Worker from loguru import logger -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp def on_starting(server: Arbiter): diff --git a/monitoring/mock_uss/health_check.sh b/monitoring/mock_uss/health_check.sh index ec803778eb..c25432f9ec 100644 --- a/monitoring/mock_uss/health_check.sh +++ b/monitoring/mock_uss/health_check.sh @@ -4,4 +4,4 @@ # mock_uss via the interuss/monitoring image to determine the health status of # the container. -curl --fail "http://localhost:${MOCK_USS_PORT:-5000}/status" || exit 1 +curl --fail --max-time 1 "http://localhost:${MOCK_USS_PORT:-5000}/status" || exit 1 diff --git a/monitoring/mock_uss/interaction_logging/config.py b/monitoring/mock_uss/interaction_logging/config.py index 1fabaedb31..4f74e2e3e4 100644 --- a/monitoring/mock_uss/interaction_logging/config.py +++ b/monitoring/mock_uss/interaction_logging/config.py @@ -1,6 +1,6 @@ import os.path -from monitoring.mock_uss import import_environment_variable, webapp +from monitoring.mock_uss.app import import_environment_variable, webapp KEY_INTERACTIONS_LOG_DIR = "MOCK_USS_INTERACTIONS_LOG_DIR" diff --git a/monitoring/mock_uss/interaction_logging/logger.py b/monitoring/mock_uss/interaction_logging/logger.py index 1446be8131..bf1729aeaa 100644 --- a/monitoring/mock_uss/interaction_logging/logger.py +++ b/monitoring/mock_uss/interaction_logging/logger.py @@ -4,8 +4,9 @@ import flask -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.interaction_logging.config import KEY_INTERACTIONS_LOG_DIR +from monitoring.mock_uss.logging import get_query_type from monitoring.monitorlib.clients import QueryHook, query_hooks from monitoring.monitorlib.clients.mock_uss.interactions import ( Interaction, @@ -76,12 +77,22 @@ def interaction_log_after_request(response): elapsed_s = ( datetime.datetime.now(datetime.UTC) - flask.current_app.custom_profiler["start"] ).total_seconds() + query_type = get_query_type() # TODO: Make this configurable instead of hardcoding exactly these query types - if ( - "/uss/v1/" in flask.request.url - or "/uss/identification_service_areas/" in flask.request.url - or "/uss/flights" in flask.request.url + if query_type in ( + QueryType.F3548v21USSGetConstraintDetails, + QueryType.F3548v21USSGetOperationalIntentDetails, + QueryType.F3548v21USSGetOperationalIntentTelemetry, + QueryType.F3548v21USSMakeUssReport, + QueryType.F3548v21USSNotifyConstraintDetailsChanged, + QueryType.F3548v21USSNotifyOperationalIntentDetailsChanged, + QueryType.F3411v22aUSSSearchFlights, + QueryType.F3411v22aUSSPostIdentificationServiceArea, + QueryType.F3411v19USSSearchFlights, + QueryType.F3411v19USSPostIdentificationServiceArea, ): query = describe_flask_query(flask.request, response, elapsed_s) + if query_type: + query.query_type = query_type log_interaction(QueryDirection.Incoming, query) return response diff --git a/monitoring/mock_uss/interaction_logging/routes_interactions_log.py b/monitoring/mock_uss/interaction_logging/routes_interactions_log.py index 5fc6a86d4d..1644c21c2d 100644 --- a/monitoring/mock_uss/interaction_logging/routes_interactions_log.py +++ b/monitoring/mock_uss/interaction_logging/routes_interactions_log.py @@ -6,7 +6,7 @@ from implicitdict import ImplicitDict, StringBasedDateTime from loguru import logger -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.interaction_logging.config import KEY_INTERACTIONS_LOG_DIR from monitoring.mock_uss.interaction_logging.logger import ( diff --git a/monitoring/mock_uss/logging.py b/monitoring/mock_uss/logging.py index e262cfbf2a..fdf7d3fd76 100644 --- a/monitoring/mock_uss/logging.py +++ b/monitoring/mock_uss/logging.py @@ -2,13 +2,15 @@ import json import os +from functools import wraps import flask import loguru from flask import Request, Response from loguru import logger -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp +from monitoring.monitorlib.fetch import QueryType def _get_request_id(req: Request) -> str: @@ -96,3 +98,33 @@ def end_request_log(e: BaseException | None) -> None: request_logger = get_request_logger() if request_logger is not None: request_logger.remove_from_loguru() + + +def query_type(q_type: QueryType): + """Decorator for a view function that indicates which QueryType it handles.""" + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + # Attach the attribute directly to the wrapper function object + setattr(wrapper, "query_type", q_type) + return wrapper + + return decorator + + +def get_query_type() -> QueryType | None: + """Get the query type handled by the active Flask view function (as indicated by the query_type decorator).""" + if ( + flask.request.endpoint + and flask.request.endpoint in flask.current_app.view_functions + ): + handler_func = flask.current_app.view_functions[flask.request.endpoint] + + q_type = getattr(handler_func, "query_type", None) + + return q_type + else: + return None diff --git a/monitoring/mock_uss/mockuss.py b/monitoring/mock_uss/mockuss.py index 65d31f5480..4d95ee17f0 100644 --- a/monitoring/mock_uss/mockuss.py +++ b/monitoring/mock_uss/mockuss.py @@ -1,7 +1,7 @@ import os import sys -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp def main(argv): diff --git a/monitoring/mock_uss/msgsigning/__init__.py b/monitoring/mock_uss/msgsigning/__init__.py index 1e8e1bb921..e69de29bb2 100644 --- a/monitoring/mock_uss/msgsigning/__init__.py +++ b/monitoring/mock_uss/msgsigning/__init__.py @@ -1,6 +0,0 @@ -from monitoring.mock_uss import SERVICE_MESSAGESIGNING, config - -if not config.Config.CERT_BASE_PATH: - raise ValueError( - f"Environment variable {config.ENV_KEY_CERT_BASE_PATH} may not be blank for the {SERVICE_MESSAGESIGNING} service" - ) diff --git a/monitoring/mock_uss/msgsigning/config.py b/monitoring/mock_uss/msgsigning/config.py index e24a56e968..0e11a7e157 100644 --- a/monitoring/mock_uss/msgsigning/config.py +++ b/monitoring/mock_uss/msgsigning/config.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import import_environment_variable +from monitoring.mock_uss.app import import_environment_variable KEY_CERT_BASE_PATH = "MOCK_USS_CERT_BASE_PATH" diff --git a/monitoring/mock_uss/msgsigning/database.py b/monitoring/mock_uss/msgsigning/database.py index b03b4df005..64661f2aa0 100644 --- a/monitoring/mock_uss/msgsigning/database.py +++ b/monitoring/mock_uss/msgsigning/database.py @@ -14,7 +14,7 @@ class Database(ImplicitDict): private_key_name: str = "messagesigning/mock_faa_priv.pem" -db = SynchronizedValue( +db = SynchronizedValue[Database]( Database(), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), Database), ) diff --git a/monitoring/mock_uss/msgsigning/routes.py b/monitoring/mock_uss/msgsigning/routes.py index 6e9bb2e646..f17b5f8063 100644 --- a/monitoring/mock_uss/msgsigning/routes.py +++ b/monitoring/mock_uss/msgsigning/routes.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from . import routes_msgsigning as routes_msgsigning diff --git a/monitoring/mock_uss/msgsigning/routes_msgsigning.py b/monitoring/mock_uss/msgsigning/routes_msgsigning.py index 8abc42e5e0..8db015b166 100644 --- a/monitoring/mock_uss/msgsigning/routes_msgsigning.py +++ b/monitoring/mock_uss/msgsigning/routes_msgsigning.py @@ -3,7 +3,7 @@ import flask from loguru import logger -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.msgsigning import config from monitoring.mock_uss.msgsigning.database import db diff --git a/monitoring/mock_uss/postman_collection.json b/monitoring/mock_uss/postman_collection.json index 271299f404..ea82976515 100644 --- a/monitoring/mock_uss/postman_collection.json +++ b/monitoring/mock_uss/postman_collection.json @@ -653,12 +653,12 @@ "method": "GET", "header": [], "url": { - "raw": "http://localhost:8082/dss/v1/operational_intent_references/{{op_intent_id}}", + "raw": "http://localhost:8001/dss/v1/operational_intent_references/{{op_intent_id}}", "protocol": "http", "host": [ "localhost" ], - "port": "8082", + "port": "8001", "path": [ "dss", "v1", diff --git a/monitoring/mock_uss/resources.py b/monitoring/mock_uss/resources.py index 754fa1e65a..767e76fc5d 100644 --- a/monitoring/mock_uss/resources.py +++ b/monitoring/mock_uss/resources.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.monitorlib import auth, infrastructure from . import config @@ -6,7 +6,7 @@ require_config_value(config.KEY_DSS_URL) require_config_value(config.KEY_AUTH_SPEC) -utm_client = infrastructure.UTMClientSession( +utm_client = infrastructure.utm_client_session_factory.get_session( webapp.config[config.KEY_DSS_URL], auth.make_auth_adapter(webapp.config[config.KEY_AUTH_SPEC]), ) diff --git a/monitoring/mock_uss/riddp/__init__.py b/monitoring/mock_uss/riddp/__init__.py index c1f530361a..22181d1368 100644 --- a/monitoring/mock_uss/riddp/__init__.py +++ b/monitoring/mock_uss/riddp/__init__.py @@ -1,8 +1,8 @@ -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.config import KEY_AUTH_SPEC, KEY_DSS_URL from monitoring.mock_uss.riddp.config import KEY_RID_VERSION from monitoring.monitorlib import auth -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import utm_client_session_factory from monitoring.monitorlib.rid import RIDVersion from . import config as config @@ -20,7 +20,7 @@ f"Cannot construct DSS base URL using RID version {webapp.config[KEY_RID_VERSION]}" ) -utm_client = UTMClientSession( +utm_client = utm_client_session_factory.get_session( _dss_base_url, auth.make_auth_adapter(webapp.config[KEY_AUTH_SPEC]), ) diff --git a/monitoring/mock_uss/riddp/behavior.py b/monitoring/mock_uss/riddp/behavior.py index ab5821361a..691f93be60 100644 --- a/monitoring/mock_uss/riddp/behavior.py +++ b/monitoring/mock_uss/riddp/behavior.py @@ -1,8 +1,8 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional ServiceProviderID = str class DisplayProviderBehavior(ImplicitDict): - always_omit_recent_paths: bool | None = False - do_not_display_flights_from: list[ServiceProviderID] | None = [] + always_omit_recent_paths: Optional[bool] = False + do_not_display_flights_from: Optional[list[ServiceProviderID]] = [] diff --git a/monitoring/mock_uss/riddp/config.py b/monitoring/mock_uss/riddp/config.py index ce3f048f6a..9066886bb0 100644 --- a/monitoring/mock_uss/riddp/config.py +++ b/monitoring/mock_uss/riddp/config.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import import_environment_variable +from monitoring.mock_uss.app import import_environment_variable from monitoring.monitorlib.rid import RIDVersion KEY_RID_VERSION = "MOCK_USS_RID_VERSION" diff --git a/monitoring/mock_uss/riddp/database.py b/monitoring/mock_uss/riddp/database.py index 0ab4adcc2f..d62da41fca 100644 --- a/monitoring/mock_uss/riddp/database.py +++ b/monitoring/mock_uss/riddp/database.py @@ -55,7 +55,7 @@ class Database(ImplicitDict): subscriptions: list[ObservationSubscription] -db = SynchronizedValue( +db = SynchronizedValue[Database]( Database(flights={}, subscriptions=[]), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), Database), ) diff --git a/monitoring/mock_uss/riddp/routes.py b/monitoring/mock_uss/riddp/routes.py index 33116e6020..642c49c719 100644 --- a/monitoring/mock_uss/riddp/routes.py +++ b/monitoring/mock_uss/riddp/routes.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.riddp.config import KEY_RID_VERSION from ...monitorlib.rid import RIDVersion diff --git a/monitoring/mock_uss/riddp/routes_behavior.py b/monitoring/mock_uss/riddp/routes_behavior.py index df363f055c..b361857b4f 100644 --- a/monitoring/mock_uss/riddp/routes_behavior.py +++ b/monitoring/mock_uss/riddp/routes_behavior.py @@ -1,14 +1,14 @@ import flask from implicitdict import ImplicitDict -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from .behavior import DisplayProviderBehavior from .database import db @webapp.route("/riddp/behavior", methods=["PUT"]) -def riddp_set_dp_behavior() -> tuple[str, int]: +def riddp_set_dp_behavior() -> tuple[str, int] | flask.Response: """Set the behavior of the mock Display Provider.""" try: json = flask.request.json @@ -19,13 +19,13 @@ def riddp_set_dp_behavior() -> tuple[str, int]: msg = f"Change behavior for Display Provider unable to parse JSON: {e}" return msg, 400 - with db as tx: - tx.behavior = dp_behavior + with db.transact() as tx: + tx.value.behavior = dp_behavior return flask.jsonify(dp_behavior) @webapp.route("/riddp/behavior", methods=["GET"]) -def riddp_get_dp_behavior() -> tuple[str, int]: +def riddp_get_dp_behavior() -> flask.Response: """Get the behavior of the mock Display Provider.""" return flask.jsonify(db.value.behavior) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 5511b6046d..6f0bd9747b 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -21,7 +21,7 @@ UAType, ) -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.config import KEY_BASE_URL from monitoring.mock_uss.riddp.database import ObservationSubscription @@ -135,16 +135,19 @@ def riddp_display_data() -> tuple[flask.Response, int]: 413, ) - with db as tx: + with db.transact() as tx: # Find an existing subscription to serve this request subscription: ObservationSubscription | None = None t_max = ( arrow.utcnow() + timedelta(seconds=1) ).datetime # Don't rely on subscriptions very near their expiration - tx.subscriptions = [ - s for s in tx.subscriptions if s.upsert_result.subscription.time_end > t_max + tx.value.subscriptions = [ + s + for s in tx.value.subscriptions + if s.upsert_result.subscription + and s.upsert_result.subscription.time_end > t_max ] - for existing_subscription in tx.subscriptions: + for existing_subscription in tx.value.subscriptions: assert isinstance(existing_subscription, ObservationSubscription) sub_rect = existing_subscription.bounds.to_latlngrect() if sub_rect.contains(view): @@ -184,7 +187,7 @@ def riddp_display_data() -> tuple[flask.Response, int]: subscription = ObservationSubscription( bounds=sub_bounds, upsert_result=upsert_result, updates=[] ) - tx.subscriptions.append(subscription) + tx.value.subscriptions.append(subscription) # Fetch flights from each unique flights URL validated_flights: list[Flight] = [] @@ -222,9 +225,9 @@ def riddp_display_data() -> tuple[flask.Response, int]: flight_info[flight.id] = database.FlightInfo(flights_url=flights_url) # Update links between flight IDs and flight URLs - with db as tx: + with db.transact() as tx: for k, v in flight_info.items(): - tx.flights[k] = v + tx.value.flights[k] = v # Make and return response flights = [_make_flight_observation(f, view) for f in validated_flights] @@ -243,7 +246,7 @@ def riddp_display_data() -> tuple[flask.Response, int]: @webapp.route("/riddp/observation/display_data/", methods=["GET"]) @requires_scope(Scope.Read) -def riddp_flight_details(flight_id: str) -> tuple[str, int]: +def riddp_flight_details(flight_id: str) -> tuple[str, int] | flask.Response: """Implements get flight details endpoint per automated testing API.""" tx = db.value flight_info = tx.flights.get(flight_id) diff --git a/monitoring/mock_uss/riddp/routes_riddp_v19.py b/monitoring/mock_uss/riddp/routes_riddp_v19.py index 4586d549a1..bdcf020daf 100644 --- a/monitoring/mock_uss/riddp/routes_riddp_v19.py +++ b/monitoring/mock_uss/riddp/routes_riddp_v19.py @@ -8,10 +8,11 @@ ) from uas_standards.astm.f3411.v19.constants import Scope -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope +from monitoring.mock_uss.logging import query_type from monitoring.mock_uss.riddp.database import db -from monitoring.monitorlib.fetch import describe_flask_query +from monitoring.monitorlib.fetch import QueryType, describe_flask_query from monitoring.monitorlib.mutate.rid import UpdatedISA @@ -22,6 +23,7 @@ def rid_v19_operation(op_id: OperationID): @rid_v19_operation(OperationID.PostIdentificationServiceArea) +@query_type(QueryType.F3411v19USSPostIdentificationServiceArea) @requires_scope(Scope.Write) def riddp_notify_isa_v19(id: str): try: @@ -37,10 +39,12 @@ def riddp_notify_isa_v19(id: str): subscription_ids = [s.subscription_id for s in put_params.subscriptions] if subscription_ids: - with db as tx: + with db.transact() as tx: updated = False - for subscription in tx.subscriptions: + for subscription in tx.value.subscriptions: + if not subscription.upsert_result.subscription: + continue if subscription.upsert_result.subscription.id in subscription_ids: query = describe_flask_query(flask.request, flask.jsonify(None), 0) subscription.updates.append(UpdatedISA(v19_query=query)) diff --git a/monitoring/mock_uss/riddp/routes_riddp_v22a.py b/monitoring/mock_uss/riddp/routes_riddp_v22a.py index 0f6a47c938..d0058c8955 100644 --- a/monitoring/mock_uss/riddp/routes_riddp_v22a.py +++ b/monitoring/mock_uss/riddp/routes_riddp_v22a.py @@ -8,10 +8,11 @@ ) from uas_standards.astm.f3411.v22a.constants import Scope -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope +from monitoring.mock_uss.logging import query_type from monitoring.mock_uss.riddp.database import db -from monitoring.monitorlib.fetch import describe_flask_query +from monitoring.monitorlib.fetch import QueryType, describe_flask_query from monitoring.monitorlib.mutate.rid import UpdatedISA @@ -22,6 +23,7 @@ def rid_v22a_operation(op_id: OperationID): @rid_v22a_operation(OperationID.PostIdentificationServiceArea) +@query_type(QueryType.F3411v22aUSSPostIdentificationServiceArea) @requires_scope(Scope.ServiceProvider) def riddp_notify_isa_v22a(id: str): try: @@ -37,10 +39,12 @@ def riddp_notify_isa_v22a(id: str): subscription_ids = [s.subscription_id for s in put_params.subscriptions] if subscription_ids: - with db as tx: + with db.transact() as tx: updated = False - for subscription in tx.subscriptions: + for subscription in tx.value.subscriptions: + if not subscription.upsert_result.subscription: + continue if subscription.upsert_result.subscription.id in subscription_ids: query = describe_flask_query(flask.request, flask.jsonify(None), 0) subscription.updates.append(UpdatedISA(v22a_query=query)) diff --git a/monitoring/mock_uss/ridsp/__init__.py b/monitoring/mock_uss/ridsp/__init__.py index 5b77d1b9a5..86edbfb5eb 100644 --- a/monitoring/mock_uss/ridsp/__init__.py +++ b/monitoring/mock_uss/ridsp/__init__.py @@ -1,8 +1,10 @@ -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.config import KEY_AUTH_SPEC, KEY_DSS_URL from monitoring.mock_uss.riddp.config import KEY_RID_VERSION from monitoring.monitorlib import auth -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import ( + utm_client_session_factory, +) from monitoring.monitorlib.rid import RIDVersion require_config_value(KEY_DSS_URL) @@ -18,7 +20,7 @@ f"Cannot construct DSS base URL using RID version {webapp.config[KEY_RID_VERSION]}" ) -utm_client = UTMClientSession( +utm_client = utm_client_session_factory.get_session( _dss_base_url, auth.make_auth_adapter(webapp.config[KEY_AUTH_SPEC]), ) diff --git a/monitoring/mock_uss/ridsp/behavior.py b/monitoring/mock_uss/ridsp/behavior.py index 0a16f93f3c..9239e0945e 100644 --- a/monitoring/mock_uss/ridsp/behavior.py +++ b/monitoring/mock_uss/ridsp/behavior.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.astm.f3411.v19.api import RIDFlight from monitoring.monitorlib.delay import sleep @@ -8,10 +8,10 @@ class ServiceProviderBehavior(ImplicitDict): - switch_latitude_and_longitude_when_reporting: bool | None = False - use_agl_instead_of_wgs84_for_altitude: bool | None = False - use_feet_instead_of_meters_for_altitude: bool | None = False - delay_flight_report_s: int | None = 0 + switch_latitude_and_longitude_when_reporting: Optional[bool] = False + use_agl_instead_of_wgs84_for_altitude: Optional[bool] = False + use_feet_instead_of_meters_for_altitude: Optional[bool] = False + delay_flight_report_s: Optional[int] = 0 def adjust_reported_flight( diff --git a/monitoring/mock_uss/ridsp/database.py b/monitoring/mock_uss/ridsp/database.py index 736b47c54c..32388587f8 100644 --- a/monitoring/mock_uss/ridsp/database.py +++ b/monitoring/mock_uss/ridsp/database.py @@ -1,20 +1,32 @@ import json +from datetime import timedelta -from implicitdict import ImplicitDict +import arrow +from implicitdict import ImplicitDict, Optional +from monitoring.mock_uss.app import webapp from monitoring.monitorlib.multiprocessing import SynchronizedValue from monitoring.monitorlib.rid_automated_testing import injection_api from .behavior import ServiceProviderBehavior from .user_notifications import ServiceProviderUserNotifications +DB_CLEANUP_INTERVAL = timedelta(hours=1) +"""Clean database this often.""" + +FLIGHTS_LIMIT = timedelta(hours=1) +"""Automatically remove flights we manage after this long beyond their end time.""" + +NOTIFICATIONS_LIMIT = timedelta(hours=1) +"""Automatically remove notifications after this long beyond their observation time.""" + class TestRecord(ImplicitDict): """Representation of RID SP's record of a set of injected test flights""" version: str flights: list[injection_api.TestFlight] - isa_version: str | None = None + isa_version: Optional[str] = None def __init__(self, **kwargs): kwargs["flights"] = [ @@ -25,6 +37,14 @@ def __init__(self, **kwargs): super().__init__(**kwargs) + def cleanup_flights(self): + self.flights = [ + flight + for flight in self.flights + if (end_time := flight.get_span()[1]) + and end_time + FLIGHTS_LIMIT > arrow.utcnow().datetime + ] + class Database(ImplicitDict): """Simple pseudo-database structure tracking the state of the mock system""" @@ -33,8 +53,36 @@ class Database(ImplicitDict): behavior: ServiceProviderBehavior = ServiceProviderBehavior() notifications: ServiceProviderUserNotifications = ServiceProviderUserNotifications() + def cleanup_notifications(self): + self.notifications.cleanup(NOTIFICATIONS_LIMIT) + + def cleanup_flights(self): + to_cleanup = [] + + for test_id, test in self.tests.items(): + if test.flights: + test.cleanup_flights() + + if not test.flights: + to_cleanup.append(test_id) -db = SynchronizedValue( + for test_id in to_cleanup: + del self.tests[test_id] + + +db = SynchronizedValue[Database]( Database(), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), Database), ) + +TASK_DATABASE_CLEANUP = "ridsp database cleanup" + + +@webapp.periodic_task(TASK_DATABASE_CLEANUP) +def database_cleanup() -> None: + with db.transact() as tx: + tx.value.cleanup_notifications() + tx.value.cleanup_flights() + + +webapp.set_task_period(TASK_DATABASE_CLEANUP, DB_CLEANUP_INTERVAL) diff --git a/monitoring/mock_uss/ridsp/routes.py b/monitoring/mock_uss/ridsp/routes.py index 9c6880ff23..695cbcc856 100644 --- a/monitoring/mock_uss/ridsp/routes.py +++ b/monitoring/mock_uss/ridsp/routes.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.riddp.config import KEY_RID_VERSION from monitoring.monitorlib.rid import RIDVersion diff --git a/monitoring/mock_uss/ridsp/routes_behavior.py b/monitoring/mock_uss/ridsp/routes_behavior.py index 5a45f65bba..ce26a8c9cc 100644 --- a/monitoring/mock_uss/ridsp/routes_behavior.py +++ b/monitoring/mock_uss/ridsp/routes_behavior.py @@ -1,14 +1,14 @@ import flask from implicitdict import ImplicitDict -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from .behavior import ServiceProviderBehavior from .database import db @webapp.route("/ridsp/behavior", methods=["PUT"]) -def ridsp_set_dp_behavior() -> tuple[str, int]: +def ridsp_set_dp_behavior() -> tuple[str, int] | flask.Response: """Set the behavior of the mock Display Provider.""" try: json = flask.request.json @@ -19,13 +19,13 @@ def ridsp_set_dp_behavior() -> tuple[str, int]: msg = f"Change behavior for Service Provider unable to parse JSON: {e}" return msg, 400 - with db as tx: - tx.behavior = dp_behavior + with db.transact() as tx: + tx.value.behavior = dp_behavior return flask.jsonify(dp_behavior) @webapp.route("/ridsp/behavior", methods=["GET"]) -def ridsp_get_dp_behavior() -> tuple[str, int]: +def ridsp_get_dp_behavior() -> flask.Response: """Get the behavior of the mock Display Provider.""" return flask.jsonify(db.value.behavior) diff --git a/monitoring/mock_uss/ridsp/routes_injection.py b/monitoring/mock_uss/ridsp/routes_injection.py index 4ee4cd3ae8..e9f439b2e2 100644 --- a/monitoring/mock_uss/ridsp/routes_injection.py +++ b/monitoring/mock_uss/ridsp/routes_injection.py @@ -12,7 +12,7 @@ QueryUserNotificationsResponse, ) -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.config import KEY_BASE_URL from monitoring.mock_uss.riddp.config import KEY_RID_VERSION @@ -41,7 +41,7 @@ class ErrorResponse(ImplicitDict): @webapp.route("/ridsp/injection/tests/", methods=["PUT"]) @requires_scope(injection_api.SCOPE_RID_QUALIFIER_INJECT) @idempotent_request() -def ridsp_create_test(test_id: str) -> tuple[str, int]: +def ridsp_create_test(test_id: str) -> tuple[str | flask.Response, int]: """Implements test creation in RID automated testing injection API.""" logger.info(f"Create test {test_id}") rid_version = webapp.config[KEY_RID_VERSION] @@ -109,18 +109,18 @@ def ridsp_create_test(test_id: str) -> tuple[str, int]: response["query"] = notification.query return flask.jsonify(response), 412 - with db as tx: - tx.tests[test_id] = record - tx.notifications.create_notifications_if_needed(record) + with db.transact() as tx: + tx.value.tests[test_id] = record + tx.value.notifications.create_notifications_if_needed(record) return flask.jsonify( ChangeTestResponse(version=record.version, injected_flights=record.flights) - ) + ), 200 @webapp.route("/ridsp/injection/tests//", methods=["DELETE"]) @requires_scope(injection_api.SCOPE_RID_QUALIFIER_INJECT) -def ridsp_delete_test(test_id: str, version: str) -> tuple[str, int]: +def ridsp_delete_test(test_id: str, version: str) -> tuple[str | flask.Response, int]: """Implements test deletion in RID automated testing injection API.""" logger.info(f"Delete test {test_id}") rid_version = webapp.config[KEY_RID_VERSION] @@ -135,36 +135,39 @@ def ridsp_delete_test(test_id: str, version: str) -> tuple[str, int]: 404, ) - # Delete ISA from DSS - deleted_isa = mutate.delete_isa( - isa_id=record.version, - isa_version=record.isa_version, - rid_version=rid_version, - utm_client=utm_client, - ) - if not deleted_isa.dss_query.success: - logger.error(f"Unable to delete ISA {record.version} from DSS") - response = ErrorResponse(message="Unable to delete ISA from DSS") - response["errors"] = deleted_isa.dss_query.errors - response["query"] = deleted_isa.dss_query - return flask.jsonify(response), 412 - logger.info(f"Created ISA {deleted_isa.dss_query.isa.id}") result = ChangeTestResponse(version=record.version, injected_flights=record.flights) - for url, notification in deleted_isa.notifications.items(): - code = notification.query.status_code - if code == 200: - logger.warning( - f"Notification to {notification.query.request.url} incorrectly returned 200 rather than 204" - ) - elif code != 204: - logger.error( - f"Notification failure {code} to {notification.query.request.url}" - ) - result["query"] = notification.query - with db as tx: - del tx.tests[test_id] - return flask.jsonify(result) + if record.isa_version is not None: + # Delete ISA from DSS + deleted_isa = mutate.delete_isa( + isa_id=record.version, + isa_version=record.isa_version, + rid_version=rid_version, + utm_client=utm_client, + ) + if not deleted_isa.dss_query.success: + logger.error(f"Unable to delete ISA {record.version} from DSS") + response = ErrorResponse(message="Unable to delete ISA from DSS") + response["errors"] = deleted_isa.dss_query.errors + response["query"] = deleted_isa.dss_query + return flask.jsonify(response), 412 + logger.info(f"Deleted ISA {deleted_isa.dss_query.isa.id}") + + for url, notification in deleted_isa.notifications.items(): + code = notification.query.status_code + if code == 200: + logger.warning( + f"Notification to {notification.query.request.url} incorrectly returned 200 rather than 204" + ) + elif code != 204: + logger.error( + f"Notification failure {code} to {notification.query.request.url}" + ) + result["query"] = notification.query + + with db.transact() as tx: + del tx.value.tests[test_id] + return flask.jsonify(result), 200 @webapp.route( @@ -172,7 +175,7 @@ def ridsp_delete_test(test_id: str, version: str) -> tuple[str, int]: methods=["GET"], ) @requires_scope(injection_api.SCOPE_RID_QUALIFIER_INJECT) -def ridsp_get_user_notifications() -> tuple[str, int]: +def ridsp_get_user_notifications() -> tuple[str | flask.Response, int]: """Returns the list of user notifications observed by the virtual user""" if "after" not in flask.request.args: @@ -207,6 +210,10 @@ def ridsp_get_user_notifications() -> tuple[str, int]: 400, ) + before = min( + arrow.utcnow(), before + ) # Ensure we don't return notifications from the future + final_list = [] for user_notification in db.value.notifications.user_notifications: @@ -219,4 +226,4 @@ def ridsp_get_user_notifications() -> tuple[str, int]: r = QueryUserNotificationsResponse(user_notifications=final_list) - return flask.jsonify(r) + return flask.jsonify(r), 200 diff --git a/monitoring/mock_uss/ridsp/routes_ridsp_v19.py b/monitoring/mock_uss/ridsp/routes_ridsp_v19.py index ddf2b51502..6ba34f08d2 100644 --- a/monitoring/mock_uss/ridsp/routes_ridsp_v19.py +++ b/monitoring/mock_uss/ridsp/routes_ridsp_v19.py @@ -24,9 +24,11 @@ ) from uas_standards.interuss.automated_testing.rid.v1 import injection -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope +from monitoring.mock_uss.logging import query_type from monitoring.monitorlib import geo +from monitoring.monitorlib.fetch import QueryType from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.rid_automated_testing.injection_api import TestFlight @@ -94,18 +96,8 @@ def rid_v19_operation(op_id: OperationID): return webapp.route("/mock/ridsp" + path, methods=[op.verb]) -@rid_v19_operation(OperationID.PostIdentificationServiceArea) -@requires_scope(Scope.Write) -def ridsp_notify_isa_v19(id: str): - return ( - flask.jsonify( - {"message": "mock_ridsp never solicits subscription notifications"} - ), - 400, - ) - - @rid_v19_operation(OperationID.SearchFlights) +@query_type(QueryType.F3411v19USSSearchFlights) @requires_scope(Scope.Read) def ridsp_flights_v19(): if "view" not in flask.request.args: @@ -150,6 +142,7 @@ def ridsp_flights_v19(): @rid_v19_operation(OperationID.GetFlightDetails) +@query_type(QueryType.F3411v19USSGetFlightDetails) @requires_scope(Scope.Read) def ridsp_flight_details_v19(id: str): now = arrow.utcnow().datetime diff --git a/monitoring/mock_uss/ridsp/routes_ridsp_v22a.py b/monitoring/mock_uss/ridsp/routes_ridsp_v22a.py index e5ee3811f8..625c25d631 100644 --- a/monitoring/mock_uss/ridsp/routes_ridsp_v22a.py +++ b/monitoring/mock_uss/ridsp/routes_ridsp_v22a.py @@ -27,9 +27,11 @@ ) from uas_standards.interuss.automated_testing.rid.v1 import injection -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope +from monitoring.mock_uss.logging import query_type from monitoring.monitorlib import geo +from monitoring.monitorlib.fetch import QueryType from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.rid_automated_testing.injection_api import TestFlight from monitoring.monitorlib.rid_v2 import make_time @@ -151,18 +153,8 @@ def rid_v22a_operation(op_id: OperationID): return webapp.route("/mock/ridsp/v2" + path, methods=[op.verb]) -@rid_v22a_operation(OperationID.PostIdentificationServiceArea) -@requires_scope(Scope.ServiceProvider) -def ridsp_notify_isa_v22a(id: str): - return ( - flask.jsonify( - {"message": "mock_ridsp never solicits subscription notifications"} - ), - 400, - ) - - @rid_v22a_operation(OperationID.SearchFlights) +@query_type(QueryType.F3411v22aUSSSearchFlights) @requires_scope(Scope.DisplayProvider) def ridsp_flights_v22a(): if "view" not in flask.request.args: @@ -225,6 +217,7 @@ def ridsp_flights_v22a(): @rid_v22a_operation(OperationID.GetFlightDetails) +@query_type(QueryType.F3411v22aUSSGetFlightDetails) @requires_scope(Scope.DisplayProvider) def ridsp_flight_details_v22a(id: str): now = arrow.utcnow().datetime diff --git a/monitoring/mock_uss/ridsp/user_notifications.py b/monitoring/mock_uss/ridsp/user_notifications.py index 0bd613e512..09a91669be 100644 --- a/monitoring/mock_uss/ridsp/user_notifications.py +++ b/monitoring/mock_uss/ridsp/user_notifications.py @@ -2,6 +2,7 @@ import arrow from implicitdict import ImplicitDict, StringBasedDateTime +from uas_standards.astm.f3411.v22a import constants from uas_standards.interuss.automated_testing.rid.v1.injection import ( Time, UserNotification, @@ -45,6 +46,13 @@ def create_notifications_if_needed(self, record: "database.TestRecord"): ): self.record_notification(message=notif_str, observed_at=notif_date) + def cleanup(self, limit: datetime.timedelta): + self.user_notifications = [ + notif + for notif in self.user_notifications + if notif.observed_at.value.datetime + limit > arrow.utcnow().datetime + ] + def check_and_generate_missing_fields_notifications( injected_flights: list[TestFlight], @@ -94,22 +102,52 @@ def check_and_generate_missing_fields_notifications( def check_and_generate_slow_update_notification( injected_flights: list[injection_api.TestFlight], -) -> list[datetime]: +) -> list[datetime.datetime]: """ Iterate over the provided list of injected TestFlight objects and, for any flight that has an average update rate under 1Hz, return a time for which a notification should be sent to the operator. """ - operator_slow_update_notifications: list[datetime] = [] + operator_slow_update_notifications: list[datetime.datetime] = [] for f in injected_flights: # Mean rate is not technically correct as per Net0040 # (20% of the samples may be above 1Hz with a mean rate below 1Hz), # but sufficient to trigger a notification to test the relevant scenario. - mean_rate = f.get_mean_update_rate_hz() - if mean_rate and mean_rate < 0.99: - # Arbitrarily use middle of the flight as notification time: - f_start, f_end = f.get_span() - if f_start and f_end: + + f_start, _ = f.get_span() + if not f_start: + continue + + # Compute update rate in 1s buckets: + rates = f.get_update_rates() + + if not rates: + continue + + # Check in a moving window of 10s, that NetMinUasLocRefreshPercentage + # samples are >= NetMinUasLocRefreshFrequency + MOVING_WINDOW_DURATION: int = 10 + + if len(rates) < MOVING_WINDOW_DURATION: + continue + + for wpos in range(0, len(rates) - MOVING_WINDOW_DURATION): + count_ok = sum( + [ + 1 if rate >= constants.NetMinUasLocRefreshFrequencyHz else 0 + for rate in rates[wpos : wpos + MOVING_WINDOW_DURATION] + ] + ) + + if ( + count_ok + < constants.NetMinUasLocRefreshPercentage + / 100.0 + * MOVING_WINDOW_DURATION + ): operator_slow_update_notifications.append( - f_start + (f_end - f_start) / 2 + f_start + + datetime.timedelta( + seconds=wpos + 2 + MOVING_WINDOW_DURATION + ) # get_update_rates is skipping the first 2 seconds (moving average of 3) ) return operator_slow_update_notifications diff --git a/monitoring/mock_uss/routes.py b/monitoring/mock_uss/routes.py index 22708e8fd5..ee0642ebaa 100644 --- a/monitoring/mock_uss/routes.py +++ b/monitoring/mock_uss/routes.py @@ -1,9 +1,10 @@ import traceback +import arrow import flask from werkzeug.exceptions import HTTPException -from monitoring.mock_uss import enabled_services, webapp +from monitoring.mock_uss.app import enabled_services, webapp from monitoring.mock_uss.logging import disable_log_reporting_for_request from monitoring.monitorlib import auth_validation, versioning @@ -17,6 +18,11 @@ def status(): ) +@webapp.route("/clock") +def get_clock() -> str: + return arrow.utcnow().isoformat() + + @webapp.route("/favicon.ico") def favicon(): flask.abort(404) diff --git a/monitoring/mock_uss/run_locally_msgsigning.sh b/monitoring/mock_uss/run_locally_msgsigning.sh index ae96a2135b..14c58979f5 100755 --- a/monitoring/mock_uss/run_locally_msgsigning.sh +++ b/monitoring/mock_uss/run_locally_msgsigning.sh @@ -18,7 +18,7 @@ make image ) AUTH="DummyOAuth(http://host.docker.internal:8085/token,uss1)" -DSS="http://host.docker.internal:8082" +DSS="http://host.docker.internal:8001" PUBLIC_KEY="/var/test-certs/auth2.pem" AUD=${MOCK_USS_TOKEN_AUDIENCE:-localhost,host.docker.internal} container_name="mock_uss_msgsigning" diff --git a/monitoring/mock_uss/run_locally_test_geoawareness.sh b/monitoring/mock_uss/run_locally_test_geoawareness.sh index ba11692bfd..01d68f1ec8 100755 --- a/monitoring/mock_uss/run_locally_test_geoawareness.sh +++ b/monitoring/mock_uss/run_locally_test_geoawareness.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # Find and change to repo root directory OS=$(uname) if [[ "$OS" == "Darwin" ]]; then @@ -20,7 +22,7 @@ container_name="mock_uss_geoawareness_test" AUD="localhost" docker_command="mock_uss/test.sh" -PORT=8076 +PORT=8070 if [ "$CI" == "true" ]; then docker_args="--add-host host.docker.internal:host-gateway" # Required to reach other containers in Ubuntu (used for Github Actions) diff --git a/monitoring/mock_uss/scd_injection/routes.py b/monitoring/mock_uss/scd_injection/routes.py index 837bd9d2cb..bc343c4ec7 100644 --- a/monitoring/mock_uss/scd_injection/routes.py +++ b/monitoring/mock_uss/scd_injection/routes.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp @webapp.route("/scdsc/status") diff --git a/monitoring/mock_uss/scd_injection/routes_injection.py b/monitoring/mock_uss/scd_injection/routes_injection.py index d92acd9408..68a75bd64a 100644 --- a/monitoring/mock_uss/scd_injection/routes_injection.py +++ b/monitoring/mock_uss/scd_injection/routes_injection.py @@ -1,4 +1,5 @@ import os +import uuid from datetime import UTC, datetime, timedelta import arrow @@ -17,7 +18,7 @@ DeleteFlightResponseResult, ) -from monitoring.mock_uss import require_config_value, webapp +from monitoring.mock_uss.app import require_config_value, webapp from monitoring.mock_uss.auth import requires_scope from monitoring.mock_uss.config import KEY_BASE_URL from monitoring.mock_uss.dynamic_configuration.configuration import get_locality @@ -30,8 +31,9 @@ share_op_intent, validate_request, ) -from monitoring.mock_uss.flights.database import FlightRecord, db +from monitoring.mock_uss.flights.database import FlightRecord, MockUSSFlightID, db from monitoring.mock_uss.flights.planning import ( + adjust_flight_info, delete_flight_record, lock_flight, release_flight_lock, @@ -55,12 +57,13 @@ PlanningActivityResult, ) from monitoring.monitorlib.clients.mock_uss.mock_uss_scd_injection_api import ( + MockUssFlightBehavior, MockUSSInjectFlightRequest, ) from monitoring.monitorlib.errors import stacktrace_string from monitoring.monitorlib.fetch import QueryError from monitoring.monitorlib.geo import Polygon -from monitoring.monitorlib.geotemporal import Volume4D +from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection from monitoring.monitorlib.idempotency import idempotent_request from monitoring.monitorlib.scd_automated_testing.scd_injection_api import ( SCOPE_SCD_QUALIFIER_INJECT, @@ -73,7 +76,7 @@ @webapp.route("/scdsc/v1/status", methods=["GET"]) @requires_scope(SCOPE_SCD_QUALIFIER_INJECT) -def scdsc_injection_status() -> tuple[str, int]: +def scdsc_injection_status() -> tuple[str | flask.Response, int]: """Implements USS status in SCD automated testing injection API.""" json, code = injection_status() return flask.jsonify(json), code @@ -88,7 +91,7 @@ def injection_status() -> tuple[dict, int]: @webapp.route("/scdsc/v1/capabilities", methods=["GET"]) @requires_scope(SCOPE_SCD_QUALIFIER_INJECT) -def scdsc_scd_capabilities() -> tuple[str, int]: +def scdsc_scd_capabilities() -> tuple[str | flask.Response, int]: """Implements USS capabilities in SCD automated testing injection API.""" json, code = scd_capabilities() return flask.jsonify(json), code @@ -110,7 +113,7 @@ def scd_capabilities() -> tuple[dict, int]: @webapp.route("/scdsc/v1/flights/", methods=["PUT"]) @requires_scope(SCOPE_SCD_QUALIFIER_INJECT) @idempotent_request() -def scdsc_inject_flight(flight_id: str) -> tuple[str, int]: +def scdsc_inject_flight(flight_id: str) -> tuple[str | flask.Response, int]: """Implements flight injection in SCD automated testing injection API.""" def log(msg): @@ -125,27 +128,33 @@ def log(msg): except ValueError as e: msg = f"Create flight {flight_id} unable to parse JSON: {e}" return msg, 400 - existing_flight = lock_flight(flight_id, log) + mock_uss_flight_id = MockUSSFlightID(flight_id) + existing_flight = lock_flight(mock_uss_flight_id, log) # Construct potential new flight flight_info = FlightInfo.from_scd_inject_flight_request(req_body) - op_intent = op_intent_from_flightinfo(flight_info, flight_id) - new_flight = FlightRecord( - flight_info=flight_info, - op_intent=op_intent, - mod_op_sharing_behavior=req_body.behavior if "behavior" in req_body else None, - ) try: - resp = inject_flight(flight_id, new_flight, existing_flight) + resp = inject_flight( + mock_uss_flight_id, + flight_info, + req_body.behavior if "behavior" in req_body else None, + existing_flight, + ) finally: - release_flight_lock(flight_id, log) - return flask.jsonify(resp.to_inject_flight_response()), 200 + release_flight_lock(mock_uss_flight_id, log) + api_response = resp.to_inject_flight_response() + if "as_planned" in resp and resp.as_planned: + # Append as_planned as an additional field formatted according to flight_planning API to ease continuing support + # of legacy scd injection API + api_response["as_planned"] = resp.as_planned.to_flight_plan() + return flask.jsonify(api_response), 200 def inject_flight( - flight_id: str, - new_flight: FlightRecord, + flight_id: MockUSSFlightID, + flight_info: FlightInfo, + mod_op_sharing_behavior: MockUssFlightBehavior | None, existing_flight: FlightRecord | None, ) -> PlanningActivityResponse: pid = os.getpid() @@ -159,17 +168,29 @@ def log(msg: str): ) def unsuccessful( - result: PlanningActivityResult, msg: str, has_conflict=None + result: PlanningActivityResult, msg: str ) -> PlanningActivityResponse: return PlanningActivityResponse( - flight_id=flight_id, + flight_id=FlightID(flight_id), queries=[], activity_result=result, flight_plan_status=old_status, notes=msg, - has_conflict=has_conflict, ) + try: + flight_info = adjust_flight_info(flight_info) + except ValueError as e: + return unsuccessful(PlanningActivityResult.Rejected, str(e)) + + op_intent = op_intent_from_flightinfo(flight_info, str(uuid.uuid4())) + new_flight = FlightRecord( + flight_info=flight_info, + op_intent=op_intent, + mod_op_sharing_behavior=mod_op_sharing_behavior, + ) + assert new_flight.op_intent + # Validate request try: if locality.is_uspace_applicable(): @@ -178,6 +199,16 @@ def unsuccessful( except PlanningError as e: return unsuccessful(PlanningActivityResult.Rejected, str(e)) + if not locality.uses_cmsa(): + if new_flight.op_intent.reference.state in [ + scd_api.OperationalIntentState.Nonconforming, + scd_api.OperationalIntentState.Contingent, + ]: + return unsuccessful( + PlanningActivityResult.NotSupported, + f"The current locality {locality} does not support CMSA, flight cannot transition to {new_flight.op_intent.reference.state}", + ) + step_name = "performing unknown operation" notes: str | None = None try: @@ -204,11 +235,11 @@ def unsuccessful( # Store flight in database step_name = "storing flight in database" log("Storing flight in database") - with db as tx: - tx.flights[flight_id] = record + with db.transact() as tx: + tx.value.flights[flight_id] = record if has_conflict: # Record virtual user notification that this flight caused/has a conflict - tx.flight_planning_notifications.append( + tx.value.flight_planning_notifications.append( UserNotification( type=UserNotificationType.CausedConflict, observed_at=StringBasedDateTime(arrow.utcnow().datetime), @@ -220,10 +251,11 @@ def unsuccessful( log("Complete.") return PlanningActivityResponse( - flight_id=flight_id, + flight_id=FlightID(flight_id), queries=[], # TODO: Add queries used activity_result=PlanningActivityResult.Completed, flight_plan_status=FlightPlanStatus.from_flightinfo(record.flight_info), + as_planned=flight_info, notes=notes, ) except (ValueError, ConnectionError) as e: @@ -246,9 +278,10 @@ def unsuccessful( @webapp.route("/scdsc/v1/flights/", methods=["DELETE"]) @requires_scope(SCOPE_SCD_QUALIFIER_INJECT) -def scdsc_delete_flight(flight_id: str) -> tuple[str, int]: +def scdsc_delete_flight(flight_id: str) -> tuple[str | flask.Response, int]: """Implements flight deletion in SCD automated testing injection API.""" - del_resp, status_code = delete_flight(flight_id) + mock_uss_flight_id = MockUSSFlightID(flight_id) + del_resp, status_code = delete_flight(mock_uss_flight_id) if del_resp.activity_result == PlanningActivityResult.Completed: if del_resp.flight_plan_status != FlightPlanStatus.Closed: @@ -271,7 +304,7 @@ def scdsc_delete_flight(flight_id: str) -> tuple[str, int]: return flask.jsonify(resp), status_code -def delete_flight(flight_id) -> tuple[PlanningActivityResponse, int]: +def delete_flight(flight_id: MockUSSFlightID) -> tuple[PlanningActivityResponse, int]: pid = os.getpid() def log(msg: str): @@ -286,7 +319,7 @@ def log(msg: str): def unsuccessful(msg: str) -> PlanningActivityResponse: return PlanningActivityResponse( - flight_id=flight_id, + flight_id=FlightID(flight_id), queries=[], activity_result=PlanningActivityResult.Failed, flight_plan_status=old_status, @@ -296,44 +329,49 @@ def unsuccessful(msg: str) -> PlanningActivityResponse: if flight is None: return unsuccessful(f"Flight {flight_id} does not exist"), 404 - # Delete operational intent from DSS - step_name = "performing unknown operation" notes: str | None = None - try: - step_name = f"deleting operational intent {flight.op_intent.reference.id} with OVN {flight.op_intent.reference.ovn} from DSS" - log(step_name) - notif_errors = delete_op_intent(flight.op_intent.reference, log) - if notif_errors: - notif_errors_messages = [ - f"{url}: {str(err)}" for url, err in notif_errors.items() - ] - notes = f"Deletion succeeded, but notification to some subscribers failed: {'; '.join(notif_errors_messages)}" + if flight.op_intent: + # Delete operational intent from DSS + step_name = "performing unknown operation" + try: + step_name = f"deleting operational intent {flight.op_intent.reference.id} with OVN {flight.op_intent.reference.ovn} from DSS" + log(step_name) + notif_errors = delete_op_intent(flight.op_intent.reference, log) + if notif_errors: + notif_errors_messages = [ + f"{url}: {str(err)}" for url, err in notif_errors.items() + ] + notes = f"Deletion succeeded, but notification to some subscribers failed: {'; '.join(notif_errors_messages)}" + log(notes) + + except (ValueError, ConnectionError) as e: + notes = f"{e.__class__.__name__} while {step_name} for flight {flight_id}: {str(e)}" log(notes) - - except (ValueError, ConnectionError) as e: - notes = ( - f"{e.__class__.__name__} while {step_name} for flight {flight_id}: {str(e)}" - ) - log(notes) - return unsuccessful(notes), 500 - except requests.exceptions.ConnectionError as e: - notes = f"Connection error to {e.request.method} {e.request.url} while {step_name} for flight {flight_id}: {str(e)}" - log(notes) - response = unsuccessful(notes) - response["stacktrace"] = stacktrace_string(e) - return response, 500 - except QueryError as e: - notes = f"Unexpected response from remote server while {step_name} for flight {flight_id}: {str(e)}" - log(notes) - response = unsuccessful(notes) - response["queries"] = e.queries - response["stacktrace"] = e.stacktrace - return response, 500 + # Activity result is Failed, but we executed the activity successfully + return unsuccessful(notes), 200 + except requests.exceptions.ConnectionError as e: + if e.request: + notes = f"Connection error to {e.request.method} {e.request.url} while {step_name} for flight {flight_id}: {str(e)}" + else: + notes = f"Connection error missing .request while {step_name} for flight {flight_id}: {str(e)}" + log(notes) + response = unsuccessful(notes) + response["stacktrace"] = stacktrace_string(e) + # Activity result is Failed, but we executed the activity successfully + return response, 200 + except QueryError as e: + notes = f"Unexpected response from remote server while {step_name} for flight {flight_id}: {str(e)}" + log(notes) + response = unsuccessful(notes) + response["queries"] = e.queries + response["stacktrace"] = e.stacktrace + # Activity result is Failed, but we executed the activity successfully + return response, 200 log("Complete.") return ( PlanningActivityResponse( - flight_id=flight_id, + flight_id=FlightID(flight_id), queries=[], activity_result=PlanningActivityResult.Completed, flight_plan_status=FlightPlanStatus.Closed, @@ -346,7 +384,7 @@ def unsuccessful(msg: str) -> PlanningActivityResponse: @webapp.route("/scdsc/v1/clear_area_requests", methods=["POST"]) @requires_scope(SCOPE_SCD_QUALIFIER_INJECT) @idempotent_request() -def scdsc_clear_area() -> tuple[str, int]: +def scdsc_clear_area() -> tuple[str | flask.Response, int]: try: json = flask.request.json if json is None: @@ -373,8 +411,8 @@ def scdsc_clear_area() -> tuple[str, int]: def clear_area(extent: Volume4D) -> ClearAreaResponse: flights_deleted: list[FlightID] = [] flight_deletion_errors: dict[FlightID, dict] = {} - op_intents_removed: list[f3548v21.EntityOVN] = [] - op_intent_removal_errors: dict[f3548v21.EntityOVN, dict] = {} + op_intents_removed: list[f3548v21.EntityID] = [] + op_intent_removal_errors: dict[f3548v21.EntityID, dict] = {} def make_result(error: dict | None = None) -> ClearAreaResponse: resp = ClearAreaResponse( @@ -407,12 +445,17 @@ def make_result(error: dict | None = None) -> ClearAreaResponse: op_intent_refs = scd_client.query_operational_intent_references( utm_client, vol4 ) - op_intent_ids = {oi.id for oi in op_intent_refs} # Try to remove all relevant flights normally for flight_id, flight in db.value.flights.items(): - # TODO: Check for intersection with flight's area rather than just relying on DSS query - if flight.op_intent.reference.id not in op_intent_ids: + if flight is None: + # Flight is locked in the process of being created + continue + + if not flight.flight_info.basic_information.area.intersects_vol4s( + Volume4DCollection([extent]) + ): + # Flight is not in the area being cleared continue del_resp, _status_code = delete_flight(flight_id) @@ -420,15 +463,16 @@ def make_result(error: dict | None = None) -> ClearAreaResponse: del_resp.activity_result == PlanningActivityResult.Completed and del_resp.flight_plan_status == FlightPlanStatus.Closed ): - flights_deleted.append(flight_id) - op_intents_removed.append(flight.op_intent.reference.id) + flights_deleted.append(FlightID(flight_id)) + if flight.op_intent: + op_intents_removed.append(flight.op_intent.reference.id) else: notes = f"Deleting known flight {flight_id} {del_resp.activity_result} with `flight_plan_status`={del_resp.flight_plan_status}" if "notes" in del_resp and del_resp.notes: notes += ": " + del_resp.notes - flight_deletion_errors[flight_id] = {"notes": notes} + flight_deletion_errors[FlightID(flight_id)] = {"notes": notes} - # Try to delete every remaining operational intent that we manage + # Try to delete every remaining operational intent that we manage in the area self_sub = utm_client.auth_adapter.get_sub() op_intent_refs = [ oi @@ -453,10 +497,10 @@ def make_result(error: dict | None = None) -> ClearAreaResponse: } # Clear the op intent cache for every op intent removed - with db as tx: + with db.transact() as tx: for op_intent_id in op_intents_removed: - if op_intent_id in tx.cached_operations: - del tx.cached_operations[op_intent_id] + if op_intent_id in tx.value.cached_operations: + del tx.value.cached_operations[op_intent_id] except (ValueError, ConnectionError) as e: msg = f"{e.__class__.__name__} while {step_name}: {str(e)}" diff --git a/monitoring/mock_uss/server.py b/monitoring/mock_uss/server.py index 1800d1eb2d..8e89e6bdf1 100644 --- a/monitoring/mock_uss/server.py +++ b/monitoring/mock_uss/server.py @@ -4,7 +4,7 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import UTC, datetime, timedelta -from enum import Enum +from enum import StrEnum from multiprocessing import Process import arrow @@ -14,12 +14,12 @@ from loguru import logger from ..monitorlib.errors import stacktrace_string -from .database import Database, PeriodicTaskStatus, TaskError, db +from .database import PeriodicTaskStatus, TaskError, db MAX_PERIODIC_LATENCY = timedelta(seconds=5) -class TaskTrigger(str, Enum): +class TaskTrigger(StrEnum): Setup = "Setup" Shutdown = "Shutdown" @@ -87,10 +87,10 @@ def shutdown_task_decorator(func): def _run_one_time_tasks(self, trigger: TaskTrigger): tasks: dict[str, OneTimeServerTask] = {} - with db as tx: + with db.transact() as tx: for task_name, task in self._one_time_tasks.items(): - if task.trigger == trigger and task_name not in tx.one_time_tasks: - tx.one_time_tasks.append(task_name) + if task.trigger == trigger and task_name not in tx.value.one_time_tasks: + tx.value.one_time_tasks.append(task_name) tasks[task_name] = task if not tasks: logger.info(f"No {trigger} tasks to initiate from process ID {os.getpid()}") @@ -105,8 +105,8 @@ def _run_one_time_tasks(self, trigger: TaskTrigger): try: setup_task.run() except Exception as e: - with db as tx: - tx.task_errors.append(TaskError.from_exception(trigger, e)) + with db.transact() as tx: + tx.value.task_errors.append(TaskError.from_exception(trigger, e)) if trigger == TaskTrigger.Shutdown: logger.error( f"{type(e).__name__} error in '{task_name}' on process ID {os.getpid()} while shutting down mock_uss: {str(e)}\n{stacktrace_string(e)}" @@ -148,11 +148,10 @@ def set_task_period(self, task_name: str, period: timedelta | None): raise ValueError( f"Periodic task '{task_name}' is not declared, so its period cannot be set" ) - with db as tx: - assert isinstance(tx, Database) - if task_name not in tx.periodic_tasks: - tx.periodic_tasks[task_name] = PeriodicTaskStatus() - tx.periodic_tasks[task_name].period = ( + with db.transact() as tx: + if task_name not in tx.value.periodic_tasks: + tx.value.periodic_tasks[task_name] = PeriodicTaskStatus() + tx.value.periodic_tasks[task_name].period = ( StringBasedTimeDelta(period) if period is not None else None ) @@ -172,21 +171,20 @@ def _periodic_tasks_daemon_loop(self): # Determine what to do on this loop (execute task or wait) task_to_execute = None next_check = None - with db as tx: - assert isinstance(tx, Database) - tx.most_recent_periodic_check = StringBasedDateTime( + with db.transact() as tx: + tx.value.most_recent_periodic_check = StringBasedDateTime( datetime.now(UTC) ) # Cancel the loop if we're stopping - if tx.stopping: + if tx.value.stopping: break # Find the earliest scheduled task earliest_task: tuple[str, datetime, PeriodicTaskStatus] | None = ( None ) - for task_name, task in tx.periodic_tasks.items(): + for task_name, task in tx.value.periodic_tasks.items(): if task.executing: # Don't consider executing tasks that are already executing continue @@ -216,7 +214,7 @@ def _periodic_tasks_daemon_loop(self): task_name, t_execute, task = earliest_task if t_execute <= arrow.utcnow().datetime: # We should execute this task immediately - tx.periodic_tasks[task_name] = PeriodicTaskStatus( + tx.value.periodic_tasks[task_name] = PeriodicTaskStatus( last_execution_time=StringBasedDateTime( arrow.utcnow().datetime ), @@ -227,7 +225,7 @@ def _periodic_tasks_daemon_loop(self): else: # We need to wait some time before executing this task next_check = t_execute - # + # if task_to_execute: # Execute the selected task right now @@ -235,8 +233,8 @@ def _periodic_tasks_daemon_loop(self): f"Executing '{task_to_execute}' periodic task from process {os.getpid()}" ) self._periodic_tasks[task_to_execute].run() - with db as tx: - periodic_task = tx.periodic_tasks[task_to_execute] + with db.transact() as tx: + periodic_task = tx.value.periodic_tasks[task_to_execute] periodic_task.executing = False if ( "period" in periodic_task @@ -260,8 +258,10 @@ def _periodic_tasks_daemon_loop(self): logger.error( f"Shutting down mock_uss due to {type(e).__name__} error while executing '{task_to_execute}' periodic task: {str(e)}\n{stacktrace_string(e)}" ) - with db as tx: - tx.task_errors.append(TaskError.from_exception(TaskTrigger.Setup, e)) + with db.transact() as tx: + tx.value.task_errors.append( + TaskError.from_exception(TaskTrigger.Setup, e) + ) self.stop() finally: logger.info(f"Periodic task daemon for process {os.getpid()} exited") @@ -271,10 +271,10 @@ def is_stopping(self) -> bool: def stop(self): send_signal = False - with db as tx: - if not tx.stopping: + with db.transact() as tx: + if not tx.value.stopping: send_signal = True - tx.stopping = True + tx.value.stopping = True if send_signal: logger.info( f"Initiating shutdown of MockUSS process {self._pid} from process {os.getpid()}" diff --git a/monitoring/mock_uss/start.sh b/monitoring/mock_uss/start.sh index 887d672f8a..fa0e6477db 100755 --- a/monitoring/mock_uss/start.sh +++ b/monitoring/mock_uss/start.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script is intended to be called from within a Docker container running # mock_uss via the interuss/monitoring image. In that context, this script is # the entrypoint into the mock_uss server. @@ -27,4 +29,4 @@ uv run gunicorn \ --workers=4 \ --worker-tmp-dir="/dev/shm" \ "--bind=0.0.0.0:${port}" \ - monitoring.mock_uss:webapp + monitoring.mock_uss.app:webapp diff --git a/monitoring/mock_uss/start_all_local_mocks.sh b/monitoring/mock_uss/start_all_local_mocks.sh index 0434ab3ce6..a0f2fea91a 100755 --- a/monitoring/mock_uss/start_all_local_mocks.sh +++ b/monitoring/mock_uss/start_all_local_mocks.sh @@ -17,4 +17,4 @@ cd monitoring || exit 1 make image ) -./monitoring/mock_uss/run_locally.sh up --wait --wait-timeout 60 -d +./monitoring/mock_uss/run_locally.sh up --wait --wait-timeout 90 -d diff --git a/monitoring/mock_uss/stop_all_local_mocks.sh b/monitoring/mock_uss/stop_all_local_mocks.sh index d0eae989db..42dbeddaca 100755 --- a/monitoring/mock_uss/stop_all_local_mocks.sh +++ b/monitoring/mock_uss/stop_all_local_mocks.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # Find and change to repo root directory OS=$(uname) if [[ "$OS" == "Darwin" ]]; then diff --git a/monitoring/mock_uss/tracer/README.md b/monitoring/mock_uss/tracer/README.md index e1a9655716..512c53979b 100644 --- a/monitoring/mock_uss/tracer/README.md +++ b/monitoring/mock_uss/tracer/README.md @@ -38,5 +38,34 @@ container in order to use the tracer subscribe capability. Visit /tracer/logs to see a list of log entries recorded by tracer while the current session has been running. +## Log download +To download the full set of current tracer logs, visit /tracer/logs.zip + ## Invocation An instance of tracer-enabled mock_uss is brought up as part of the [local deployment](../README.md#local-deployment). It can also be deployed [with Google Cloud Platform](../deployment/gcp) when configured appropriately. + +## Offline historical KML generation + +With a large number of log files, KML generation via the server endpoint can +require a prohibitive amount of time. To generate a historical KML in these +cases, the [make_historical_kml utility](./make_historical_kml.py) can be used +to parse a folder of logs (generally acquired from downloading a .zip file of +logs while the server is active) into a KML file. + +To use this utility via docker, first set `LOG_PATH` to the folder containing the unzipped log files: + +```shell +export LOG_PATH=/path/to/log/files +``` + +Then, invoke the tool, writing to `historical.kml` in the log file folder: + +```shell +docker container run \ + -u "$(id -u):$(id -g)" \ + -v "$LOG_PATH:/logs" \ + interuss/monitoring \ + uv run /app/monitoring/mock_uss/tracer/make_historical_kml.py \ + --logfolder /logs \ + --kmlfile /logs/historical.kml +``` diff --git a/monitoring/mock_uss/tracer/config.py b/monitoring/mock_uss/tracer/config.py index de168da963..0cccafdab0 100644 --- a/monitoring/mock_uss/tracer/config.py +++ b/monitoring/mock_uss/tracer/config.py @@ -1,4 +1,4 @@ -from monitoring.mock_uss import import_environment_variable, require_config_value +from monitoring.mock_uss.app import import_environment_variable, require_config_value from monitoring.mock_uss.config import KEY_BASE_URL, KEY_DSS_URL KEY_TRACER_OUTPUT_FOLDER = "MOCK_USS_TRACER_OUTPUT_FOLDER" diff --git a/monitoring/mock_uss/tracer/context.py b/monitoring/mock_uss/tracer/context.py index dfe19e08df..91b938f2d8 100644 --- a/monitoring/mock_uss/tracer/context.py +++ b/monitoring/mock_uss/tracer/context.py @@ -1,8 +1,4 @@ -import yaml -from implicitdict import StringBasedDateTime -from yaml.representer import Representer - -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.config import KEY_AUTH_SPEC, KEY_DSS_URL from monitoring.mock_uss.tracer.config import ( KEY_TRACER_KML_FOLDER, @@ -10,20 +6,22 @@ KEY_TRACER_OUTPUT_FOLDER, ) from monitoring.mock_uss.tracer.observation_areas import ObservationAreaID -from monitoring.mock_uss.tracer.tracerlog import Logger +from monitoring.mock_uss.tracer.tracerlog import DummyLogger, Logger from monitoring.monitorlib import infrastructure from monitoring.monitorlib.auth import make_auth_adapter from monitoring.monitorlib.fetch import scd -from monitoring.monitorlib.infrastructure import AuthAdapter, AuthSpec, UTMClientSession +from monitoring.monitorlib.infrastructure import ( + AuthAdapter, + AuthSpec, + UTMClientSession, + utm_client_session_factory, +) from monitoring.monitorlib.rid import RIDVersion -yaml.add_representer(StringBasedDateTime, Representer.represent_str) - - scd_cache: dict[ObservationAreaID, dict[str, scd.FetchedEntity]] = {} -def _get_tracer_logger() -> Logger | None: +def _get_tracer_logger() -> Logger: kml_server = webapp.config[KEY_TRACER_KML_SERVER] kml_folder = webapp.config[KEY_TRACER_KML_FOLDER] output_folder = webapp.config[KEY_TRACER_OUTPUT_FOLDER] @@ -36,7 +34,7 @@ def _get_tracer_logger() -> Logger | None: if kml_server else None ) - return Logger(output_folder, kml_session) if output_folder else None + return Logger(output_folder, kml_session) if output_folder else DummyLogger() tracer_logger: Logger = _get_tracer_logger() @@ -57,14 +55,15 @@ def resolve_auth_spec(requested_auth_spec: AuthSpec | None) -> AuthSpec: return requested_auth_spec -def resolve_rid_dss_base_url(dss_base_url: str, rid_version: RIDVersion) -> str: +def resolve_rid_dss_base_url(dss_base_url: str | None, rid_version: RIDVersion) -> str: if not dss_base_url: - if KEY_DSS_URL not in webapp.config or not webapp.config[KEY_DSS_URL]: + dss_base_url = webapp.config.get(KEY_DSS_URL) + + if not dss_base_url: raise ValueError( "DSS base URL was not specified explicitly nor in mock_uss_configuration" ) - else: - dss_base_url = webapp.config[KEY_DSS_URL] + if rid_version == RIDVersion.f3411_19: return dss_base_url elif rid_version == RIDVersion.f3411_22a: @@ -75,18 +74,19 @@ def resolve_rid_dss_base_url(dss_base_url: str, rid_version: RIDVersion) -> str: ) -def resolve_scd_dss_base_url(dss_base_url: str) -> str: +def resolve_scd_dss_base_url(dss_base_url: str | None) -> str: if not dss_base_url: - if KEY_DSS_URL not in webapp.config or not webapp.config[KEY_DSS_URL]: + dss_base_url = webapp.config.get(KEY_DSS_URL) + + if not dss_base_url: raise ValueError( "DSS base URL was not specified explicitly nor in mock_uss_configuration" ) - else: - dss_base_url = webapp.config[KEY_DSS_URL] + return dss_base_url def get_client(auth_spec: AuthSpec, dss_base_url: str) -> UTMClientSession: if auth_spec not in _adapters: _adapters[auth_spec] = make_auth_adapter(auth_spec) - return UTMClientSession(dss_base_url, _adapters[auth_spec]) + return utm_client_session_factory.get_session(dss_base_url, _adapters[auth_spec]) diff --git a/monitoring/mock_uss/tracer/database.py b/monitoring/mock_uss/tracer/database.py index 208286256e..07b364961b 100644 --- a/monitoring/mock_uss/tracer/database.py +++ b/monitoring/mock_uss/tracer/database.py @@ -29,7 +29,7 @@ class Database(ImplicitDict): """Interval at which polling of observation areas should occur""" -db = SynchronizedValue( +db = SynchronizedValue[Database]( Database(observation_areas={}), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), Database), ) diff --git a/monitoring/mock_uss/tracer/diff.py b/monitoring/mock_uss/tracer/diff.py index 06502d61fb..5d3d7e5228 100644 --- a/monitoring/mock_uss/tracer/diff.py +++ b/monitoring/mock_uss/tracer/diff.py @@ -1,3 +1,5 @@ +from typing import cast + from monitoring.monitorlib import formatting from monitoring.monitorlib.fetch import rid, scd, summarize @@ -5,9 +7,9 @@ def isa_diff_text(a: rid.FetchedISAs | None, b: rid.FetchedISAs | None) -> str: """Create text to display to a real-time user describing a change in ISAs.""" a_summary = summarize.isas(a) if a else {} - a_summary = summarize.limit_long_arrays(a_summary, 6) + a_summary = cast(dict, summarize.limit_long_arrays(a_summary, 6)) b_summary = summarize.isas(b) if b else {} - b_summary = summarize.limit_long_arrays(b_summary, 6) + b_summary = cast(dict, summarize.limit_long_arrays(b_summary, 6)) if b is not None and b.success and a is not None and not a.success: a_summary = {} if a is not None and a.success and b is not None and not b.success: @@ -26,9 +28,9 @@ def entity_diff_text( if entity_type and "_" in entity_type: entity_type = entity_type[0 : entity_type.index("_")] a_summary = summarize.entities(a, entity_type) if a else {} - a_summary = summarize.limit_long_arrays(a_summary, 6) + a_summary = cast(dict, summarize.limit_long_arrays(a_summary, 6)) b_summary = summarize.entities(b, entity_type) if b else {} - b_summary = summarize.limit_long_arrays(b_summary, 6) + b_summary = cast(dict, summarize.limit_long_arrays(b_summary, 6)) if b is not None and b.success and a is not None and not a.success: a_summary = {} if a is not None and a.success and b is not None and not b.success: diff --git a/monitoring/mock_uss/tracer/kml.py b/monitoring/mock_uss/tracer/kml.py index 3d8873b524..835249b092 100644 --- a/monitoring/mock_uss/tracer/kml.py +++ b/monitoring/mock_uss/tracer/kml.py @@ -3,10 +3,11 @@ import glob import os import re +from abc import abstractmethod from dataclasses import dataclass from datetime import UTC, datetime, timedelta -from enum import Enum -from typing import Protocol +from enum import StrEnum +from typing import Protocol, TypeVar import yaml from implicitdict import ImplicitDict @@ -40,10 +41,11 @@ def __enter__(self): self._start_time = datetime.now(UTC) def __exit__(self, exc_type, exc_val, exc_tb): - self.elapsed_time += datetime.now(UTC) - self._start_time + if self._start_time: + self.elapsed_time += datetime.now(UTC) - self._start_time -class VolumeType(str, Enum): +class VolumeType(StrEnum): OperationalIntent = "OperationalIntent" @@ -57,10 +59,14 @@ class HistoricalVolumesCollection: active_at: datetime -class HistoricalVolumesRenderer(Protocol): +TracerLogEntryType = TypeVar("TracerLogEntryType", bound=TracerLogEntry) + + +class HistoricalVolumesRenderer[TracerLogEntryType](Protocol): + @abstractmethod def __call__( self, - log_entry: TracerLogEntry, + log_entry: TracerLogEntryType, existing_volume_collections: list[HistoricalVolumesCollection], ) -> list[HistoricalVolumesCollection]: """Function that generates named collections of 4D volumes from a tracer log entry. @@ -71,6 +77,7 @@ def __call__( Returns: Collection of 4D volume collections. """ + raise NotImplementedError @dataclass @@ -126,6 +133,9 @@ def _historical_volumes_op_intent_notification( existing_volume_collections: list[HistoricalVolumesCollection], ) -> list[HistoricalVolumesCollection]: try: + if log_entry.request.json is None: + raise ValueError("No json data in log entry") + req = ImplicitDict.parse( log_entry.request.json, PutOperationalIntentDetailsParameters ) @@ -136,7 +146,7 @@ def _historical_volumes_op_intent_notification( return [] assert isinstance(req, PutOperationalIntentDetailsParameters) - claims = get_token_claims(log_entry.request.headers) + claims = get_token_claims(log_entry.request.headers or {}) manager = claims.get("sub", "[Unknown manager]") name = f"{manager} {req.operational_intent_id}" @@ -148,7 +158,7 @@ def _historical_volumes_op_intent_notification( else: version = "[deleted]" state = "Ended" - volumes = [] + volumes = Volume4DCollection() # See if this op intent version already has a volumes collection already_defined = False @@ -183,6 +193,9 @@ def _historical_volumes_op_intent_poll( # Add newly-polled operational intents for op_intent_id, query in log_entry.poll.uss_queries.items(): try: + if query.json_result is None: + raise ValueError("No json result in query") + resp = ImplicitDict.parse( query.json_result, GetOperationalIntentDetailsResponse ) @@ -222,6 +235,9 @@ def _historical_volumes_op_intent_poll( # Remove any existing operational intents that no longer exist as of this poll for cached_op_intent_id, cached_query in log_entry.poll.cached_uss_queries.items(): try: + if cached_query.json_result is None: + raise ValueError("No json result in query") + resp = ImplicitDict.parse( cached_query.json_result, GetOperationalIntentDetailsResponse ) @@ -274,26 +290,38 @@ class VolumesFolder: def truncate(self, latest_time: Time) -> None: to_remove = [] for v in self.volumes: - if v.volume.time_start.datetime > latest_time.datetime: + if ( + v.volume.time_start + and v.volume.time_start.datetime > latest_time.datetime + ): to_remove.append(v) - elif v.volume.time_end.datetime > latest_time.datetime: + elif ( + v.volume.time_end and v.volume.time_end.datetime > latest_time.datetime + ): v.volume.time_end = latest_time for v in to_remove: self.volumes.remove(v) for c in self.children: c.truncate(latest_time) - def to_kml_folder(self) -> kml.Folder: - def dt(t: Time) -> int: - return round((t.datetime - self.reference_time.datetime).total_seconds()) - + def to_kml_folder(self): if self.reference_time: description = f"Relative to {self.reference_time}" folder = kml.Folder(kml.name(self.name), kml.description(description)) else: folder = kml.Folder(kml.name(self.name)) + for v in self.volumes: - name = f"{v.name} {dt(v.volume.time_start)}s-{dt(v.volume.time_end)}s" + name = v.name + + if self.reference_time: + base_time = self.reference_time.datetime + + def dt(t: Time) -> int: + return round((t.datetime - base_time).total_seconds()) + + name = f"{name} {dt(v.volume.time_start) if v.volume.time_start else '?'}s-{dt(v.volume.time_end) if v.volume.time_end else '?'}s" + folder.append( make_placemark_from_volume(v.volume, name=name, style_url=v.style) ) @@ -323,6 +351,8 @@ def render_historical_kml(log_folder: str) -> str: log_files = glob.glob(os.path.join(log_folder, "*.yaml")) log_files.sort() for log_file in log_files: + logger.debug(f"Processing {log_file}") + if "nochange_queries" in log_file: continue # This is a known case where we don't want to print a warning @@ -408,13 +438,13 @@ def render_historical_kml(log_folder: str) -> str: version_folder.children.append(future_folder) for i, v in enumerate(hvc.volumes): - if v.time_end.datetime <= hvc.active_at: + if v.time_end and v.time_end.datetime <= hvc.active_at: # This volume ended before the collection was declared, so it never actually existed continue - if v.time_start.datetime < hvc.active_at: + if v.time_start and v.time_start.datetime < hvc.active_at: # Volume is declared in the past, but it's only visible starting now v.time_start = t_hvc - elif v.time_start.datetime > hvc.active_at: + elif v.time_start and v.time_start.datetime > hvc.active_at: # Add a "future" volume between when this volume was declared and its start time future_v = Volume4D(v) future_v.time_end = v.time_start diff --git a/monitoring/mock_uss/tracer/log_types.py b/monitoring/mock_uss/tracer/log_types.py index f7a5ef33a4..14c8df41db 100644 --- a/monitoring/mock_uss/tracer/log_types.py +++ b/monitoring/mock_uss/tracer/log_types.py @@ -3,7 +3,7 @@ import sys from abc import abstractmethod -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from monitoring.monitorlib.fetch import RequestDescription, summarize from monitoring.monitorlib.fetch import rid as rid_fetch @@ -124,7 +124,7 @@ def prefix_code() -> str: existing_subscription: rid_fetch.FetchedSubscription """Subscription, as read from the DSS just before deletion.""" - deleted_subscription: rid_mutate.ChangedSubscription | None + deleted_subscription: Optional[rid_mutate.ChangedSubscription] """Subscription returned from DSS upon deletion.""" @@ -214,7 +214,7 @@ def prefix_code() -> str: existing_subscription: scd_fetch.FetchedSubscription """Subscription, as read from the DSS just before deletion.""" - deleted_subscription: scd_mutate.MutatedSubscription | None + deleted_subscription: Optional[scd_mutate.MutatedSubscription] """Subscription returned from DSS upon deletion.""" @@ -270,7 +270,7 @@ class ObservationAreaImportError(TracerLogEntry): def prefix_code() -> str: return "import_obs_areas_error" - rid_subscriptions: rid_fetch.FetchedSubscriptions | None + rid_subscriptions: Optional[rid_fetch.FetchedSubscriptions] """Result of attempting to fetch RID subscriptions""" diff --git a/monitoring/mock_uss/tracer/make_historical_kml.py b/monitoring/mock_uss/tracer/make_historical_kml.py new file mode 100644 index 0000000000..428f8ebbff --- /dev/null +++ b/monitoring/mock_uss/tracer/make_historical_kml.py @@ -0,0 +1,36 @@ +import argparse +import os +import sys + +from monitoring.mock_uss.tracer.kml import render_historical_kml + + +def main(log_folder: str, kml_file: str) -> int: + kml_text = render_historical_kml(log_folder) + with open(kml_file, "w") as f: + f.write(kml_text) + return os.EX_OK + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Generate an historical KML based on a folder of tracer logs" + ) + + parser.add_argument( + "--logfolder", + type=str, + default=None, + help="Path to the folder containing tracer log files", + ) + + parser.add_argument( + "--kmlfile", type=str, default=None, help="Path to the KML file to create" + ) + + return parser.parse_args() + + +if __name__ == "__main__": + args = _parse_args() + sys.exit(main(args.logfolder, args.kmlfile)) diff --git a/monitoring/mock_uss/tracer/observation_areas.py b/monitoring/mock_uss/tracer/observation_areas.py index dc11b4da1a..794e94bda7 100644 --- a/monitoring/mock_uss/tracer/observation_areas.py +++ b/monitoring/mock_uss/tracer/observation_areas.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.infrastructure import AuthSpec @@ -45,7 +45,7 @@ class F3548ObservationArea(ImplicitDict): poll: bool """This area observes by periodically polling for information.""" - subscription_id: str | None = None + subscription_id: Optional[str] = None """The F3548 subscription ID established to provide observation via notifications.""" @@ -58,27 +58,29 @@ class ObservationArea(ImplicitDict): area: Volume4D """Spatial-temporal area being observed.""" - f3411: F3411ObservationArea | None = None + f3411: Optional[F3411ObservationArea] = None """How F3411 information is being observed (or not observed, if not specified).""" - f3548: F3548ObservationArea | None = None + f3548: Optional[F3548ObservationArea] = None """How F3548 information is being observed (or not observed, if not specified).""" @property def polls(self) -> bool: """Whether any of the observation activity involves periodic polling.""" - return (self.f3411 and self.f3411.poll) or (self.f3548 and self.f3548.poll) + return bool(self.f3411 and self.f3411.poll) or bool( + self.f3548 and self.f3548.poll + ) class F3411ObservationAreaRequest(ImplicitDict): """How to observe F3411 activity.""" - auth_spec: AuthSpec | None = None + auth_spec: Optional[AuthSpec] = None """If specified, use this auth spec when performing observation activities. If not specified or blank, use auth spec provided on the command line.""" - dss_base_url: str | None = None + dss_base_url: Optional[str] = None """If specified, use the DSS at this base URL when performing relevant observation activities. If not specified or blank, use DSS URL provided on the command line.""" @@ -96,12 +98,12 @@ class F3411ObservationAreaRequest(ImplicitDict): class F3548ObservationAreaRequest(ImplicitDict): """How to observe F3548 activity.""" - auth_spec: AuthSpec | None = None + auth_spec: Optional[AuthSpec] = None """If specified, use this auth spec when performing observation activities. If not specified or blank, use auth spec provided on the command line.""" - dss_base_url: str | None = None + dss_base_url: Optional[str] = None """If specified, use the DSS at this base URL when performing relevant observation activities. If not specified or blank, use DSS URL provided on the command line.""" @@ -125,16 +127,18 @@ class ObservationAreaRequest(ImplicitDict): area: Volume4D """Spatial-temporal area that should be observed.""" - f3411: F3411ObservationAreaRequest | None = None + f3411: Optional[F3411ObservationAreaRequest] = None """How to observe F3411 (NetRID) activity.""" - f3548: F3548ObservationAreaRequest | None = None + f3548: Optional[F3548ObservationAreaRequest] = None """How to observe F3548 (strategic coordination, conformance monitoring, and constraints) activity.""" @property def polls(self) -> bool: """Whether any of the observation activity requested involves periodic polling.""" - return (self.f3411 and self.f3411.poll) or (self.f3548 and self.f3548.poll) + return bool(self.f3411 and self.f3411.poll) or bool( + self.f3548 and self.f3548.poll + ) class ListObservationAreasResponse(ImplicitDict): @@ -164,7 +168,7 @@ class ImportObservationAreasRequest(ImplicitDict): area: Volume4D """Spatial-temporal area containing subscriptions to be imported.""" - f3411: RIDVersion | None = None + f3411: Optional[RIDVersion] = None """If specified, search for subscriptions using this F3411 version.""" f3548: bool = False diff --git a/monitoring/mock_uss/tracer/routes/__init__.py b/monitoring/mock_uss/tracer/routes/__init__.py index e88190dc50..03e3632e13 100644 --- a/monitoring/mock_uss/tracer/routes/__init__.py +++ b/monitoring/mock_uss/tracer/routes/__init__.py @@ -6,7 +6,7 @@ from loguru import logger from termcolor import colored -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.tracer import context from monitoring.mock_uss.tracer.log_types import BadRoute from monitoring.mock_uss.tracer.routes import observation_areas as observation_areas diff --git a/monitoring/mock_uss/tracer/routes/observation_areas.py b/monitoring/mock_uss/tracer/routes/observation_areas.py index de7d03fa88..e8751c56ae 100644 --- a/monitoring/mock_uss/tracer/routes/observation_areas.py +++ b/monitoring/mock_uss/tracer/routes/observation_areas.py @@ -8,7 +8,7 @@ from implicitdict import ImplicitDict, StringBasedDateTime from loguru import logger -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.tracer import context from monitoring.mock_uss.tracer.database import db from monitoring.mock_uss.tracer.log_types import ( @@ -36,11 +36,13 @@ @webapp.route("/tracer/observation_areas", methods=["GET"]) -@ui_auth.login_required +@ui_auth.login_required() def tracer_list_observation_areas() -> flask.Response: - with db as tx: + with db.transact() as tx: result = ListObservationAreasResponse( - areas=[redact_observation_area(a) for a in tx.observation_areas.values()] + areas=[ + redact_observation_area(a) for a in tx.value.observation_areas.values() + ] ) return flask.jsonify(result) @@ -62,24 +64,24 @@ def tracer_upsert_observation_area( msg = f"Upsert observation area for tracer unable to parse JSON: {e}" return msg, 400 - with db as tx: + with db.transact() as tx: # Determine if this observation area triggers the need to start polling - if tx.observation_areas: - poll_interval = tx.polling_interval.timedelta - for a in tx.observation_areas.values(): + if tx.value.observation_areas: + poll_interval = tx.value.polling_interval.timedelta + for a in tx.value.observation_areas.values(): if a.polls: poll_interval = None break else: poll_interval = ( - tx.polling_interval.timedelta if request.area.polls else None + tx.value.polling_interval.timedelta if request.area.polls else None ) - if area_id in tx.observation_areas: + if area_id in tx.value.observation_areas: # Request is to mutate an existing observation area, so we'll first just delete the existing area - delete_observation_area(tx.observation_areas[area_id]) + delete_observation_area(tx.value.observation_areas[area_id]) created = create_observation_area(area_id, request.area) - tx.observation_areas[area_id] = created + tx.value.observation_areas[area_id] = created if poll_interval is not None: webapp.set_task_period(TASK_POLL_OBSERVATION_AREAS, poll_interval) @@ -91,13 +93,13 @@ def tracer_upsert_observation_area( def tracer_delete_observation_area( area_id: str, ) -> tuple[str, int] | flask.Response: - with db as tx: - if area_id not in tx.observation_areas: + with db.transact() as tx: + if area_id not in tx.value.observation_areas: return "Specified observation area not in system", 404 - area = tx.observation_areas.pop(area_id) + area = tx.value.observation_areas.pop(area_id) area = delete_observation_area(area) remaining_polling_areas = sum( - 1 if a.polls else 0 for a in tx.observation_areas.values() + 1 if a.polls else 0 for a in tx.value.observation_areas.values() ) if not remaining_polling_areas: @@ -131,7 +133,7 @@ def tracer_import_observation_areas() -> tuple[str, int] | flask.Response: elif request.area.volume.outline_polygon: points = [ s2sphere.LatLng.from_degrees(p.lat, p.lng) - for p in request.area.volume.outline_polygon.vertices + for p in request.area.volume.outline_polygon.vertices or [] ] else: raise NotImplementedError( @@ -180,11 +182,13 @@ def tracer_import_observation_areas() -> tuple[str, int] | flask.Response: "Import of F3548 subscriptions into observation areas is not yet implemented" ) - with db as tx: + with db.transact() as tx: new_obs_areas = [] f3411_subscription_ids = { - a.f3411.subscription_id for a in tx.observation_areas.values() if a.f3411 + a.f3411.subscription_id + for a in tx.value.observation_areas.values() + if a.f3411 } new_obs_areas.extend( a @@ -193,7 +197,9 @@ def tracer_import_observation_areas() -> tuple[str, int] | flask.Response: ) f3548_subscription_ids = { - a.f3548.subscription_id for a in tx.observation_areas.values() if a.f3548 + a.f3548.subscription_id + for a in tx.value.observation_areas.values() + if a.f3548 } new_obs_areas.extend( a @@ -202,7 +208,7 @@ def tracer_import_observation_areas() -> tuple[str, int] | flask.Response: ) for obs_area in new_obs_areas: - tx.observation_areas[obs_area.id] = obs_area + tx.value.observation_areas[obs_area.id] = obs_area return flask.jsonify( ListObservationAreasResponse( @@ -217,9 +223,9 @@ def _shutdown(): f"Cleaning up observation areas from PID {os.getpid()} at {datetime.now(UTC)}..." ) - with db as tx: - observation_areas: list[ObservationArea] = [v for _, v in tx.observation_areas] - tx.observation_areas.clear() + with db.transact() as tx: + observation_areas = list(tx.value.observation_areas.values()) + tx.value.observation_areas.clear() for area in observation_areas: delete_observation_area(area) diff --git a/monitoring/mock_uss/tracer/routes/rid.py b/monitoring/mock_uss/tracer/routes/rid.py index f3030d6ae7..3faf3cdb7f 100644 --- a/monitoring/mock_uss/tracer/routes/rid.py +++ b/monitoring/mock_uss/tracer/routes/rid.py @@ -12,7 +12,7 @@ PutIdentificationServiceAreaNotificationParameters as PutIdentificationServiceAreaNotificationParametersV22a, ) -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.tracer import context from monitoring.mock_uss.tracer.log_types import RIDISANotification from monitoring.mock_uss.tracer.template import _print_time_range diff --git a/monitoring/mock_uss/tracer/routes/scd.py b/monitoring/mock_uss/tracer/routes/scd.py index 02e419888e..624a5b9e78 100644 --- a/monitoring/mock_uss/tracer/routes/scd.py +++ b/monitoring/mock_uss/tracer/routes/scd.py @@ -6,7 +6,7 @@ from loguru import logger from termcolor import colored -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.tracer import context from monitoring.mock_uss.tracer.log_types import ( ConstraintNotification, @@ -39,6 +39,10 @@ def tracer_scd_v21_operation_notification(observation_area_id: str) -> tuple[str label = colored("Operation", "blue") try: json = flask.request.json + + if json is None: + raise ValueError("No json in request") + id = json.get("operational_intent_id", "") if json.get("operational_intent"): op = json["operational_intent"] @@ -94,6 +98,10 @@ def tracer_scd_v21_constraint_notification(observation_area_id: str) -> tuple[st label = colored("Constraint", "magenta") try: json = flask.request.json + + if json is None: + raise ValueError("No json in request") + id = json.get("constraint_id", "") if json.get("constraint"): constraint = json["constraint"] diff --git a/monitoring/mock_uss/tracer/routes/ui.py b/monitoring/mock_uss/tracer/routes/ui.py index 426549c9e1..005d6960a0 100644 --- a/monitoring/mock_uss/tracer/routes/ui.py +++ b/monitoring/mock_uss/tracer/routes/ui.py @@ -3,6 +3,7 @@ import io import os import zipfile +from typing import cast import arrow import flask @@ -11,7 +12,7 @@ from implicitdict import ImplicitDict, StringBasedDateTime from loguru import logger -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.tracer import context from monitoring.mock_uss.tracer.database import db from monitoring.mock_uss.tracer.kml import render_historical_kml @@ -23,7 +24,7 @@ @webapp.route("/tracer/logs", methods=["GET"]) -@ui_auth.login_required +@ui_auth.login_required() def tracer_list_logs(): logger.debug(f"Handling tracer_list_logs from {os.getpid()}") logs = [ @@ -109,7 +110,7 @@ def _redact_and_augment_log(obj): @webapp.route("/tracer/logs/") -@ui_auth.login_required +@ui_auth.login_required() def tracer_logs(log): logger.debug(f"Handling tracer_logs from {os.getpid()}") logfile = os.path.join(context.tracer_logger.log_path, log) @@ -122,7 +123,7 @@ def tracer_logs(log): else: obj = {"entries": objs} - object_type_name = obj.get("object_type", None) + object_type_name = cast(str | None, obj.get("object_type", None)) object_type = TracerLogEntry.entry_type(object_type_name) if object_type: parsed: TracerLogEntry = ImplicitDict.parse(obj, object_type) @@ -137,7 +138,7 @@ def tracer_logs(log): @webapp.route("/tracer/kml/now.kml") -@ui_auth.login_required +@ui_auth.login_required() def tracer_kml_now(): logger.debug(f"Handling tracer_kml_now from {os.getpid()}") all_kmls = glob.glob(os.path.join(context.tracer_logger.log_path, "kml", "*.kml")) @@ -147,13 +148,13 @@ def tracer_kml_now(): return flask.send_file( latest_kml, mimetype="application/vnd.google-earth.kml+xml", - attachment_filename="now.kml", + download_name="now.kml", as_attachment=True, ) @webapp.route("/tracer/kml/") -@ui_auth.login_required +@ui_auth.login_required() def tracer_kmls(kml): logger.debug(f"Handling tracer_kmls from {os.getpid()}") kmlfile = os.path.join(context.tracer_logger.log_path, "kml", kml) @@ -162,13 +163,13 @@ def tracer_kmls(kml): return flask.send_file( kmlfile, mimetype="application/vnd.google-earth.kml+xml", - attachment_filename=kml, + download_name=kml, as_attachment=True, ) @webapp.route("/tracer/kml/historical.kml") -@ui_auth.login_required +@ui_auth.login_required() def tracer_kml_historical(): kml_name = f"historical_{datetime.datetime.now(datetime.UTC).isoformat().split('.')[0]}.kml" return flask.Response( @@ -179,11 +180,10 @@ def tracer_kml_historical(): def _get_validated_obs_area(observation_area_id: str) -> ObservationArea: - with db as tx: - if observation_area_id not in tx.observation_areas: - flask.abort(404, "Specified observation area not found") - area: ObservationArea = tx.observation_areas[observation_area_id] - return area + tx = db.value + if observation_area_id not in tx.observation_areas: + flask.abort(404, "Specified observation area not found") + return tx.observation_areas[observation_area_id] @webapp.route("/tracer/observation_areas//ui", methods=["GET"]) @@ -227,18 +227,21 @@ def tracer_rid_request_poll(observation_area_id: str): area = _get_validated_obs_area(observation_area_id) if not area.f3411: flask.abort(400, "Specified observation area is not observing F3411 remote ID") + raise RuntimeError("An exception should have been raised.") if not area.area.volume.outline_polygon and not area.area.volume.outline_circle: flask.abort( 400, "Specified observation area does not define its spatial outline" ) + raise RuntimeError("An exception should have been raised.") + rid_client = context.get_client(area.f3411.auth_spec, area.f3411.dss_base_url) flights_result = rid.all_flights( geo.make_latlng_rect(area.area.volume), - flask.request.form.get("include_recent_positions", type=bool), - flask.request.form.get("get_details", type=bool), + flask.request.form.get("include_recent_positions", False, type=bool), + flask.request.form.get("get_details", False, type=bool), area.f3411.rid_version, rid_client, - enhanced_details=flask.request.form.get("enhanced_details", type=bool), + enhanced_details=flask.request.form.get("enhanced_details", False, type=bool), ) log_name = context.tracer_logger.log_new( PollFlights( @@ -251,7 +254,7 @@ def tracer_rid_request_poll(observation_area_id: str): @webapp.route("/tracer/observation_areas/ui", methods=["GET"]) -@ui_auth.login_required +@ui_auth.login_required() def tracer_observation_areas_ui(): return flask.render_template( "tracer/observation_areas_ui.html", diff --git a/monitoring/mock_uss/tracer/subscriptions.py b/monitoring/mock_uss/tracer/subscriptions.py index a13debfc64..0f3c52e763 100644 --- a/monitoring/mock_uss/tracer/subscriptions.py +++ b/monitoring/mock_uss/tracer/subscriptions.py @@ -1,15 +1,13 @@ import uuid import arrow -import yaml from implicitdict import StringBasedDateTime -from yaml.representer import Representer import monitoring.monitorlib.fetch.rid as fetch_rid import monitoring.monitorlib.fetch.scd as fetch_scd import monitoring.monitorlib.mutate.rid as mutate_rid import monitoring.monitorlib.mutate.scd as mutate_scd -from monitoring.mock_uss import config, webapp +from monitoring.mock_uss.app import config, webapp from monitoring.mock_uss.tracer import context from monitoring.mock_uss.tracer.log_types import ( RIDSubscribe, @@ -23,8 +21,6 @@ from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.rid import RIDVersion -yaml.add_representer(StringBasedDateTime, Representer.represent_str) - class SubscriptionManagementError(RuntimeError): def __init__(self, msg): @@ -54,8 +50,8 @@ def subscribe_rid( area_vertices=vertices, alt_lo=area.volume.altitude_lower_wgs84_m(0), alt_hi=area.volume.altitude_upper_wgs84_m(3048), - start_time=area.time_start.datetime, - end_time=area.time_end.datetime, + start_time=area.time_start.datetime if area.time_start else None, + end_time=area.time_end.datetime if area.time_end else None, uss_base_url=uss_base_url, subscription_id=subscription_id, rid_version=rid_version, @@ -85,6 +81,11 @@ def subscribe_scd( base_url = webapp.config[config.KEY_BASE_URL] uss_base_url = f"{base_url}/tracer/f3548v21/{area_id}" + if not area.time_start or not area.time_end: + raise SubscriptionManagementError( + "Could not create new SCD Subscription -> time_start or time_end not set" + ) + create_result = mutate_scd.upsert_subscription( scd_client, box, diff --git a/monitoring/mock_uss/tracer/tracer_poll.py b/monitoring/mock_uss/tracer/tracer_poll.py index 9b1ee16dc0..7914c66be1 100755 --- a/monitoring/mock_uss/tracer/tracer_poll.py +++ b/monitoring/mock_uss/tracer/tracer_poll.py @@ -3,9 +3,9 @@ import sys import arrow -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ImplicitDict, Optional, StringBasedDateTime -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.tracer import context, diff, tracerlog from monitoring.mock_uss.tracer.config import ( KEY_TRACER_KML_FOLDER, @@ -23,9 +23,18 @@ ObservationArea, ObservationAreaID, ) -from monitoring.monitorlib import fetch, versioning +from monitoring.monitorlib import versioning from monitoring.monitorlib.fetch.rid import FetchedISAs -from monitoring.monitorlib.fetch.scd import FetchedEntities +from monitoring.monitorlib.fetch.rid import isas as fetch_rid_isas +from monitoring.monitorlib.fetch.scd import ( + FetchedEntities, +) +from monitoring.monitorlib.fetch.scd import ( + constraints as fetch_scd_constraints, +) +from monitoring.monitorlib.fetch.scd import ( + operations as fetch_scd_operations, +) from monitoring.monitorlib.geo import get_latlngrect_vertices, make_latlng_rect from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.multiprocessing import SynchronizedValue @@ -37,7 +46,7 @@ class PollingStatus(ImplicitDict): started: bool = False -polling_status = SynchronizedValue( +polling_status = SynchronizedValue[PollingStatus]( PollingStatus(), capacity_bytes=1000, decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), PollingStatus), @@ -46,12 +55,12 @@ class PollingStatus(ImplicitDict): class PollingValues(ImplicitDict): need_line_break: bool = False - last_isa_result: FetchedISAs | None = None - last_ops_result: FetchedEntities | None = None - last_constraints_result: FetchedEntities | None = None + last_isa_result: Optional[FetchedISAs] = None + last_ops_result: Optional[FetchedEntities] = None + last_constraints_result: Optional[FetchedEntities] = None -polling_values = SynchronizedValue( +polling_values = SynchronizedValue[PollingValues]( PollingValues(), decoder=lambda b: ImplicitDict.parse(json.loads(b.decode("utf-8")), PollingValues), ) @@ -64,10 +73,10 @@ def print_no_newline(s): def _log_poll_start(logger): init = False - with polling_status as tx: - if not tx.started: + with polling_status.transact() as tx: + if not tx.value.started: init = True - tx.started = True + tx.value.started = True if init: config = { KEY_TRACER_OUTPUT_FOLDER: webapp.config[KEY_TRACER_OUTPUT_FOLDER], @@ -102,14 +111,17 @@ def poll_observation_areas() -> None: def poll_isas(area: ObservationArea, logger: tracerlog.Logger) -> None: + if not area.f3411: + return + rid_client = context.get_client(area.f3411.auth_spec, area.f3411.dss_base_url) box = get_latlngrect_vertices(make_latlng_rect(area.area.volume)) t0 = datetime.datetime.now(datetime.UTC) - result = fetch.rid.isas( + result = fetch_rid_isas( box, - area.area.time_start.datetime, - area.area.time_end.datetime, + area.area.time_start.datetime if area.area.time_start else None, + area.area.time_end.datetime if area.area.time_end else None, area.f3411.rid_version, rid_client, ) @@ -117,18 +129,17 @@ def poll_isas(area: ObservationArea, logger: tracerlog.Logger) -> None: log_new = False last_result = None - with polling_values as tx: - assert isinstance(tx, PollingValues) - if tx.last_isa_result is None or result.has_different_content_than( - tx.last_isa_result + with polling_values.transact() as tx: + if tx.value.last_isa_result is None or result.has_different_content_than( + tx.value.last_isa_result ): - last_result = tx.last_isa_result + last_result = tx.value.last_isa_result log_new = True - tx.need_line_break = False - tx.last_isa_result = result + tx.value.need_line_break = False + tx.value.last_isa_result = result else: - tx.need_line_break = True - need_line_break = tx.need_line_break + tx.value.need_line_break = True + need_line_break = tx.value.need_line_break log_entry = PollISAs(poll=result, recorded_at=StringBasedDateTime(arrow.utcnow())) if log_new: @@ -144,13 +155,14 @@ def poll_isas(area: ObservationArea, logger: tracerlog.Logger) -> None: def poll_ops( area: ObservationArea, scd_client: UTMClientSession, logger: tracerlog.Logger ) -> None: + if not area.area.time_start or not area.area.time_end: + return + box = make_latlng_rect(area.area.volume) t0 = datetime.datetime.now(datetime.UTC) if "operational_intents" not in context.scd_cache: - context.scd_cache["operational_intents"]: dict[ - str, fetch.scd.FetchedEntity - ] = {} - result = fetch.scd.operations( + context.scd_cache["operational_intents"] = {} + result = fetch_scd_operations( scd_client, box, area.area.time_start.datetime, @@ -161,17 +173,17 @@ def poll_ops( log_new = False last_result = None - with polling_values as tx: - if tx.last_ops_result is None or result.has_different_content_than( - tx.last_ops_result + with polling_values.transact() as tx: + if tx.value.last_ops_result is None or result.has_different_content_than( + tx.value.last_ops_result ): - last_result = tx.last_ops_result + last_result = tx.value.last_ops_result log_new = True - tx.need_line_break = False - tx.last_ops_result = result + tx.value.need_line_break = False + tx.value.last_ops_result = result else: - tx.need_line_break = True - need_line_break = tx.need_line_break + tx.value.need_line_break = True + need_line_break = tx.value.need_line_break log_entry = PollOperationalIntents( poll=result, recorded_at=StringBasedDateTime(arrow.utcnow()) @@ -189,11 +201,14 @@ def poll_ops( def poll_constraints( area: ObservationArea, scd_client: UTMClientSession, logger: tracerlog.Logger ) -> None: + if not area.area.time_start or not area.area.time_end: + return + box = make_latlng_rect(area.area.volume) t0 = datetime.datetime.now(datetime.UTC) if "constraints" not in context.scd_cache: - context.scd_cache["constraints"]: dict[str, fetch.scd.FetchedEntity] = {} - result = fetch.scd.constraints( + context.scd_cache["constraints"] = {} + result = fetch_scd_constraints( scd_client, box, area.area.time_start.datetime, @@ -204,15 +219,15 @@ def poll_constraints( log_new = False last_result = None - with polling_values as tx: - if result.has_different_content_than(tx.last_constraints_result): - last_result = tx.last_constraints_result + with polling_values.transact() as tx: + if result.has_different_content_than(tx.value.last_constraints_result): + last_result = tx.value.last_constraints_result log_new = True - tx.need_line_break = False - tx.last_constraints_result = result + tx.value.need_line_break = False + tx.value.last_constraints_result = result else: - tx.need_line_break = True - need_line_break = tx.need_line_break + tx.value.need_line_break = True + need_line_break = tx.value.need_line_break log_entry = PollConstraints( poll=result, recorded_at=StringBasedDateTime(arrow.utcnow()) diff --git a/monitoring/mock_uss/tracer/tracerlog.py b/monitoring/mock_uss/tracer/tracerlog.py index 8535974818..6eea5c3343 100644 --- a/monitoring/mock_uss/tracer/tracerlog.py +++ b/monitoring/mock_uss/tracer/tracerlog.py @@ -10,7 +10,9 @@ class Logger: def __init__( - self, log_path: str, kml_session: infrastructure.KMLGenerationSession = None + self, + log_path: str, + kml_session: infrastructure.KMLGenerationSession | None = None, ): self.log_path = log_path os.makedirs(self.log_path, exist_ok=True) @@ -54,3 +56,14 @@ def log_new(self, content: TracerLogEntry) -> str: print(f"Error posting {kml_server_filename} to KML server: {e}") return logname + + +class DummyLogger(Logger): + def __init__(self): + pass + + def log_same(self, t0: datetime.datetime, t1: datetime.datetime, code: str) -> None: + pass + + def log_new(self, content: TracerLogEntry) -> str: + return "dummy" diff --git a/monitoring/mock_uss/ui/auth.py b/monitoring/mock_uss/ui/auth.py index 259868b51f..49faa3b974 100644 --- a/monitoring/mock_uss/ui/auth.py +++ b/monitoring/mock_uss/ui/auth.py @@ -10,7 +10,7 @@ from oauthlib.oauth2 import WebApplicationClient from werkzeug.security import check_password_hash, generate_password_hash -from monitoring.mock_uss import import_environment_variable, webapp +from monitoring.mock_uss.app import import_environment_variable, webapp login_manager = LoginManager() login_manager.init_app(webapp) @@ -63,7 +63,7 @@ def is_admin(self) -> bool: def _get_users() -> list[User]: users = [] - user_strings = webapp.config.get(KEY_UI_USERS).split(";") + user_strings = (webapp.config.get(KEY_UI_USERS) or "").split(";") for user_string in user_strings: if not user_string.strip(): continue @@ -122,7 +122,9 @@ def ui_login_usernamepassword(): if not users: flask.flash("Invalid username/password combination") return flask.redirect(flask.url_for("ui_login")) - if check_password_hash(users[0].password_hash, flask.request.form["password"]): + if users[0].password_hash and check_password_hash( + users[0].password_hash, flask.request.form["password"] + ): flask_login.login_user(users[0]) return flask.redirect(flask.url_for("ui_login_successful")) else: @@ -132,6 +134,9 @@ def ui_login_usernamepassword(): @webapp.route("/ui/login/callback") def ui_login_callback(): + if not oauth_client: + return "Not in oauth mode", 400 + if "code" not in flask.request.args: return "Missing `code` in request arguments", 400 code = flask.request.args.get("code") @@ -150,8 +155,8 @@ def ui_login_callback(): headers=headers, data=body, auth=( - webapp.config.get(KEY_GOOGLE_OAUTH_CLIENT_ID), - webapp.config.get(KEY_GOOGLE_OAUTH_CLIENT_SECRET), + webapp.config.get(KEY_GOOGLE_OAUTH_CLIENT_ID, ""), + webapp.config.get(KEY_GOOGLE_OAUTH_CLIENT_SECRET, ""), ), ) oauth_client.parse_request_body_response(json.dumps(token_response.json())) diff --git a/monitoring/mock_uss/user_interactions/notifications.py b/monitoring/mock_uss/user_interactions/notifications.py index 531b0b6930..ab2ac31ce8 100644 --- a/monitoring/mock_uss/user_interactions/notifications.py +++ b/monitoring/mock_uss/user_interactions/notifications.py @@ -1,11 +1,11 @@ -from enum import Enum +from enum import StrEnum from implicitdict import ImplicitDict, StringBasedDateTime from monitoring.monitorlib.clients.flight_planning.planning import Conflict -class UserNotificationType(str, Enum): +class UserNotificationType(StrEnum): """Type of notification the virtual user received""" CausedConflict = "CausedConflict" diff --git a/monitoring/mock_uss/uspace/flight_auth.py b/monitoring/mock_uss/uspace/flight_auth.py index 9929b9306f..6f77d337fb 100644 --- a/monitoring/mock_uss/uspace/flight_auth.py +++ b/monitoring/mock_uss/uspace/flight_auth.py @@ -9,6 +9,10 @@ def validate_request(flight_info: FlightInfo) -> None: Args: flight_info: Information about the requested flight. """ + + if not flight_info.uspace_flight_authorisation: + return + problems = problems_with_flight_authorisation( flight_info.uspace_flight_authorisation ) diff --git a/monitoring/mock_uss/versioning/routes.py b/monitoring/mock_uss/versioning/routes.py old mode 100644 new mode 100755 index b26942dbb6..ef2a3625e8 --- a/monitoring/mock_uss/versioning/routes.py +++ b/monitoring/mock_uss/versioning/routes.py @@ -1,14 +1,14 @@ import flask from uas_standards.interuss.automated_testing.versioning import api, constants -from monitoring.mock_uss import webapp +from monitoring.mock_uss.app import webapp from monitoring.mock_uss.auth import requires_scope from monitoring.monitorlib import versioning @webapp.route("/versioning/versions/", methods=["GET"]) @requires_scope(constants.Scope.ReadSystemVersions) -def versioning_get_version(system_identity: str) -> tuple[str, int]: +def versioning_get_version(system_identity: str) -> flask.Response: version = versioning.get_code_version() return flask.jsonify( api.GetVersionResponse( diff --git a/monitoring/monitorlib/Makefile b/monitoring/monitorlib/Makefile new file mode 100644 index 0000000000..fe42ad0f5f --- /dev/null +++ b/monitoring/monitorlib/Makefile @@ -0,0 +1,3 @@ +.PHONY: unit_test +unit_test: + ./scripts/run_unit_tests.sh diff --git a/monitoring/monitorlib/auth.py b/monitoring/monitorlib/auth.py index d4eea0742a..14de18748b 100644 --- a/monitoring/monitorlib/auth.py +++ b/monitoring/monitorlib/auth.py @@ -36,19 +36,31 @@ class NoAuth(AuthAdapter): # This is the private key from test-certs/auth2.key. dummy_private_key = jwcrypto.jwk.JWK.from_pem( b"-----BEGIN RSA PRIVATE KEY-----\n" - b"MIICWwIBAAKBgHkNtpy3GB0YTCl2VCCd22i0rJwIGBSazD4QRKvH6rch0IP4igb+\n" - b"02r7t0X//tuj0VbwtJz3cEICP8OGSqrdTSCGj5Y03Oa2gPkx/0c0V8D0eSXS/CUC\n" - b"0qrYHnAGLqko7eW87HW0rh7nnl2bB4Lu+R8fOmQt5frCJ5eTkzwK5YczAgMBAAEC\n" - b"gYAtSgMjGKEt6XQ9IucQmN6Iiuf1LFYOB2gYZC+88PuQblc7uJWzTk08vlXwG3l3\n" - b"JQ/h7gY0n6JhH8RJW4m96TO8TrlHLx5aVcW8E//CtgayMn3vBgXida3wvIlAXT8G\n" - b"WezsNsWorXLVmz5yov0glu+TIk31iWB5DMs4xXhXdH/t8QJBALQzvF+y5bZEhZin\n" - b"qTXkiKqMsKsJbXjP1Sp/3t52VnYVfbxN3CCb7yDU9kg5QwNa3ungE3cXXNMUr067\n" - b"9zIraekCQQCr+NSeWAXIEutWewPIykYMQilVtiJH4oFfoEpxvecVv7ulw6kM+Jsb\n" - b"o6Pi7x86tMVkwOCzZzy/Uyo/gSHnEZq7AkEAm0hBuU2VuTzOyr8fhvtJ8X2O97QG\n" - b"C6c8j4Tk7lqXIuZeFRga6la091vMZmxBnPB/SpX28BbHvHUEpBpBZ5AVkQJAX7Lq\n" - b"7urg3MPafpeaNYSKkovG4NGoJgSgJgzXIJCjJfE6hTZqvrMh7bGUo9aZtFugdT74\n" - b"TB2pKncnTYuYyDN9vQJACDVr+wvYYA2VdnA9k+/1IyGc1HHd2npQqY9EduCeOGO8\n" - b"rXQedG6rirVOF6ypkefIayc3usipVvfadpqcS5ERhw==\n" + b"MIIEowIBAAKCAQEAtjrMt+vOxuqjOU+hwrVAgHjBMs9nMw1ONSpiLOpUQSnqvBwB\n" + b"0Zba+8e2tGJeQDWEf8KoVt3PfMa34EomLhWZGWEosftV8gZSbneDjUnE+kUTXvZm\n" + b"MFa2Byc9njsl5N7dOC1pT7qHNJOB9RhK/9ehPl7XczRuIJR7N/ZFk+XZSlFh5wer\n" + b"q7ME7GdhBPFaO30KZRmRhgwVtAP2kA8sSrqgxXuuyy808UvT3MkzL63Sv/EzQSs8\n" + b"YLVAn/BwiQlmWxKFmtzNk9RJdYkxRJN1E9E2sS2i6b55j94hiPwwQ16GsAAWUT+i\n" + b"jezlHuGZU+OLYtYfoHy+4Ku31doQc0ujvtYOJQIDAQABAoIBACTuccLslXGW6BGb\n" + b"Y+s0FKh00KLdicq87Za0ykTUENNMDXimLHAvpJ3Wcd7I+NUGg53o83j3Zy+gjm90\n" + b"V5yLYAXWvQqlJ1vvkBE3Q4AE7VjTWwOp6DfvuuBkQYap8hoaWLcj7O3tna04H+Ru\n" + b"UfTb3J/pVLzSaWdM8FP9I0jAEnOPBwwgmQ74G/NmCZpv39boLJQ2sdqY9gSaQUsn\n" + b"4fwKtQQ6yBLDw8mkNnzxnRxXe7FiKjKtnTUp7GhELoQ37XbwJRJTlBA9jirxadss\n" + b"4NiHpSq+QhkHdJPMOdE/DTZ0H52mJe92t+EEptJADX4O4b54oNaJ4p99Zk0rlW97\n" + b"s3lowdkCgYEAygLJiYBpv/wuJd1peqLoYnOIBriFQi7vSF+Ho4UNnS76IapSZ/qY\n" + b"nUGrTYNaNhTg39Gsbpdax7bnaWiTDNzdCiiQxMc5BSR3tDF52tIaOqvVFgd5KRu/\n" + b"7pOtH8fToR770KviT85G8Wb+ozFY7F0/yklU+OQEtfmuAVs6bEx4m8cCgYEA5u6e\n" + b"e00l01h/SvM7CoYdoOG9N1vyhMYbUBDR6SUIAaR25/UJPH7kPpAqxXKR1BWJh7q+\n" + b"1cBF0ZFnQ3xkRsfETcvLPJLAsIMwd1HN2sJ0/tPSgFSw6RTFswa3SlpxON+8shcR\n" + b"2617UhFDc/5MfCXiNb8u/4Ng7iacfjVhdLehzrMCgYAX1g5byCgyPBph42dPzisn\n" + b"esRhLqKitZEMdCE4HToHAwUGtec1V69sVtRUuBwL55jFMCNthTRz/lP97xXy3ZjD\n" + b"WxgB8BP9VFk/jNr5A/OOWrow+D7Gp/yUtR4ncte42kQSUkXI7ukWEPYY4XjBoxsk\n" + b"zlRVbepUYpqylEYngzpz/wKBgQCNwSntXDT839T7iATU9/CWAhupMLrUv9qiMkD4\n" + b"EXAxuef3iNWLmgS3Vr26iBJ2EmZit8JO6YCyHMQ7i87uF9ArRQ7Tdu3rLAyDIebw\n" + b"Au/YQOR1PAeAe+zDcTrv3Eal98kXtMuUgpAxl0FFoXMHviV2go3x8I5+gZsMae4R\n" + b"vGsJuwKBgH1KY+N3629mtdyhb1xfoxFRDq0Izyl/OpeWZHe4wd+nklkRsj0CvLHz\n" + b"m+F5V4CxlUufLadvg9KFxd48UWTwqjrTysixDN4MUPngFoBSh0i/egq6DJlsEhAL\n" + b"0Q9fZLTCV7sKG9FAUCWT6nE+k8K9U0hTCUeyVqx85iTUu10zQrTR\n" b"-----END RSA PRIVATE KEY-----" ) diff --git a/monitoring/monitorlib/auth_validation.py b/monitoring/monitorlib/auth_validation.py index c066cfd55e..fca09e6feb 100644 --- a/monitoring/monitorlib/auth_validation.py +++ b/monitoring/monitorlib/auth_validation.py @@ -11,7 +11,6 @@ class Authorization(NamedTuple): client_id: str scopes: list[str] - issuer: str class InvalidScopeError(Exception): @@ -99,6 +98,7 @@ def wrapper(*args, **kwargs): client_id = ( r["client_id"] if "client_id" in r else r.get("sub", None) ) + assert isinstance(client_id, str) except jwt.ImmatureSignatureError: raise InvalidAccessTokenError("Access token is immature.") except jwt.ExpiredSignatureError: @@ -113,10 +113,7 @@ def wrapper(*args, **kwargs): raise InvalidAccessTokenError( f"Unexpected InvalidTokenError: {str(e)}" ) - issuer = r.get("iss", None) - flask.request.jwt = Authorization( - client_id, provided_scopes, issuer - ) + flask.request.jwt = Authorization(client_id, provided_scopes) return fn(*args, **kwargs) diff --git a/monitoring/monitorlib/clients/flight_planning/client.py b/monitoring/monitorlib/clients/flight_planning/client.py index 0d2bc8aa04..f4186f4e1b 100644 --- a/monitoring/monitorlib/clients/flight_planning/client.py +++ b/monitoring/monitorlib/clients/flight_planning/client.py @@ -30,7 +30,7 @@ class FlightPlannerClient(ABC): def __init__(self, participant_id: ParticipantID): self.participant_id = participant_id - self.created_flight_ids: set[FlightID] = set() + self.created_flight_ids = set() # ===== Emulation of user actions ===== diff --git a/monitoring/monitorlib/clients/flight_planning/client_scd.py b/monitoring/monitorlib/clients/flight_planning/client_scd.py index 73042451e0..85837d1849 100644 --- a/monitoring/monitorlib/clients/flight_planning/client_scd.py +++ b/monitoring/monitorlib/clients/flight_planning/client_scd.py @@ -3,6 +3,8 @@ import arrow from implicitdict import ImplicitDict, StringBasedDateTime +from loguru import logger +from uas_standards.interuss.automated_testing.flight_planning.v1.api import FlightPlan from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api from uas_standards.interuss.automated_testing.scd.v1 import ( constants as scd_api_constants, @@ -157,6 +159,20 @@ def _inject( if response.flight_plan_status in created_status: self.created_flight_ids.add(flight_id) + if query.response.json and "as_planned" in query.response.json: + # Make best effort to interpret additional `as_planned` field according to flight_planning API as an ad-hoc + # retrofit to the legacy scd injection API + try: + response.as_planned = FlightInfo.from_flight_plan( + ImplicitDict.parse(query.response.json["as_planned"], FlightPlan) + ) + except ValueError: + # Best effort failed so it's ok to ignore additional `as_planned` field + logger.warning( + "SCD API response contained unparseable `as_planned` supplemental field" + ) + pass + self._plan_statuses[flight_id] = response.flight_plan_status return response diff --git a/monitoring/monitorlib/clients/flight_planning/client_v1.py b/monitoring/monitorlib/clients/flight_planning/client_v1.py index 455560014e..eedb47997c 100644 --- a/monitoring/monitorlib/clients/flight_planning/client_v1.py +++ b/monitoring/monitorlib/clients/flight_planning/client_v1.py @@ -100,6 +100,9 @@ def _inject( ), ) + if "as_planned" in resp and resp.as_planned: + response.as_planned = FlightInfo.from_flight_plan(resp.as_planned) + # If we know that the flight was successfully not created # (the server explicitly refused to), we remove it from set of flights. # That the only case when we do this, if we recieve no response after a diff --git a/monitoring/monitorlib/clients/flight_planning/flight_info.py b/monitoring/monitorlib/clients/flight_planning/flight_info.py index a2ca876bbf..4bdf5277b3 100644 --- a/monitoring/monitorlib/clients/flight_planning/flight_info.py +++ b/monitoring/monitorlib/clients/flight_planning/flight_info.py @@ -1,14 +1,16 @@ from __future__ import annotations -from enum import Enum +from enum import StrEnum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3548.v21 import api as f3548v21 from uas_standards.en4709_02 import OperatorRegistrationNumber from uas_standards.interuss.automated_testing.flight_planning.v1 import api as fp_api from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api +from monitoring.monitorlib.clients.flight_planning.telemetry import FlightTelemetry +from monitoring.monitorlib.geo import Altitude, LatLngPoint from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection # ===== ASTM F3548-21 ===== @@ -21,13 +23,13 @@ class ASTMF354821OpIntentInformation(ImplicitDict): """Information provided about a flight plan that is necessary for ASTM F3548-21.""" - priority: Priority | None + priority: Optional[Priority] # ===== U-space ===== -class FlightAuthorisationDataOperationCategory(str, Enum): +class FlightAuthorisationDataOperationCategory(StrEnum): """Category of UAS operation (‘open’, ‘specific’, ‘certified’) as defined in COMMISSION DELEGATED REGULATION (EU) 2019/945. Required by ANNEX IV of COMMISSION IMPLEMENTING REGULATION (EU) 2021/664, paragraph 4.""" Unknown = "Unknown" @@ -36,7 +38,7 @@ class FlightAuthorisationDataOperationCategory(str, Enum): Certified = "Certified" -class OperationMode(str, Enum): +class OperationMode(StrEnum): """Specify if the operation is a `VLOS` or `BVLOS` operation. Required by ANNEX IV of COMMISSION IMPLEMENTING REGULATION (EU) 2021/664, paragraph 2.""" Undeclared = "Undeclared" @@ -44,7 +46,7 @@ class OperationMode(str, Enum): Bvlos = "Bvlos" -class UASClass(str, Enum): +class UASClass(StrEnum): """Specify the class of the UAS to be flown, the specifition matches EASA class identification label categories. UAS aircraft class as defined in COMMISSION DELEGATED REGULATION (EU) 2019/945 (C0 to C4) and COMMISSION DELEGATED REGULATION (EU) 2020/1058 (C5 and C6). This field is required by ANNEX IV of COMMISSION IMPLEMENTING REGULATION (EU) 2021/664, paragraph 4.""" Other = "Other" @@ -76,7 +78,7 @@ class FlightAuthorisationData(ImplicitDict): identification_technologies: list[str] """Technology used to identify the UAS. Required by ANNEX IV of COMMISSION IMPLEMENTING REGULATION (EU) 2021/664, paragraph 6.""" - uas_type_certificate: str | None + uas_type_certificate: Optional[str] """Provisional field. Not applicable as of September 2021. Required only if `uas_class` is set to `other` by ANNEX IV of COMMISSION IMPLEMENTING REGULATION (EU) 2021/664, paragraph 4.""" connectivity_methods: list[str] @@ -95,7 +97,7 @@ class FlightAuthorisationData(ImplicitDict): Required by ANNEX IV of COMMISSION IMPLEMENTING REGULATION (EU) 2021/664, paragraph 10. """ - uas_id: str | None + uas_id: Optional[str] """When applicable, the registration number of the unmanned aircraft. This is expressed using the nationality and registration mark of the unmanned aircraft in line with ICAO Annex 7. @@ -106,7 +108,7 @@ class FlightAuthorisationData(ImplicitDict): # ===== RPAS Operating Rules 2.6 ===== -class RPAS26FlightDetailsOperatorType(str, Enum): +class RPAS26FlightDetailsOperatorType(StrEnum): """The type of operator.""" Recreational = "Recreational" @@ -114,7 +116,7 @@ class RPAS26FlightDetailsOperatorType(str, Enum): ReOC = "ReOC" -class RPAS26FlightDetailsAircraftType(str, Enum): +class RPAS26FlightDetailsAircraftType(StrEnum): """Type of vehicle being used as per ASTM F3411-22a.""" NotDeclared = "NotDeclared" @@ -135,7 +137,7 @@ class RPAS26FlightDetailsAircraftType(str, Enum): Other = "Other" -class RPAS26FlightDetailsFlightProfile(str, Enum): +class RPAS26FlightDetailsFlightProfile(StrEnum): """Type of flight profile.""" AutomatedGrid = "AutomatedGrid" @@ -146,28 +148,28 @@ class RPAS26FlightDetailsFlightProfile(str, Enum): class RPAS26FlightDetails(ImplicitDict): """Information about a flight necessary to plan successfully using the RPAS Platform Operating Rules version 2.6.""" - operator_type: RPAS26FlightDetailsOperatorType | None + operator_type: Optional[RPAS26FlightDetailsOperatorType] """The type of operator.""" - uas_serial_numbers: list[str] | None + uas_serial_numbers: Optional[list[str]] """The list of UAS/drone serial numbers that will be operated during the operation.""" - uas_registration_numbers: list[str] | None + uas_registration_numbers: Optional[list[str]] """The list of UAS/drone registration numbers that will be operated during the operation.""" - aircraft_type: RPAS26FlightDetailsAircraftType | None + aircraft_type: Optional[RPAS26FlightDetailsAircraftType] """Type of vehicle being used as per ASTM F3411-22a.""" - flight_profile: RPAS26FlightDetailsFlightProfile | None + flight_profile: Optional[RPAS26FlightDetailsFlightProfile] """Type of flight profile.""" - pilot_license_number: str | None + pilot_license_number: Optional[str] """License number for the pilot.""" - pilot_phone_number: str | None + pilot_phone_number: Optional[str] """Contact phone number for the pilot.""" - operator_number: str | None + operator_number: Optional[str] """Operator number.""" @@ -177,7 +179,7 @@ class RPAS26FlightDetails(ImplicitDict): FlightID = str -class AirspaceUsageState(str, Enum): +class AirspaceUsageState(StrEnum): """User's current usage of the airspace defined in the flight plan.""" Planned = "Planned" @@ -187,7 +189,7 @@ class AirspaceUsageState(str, Enum): """The user is currently using the defined area with an active UAS.""" -class UasState(str, Enum): +class UasState(StrEnum): """State of the user's UAS associated with a flight plan.""" Nominal = "Nominal" @@ -227,7 +229,7 @@ def from_flight_planning_api( def to_flight_planning_api(self) -> fp_api.BasicFlightPlanInformation: return fp_api.BasicFlightPlanInformation( usage_state=fp_api.BasicFlightPlanInformationUsageState(self.usage_state), - uas_state=fp_api.BasicFlightPlanInformationUasState(self.uas_state), + uas_state=fp_api.FunctionalState(self.uas_state), area=[v.to_flight_planning_api() for v in self.area], ) @@ -250,60 +252,176 @@ def f3548v21_op_intent_state(self) -> f3548v21.OperationalIntentState: return state +class UAType(StrEnum): + """The UA Type can help infer performance, speed, and duration of flights, for example, a + "fixed wing" can generally fly in a forward direction only (as compared to a multi-rotor). + + `HybridLift` is a fixed wing aircraft that can take off vertically. `Helicopter` includes multirotor. + + `VTOL` is equivalent to HybridLift. + """ + + NotDeclared = "NotDeclared" + Aeroplane = "Aeroplane" + Helicopter = "Helicopter" + Gyroplane = "Gyroplane" + VTOL = "VTOL" + HybridLift = "HybridLift" + Ornithopter = "Ornithopter" + Glider = "Glider" + Kite = "Kite" + FreeBalloon = "FreeBalloon" + CaptiveBalloon = "CaptiveBalloon" + Airship = "Airship" + FreeFallOrParachute = "FreeFallOrParachute" + Rocket = "Rocket" + TetheredPoweredAircraft = "TetheredPoweredAircraft" + GroundObstacle = "GroundObstacle" + Other = "Other" + + +class UASRegistrationNumber(ImplicitDict): + """Number provided by CAA or authorized representative for registering and/or identifying UAS.""" + + authority: str | None = "" + """Authority providing this registration number. If authority represents a country, the ICAO nationality + mark is recommended. + """ + + identifier: str + """Authority-assigned number or identifier.""" + + +class UAClassificationEUCategory(StrEnum): + EUCategoryUndefined = "EUCategoryUndefined" + Open = "Open" + Specific = "Specific" + Certified = "Certified" + + +class UAClassificationEUClass(StrEnum): + EUClassUndefined = "EUClassUndefined" + Class0 = "Class0" + Class1 = "Class1" + Class2 = "Class2" + Class3 = "Class3" + Class4 = "Class4" + Class5 = "Class5" + Class6 = "Class6" + + +UAClassificationEU = dict[UAClassificationEUCategory, UAClassificationEUClass] + + +class UASInformation(ImplicitDict): + """Information about a UAS that may be provided in flight planning scenarios.""" + + aircraft_type: UAType | None + """Aircraft type of the injected test flight.""" + + serial_number: str | None = "" + """This is generally expressed in the CTA-2063-A Serial Number format.""" + + registration_numbers: list[UASRegistrationNumber] | None = [] + """For each relevant authority with which this UAS is registered, the number/identifier assigned to this UAS.""" + + eu_classification: UAClassificationEU | None + """EU classification of aircraft.""" + + +class OperatorInformation(ImplicitDict): + """Information about the operator that may be provided in flight planning scenarios.""" + + registration_numbers: list[OperatorRegistrationNumber] | None + """Registration numbers for the remote pilot or operator.""" + + location: LatLngPoint | None + """Location of operator.""" + + altitude: Altitude | None + """Altitude of operator.""" + + class FlightInfo(ImplicitDict): """Details of user's intent to create or modify a flight plan.""" basic_information: BasicFlightPlanInformation - astm_f3548_21: ASTMF354821OpIntentInformation | None + uas: UASInformation | None + + operator: OperatorInformation | None + + telemetry: FlightTelemetry | None - uspace_flight_authorisation: FlightAuthorisationData | None + astm_f3548_21: Optional[ASTMF354821OpIntentInformation] - rpas_operating_rules_2_6: RPAS26FlightDetails | None + uspace_flight_authorisation: Optional[FlightAuthorisationData] - additional_information: dict | None + rpas_operating_rules_2_6: Optional[RPAS26FlightDetails] + + additional_information: Optional[dict] """Any information relevant to a particular jurisdiction or use case not described in the standard schema. The keys and values must be agreed upon between the test designers and USSs under test.""" @staticmethod def from_flight_plan(plan: fp_api.FlightPlan) -> FlightInfo: - kwargs = { - "basic_information": BasicFlightPlanInformation.from_flight_planning_api( + result = FlightInfo( + basic_information=BasicFlightPlanInformation.from_flight_planning_api( plan.basic_information ) - } + ) + if "uas" in plan and plan.uas: + result.uas = ImplicitDict.parse(plan.uas, UASInformation) + if "operator" in plan and plan.operator: + result.operator = ImplicitDict.parse(plan.operator, OperatorInformation) + if "telemetry" in plan and plan.telemetry: + result.telemetry = ImplicitDict.parse(plan.telemetry, FlightTelemetry) if "astm_f3548_21" in plan and plan.astm_f3548_21: - kwargs["astm_f3548_21"] = ImplicitDict.parse( + result.astm_f3548_21 = ImplicitDict.parse( plan.astm_f3548_21, ASTMF354821OpIntentInformation ) if "uspace_flight_authorisation" in plan and plan.uspace_flight_authorisation: - kwargs["uspace_flight_authorisation"] = ImplicitDict.parse( + result.uspace_flight_authorisation = ImplicitDict.parse( plan.uspace_flight_authorisation, FlightAuthorisationData ) if "rpas_operating_rules_2_6" in plan and plan.rpas_operating_rules_2_6: - kwargs["rpas_operating_rules_2_6"] = ImplicitDict.parse( + result.rpas_operating_rules_2_6 = ImplicitDict.parse( plan.rpas_operating_rules_2_6, RPAS26FlightDetails ) if "additional_information" in plan and plan.additional_information: - kwargs["additional_information"] = plan.additional_information - return FlightInfo(**kwargs) + result.additional_information = plan.additional_information + return result def to_flight_plan(self) -> fp_api.FlightPlan: - kwargs = {"basic_information": self.basic_information.to_flight_planning_api()} + result = fp_api.FlightPlan( + basic_information=self.basic_information.to_flight_planning_api() + ) + if "uas" in self and self.uas: + result.uas = ImplicitDict.parse(self.uas, fp_api.UASInformation) + if "operator" in self and self.operator: + result.operator = ImplicitDict.parse( + self.operator, fp_api.OperatorInformation + ) + if "telemetry" in self and self.telemetry: + result.telemetry = ImplicitDict.parse( + self.telemetry, fp_api.FlightTelemetry + ) if "astm_f3548_21" in self and self.astm_f3548_21: - kwargs["astm_f3548_21"] = ImplicitDict.parse( + result.astm_f3548_21 = ImplicitDict.parse( self.astm_f3548_21, fp_api.ASTMF354821OpIntentInformation ) if "uspace_flight_authorisation" in self and self.uspace_flight_authorisation: - kwargs["uspace_flight_authorisation"] = ImplicitDict.parse( + result.uspace_flight_authorisation = ImplicitDict.parse( self.uspace_flight_authorisation, fp_api.FlightAuthorisationData ) if "rpas_operating_rules_2_6" in self and self.rpas_operating_rules_2_6: - kwargs["rpas_operating_rules_2_6"] = ImplicitDict.parse( + result.rpas_operating_rules_2_6 = ImplicitDict.parse( self.rpas_operating_rules_2_6, fp_api.RPAS26FlightDetails ) if "additional_information" in self and self.additional_information: - kwargs["additional_information"] = self.additional_information - return fp_api.FlightPlan(**kwargs) + result.additional_information = fp_api.FlightPlanAdditionalInformation() + for k, v in self.additional_information.items(): + result.additional_information[k] = v + return result @staticmethod def from_scd_inject_flight_request( @@ -437,7 +555,7 @@ def get_f3548v21_op_intent_state(self) -> f3548v21.OperationalIntentState: ) -class ExecutionStyle(str, Enum): +class ExecutionStyle(StrEnum): Hypothetical = "Hypothetical" """The user does not want the USS to actually perform any action regarding the actual flight plan. Instead, the user would like to know the likely outcome if the action were hypothetically attempted. The response to this request will not refer to an actual flight plan, or an actual state change in an existing flight plan, but rather a hypothetical flight plan or a hypothetical change to an existing flight plan.""" diff --git a/monitoring/monitorlib/clients/flight_planning/flight_info_template.py b/monitoring/monitorlib/clients/flight_planning/flight_info_template.py index 6717bc669d..51c8aa16e8 100644 --- a/monitoring/monitorlib/clients/flight_planning/flight_info_template.py +++ b/monitoring/monitorlib/clients/flight_planning/flight_info_template.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api from monitoring.monitorlib.clients.flight_planning.flight_info import ( @@ -14,7 +14,7 @@ Volume4DCollection, Volume4DTemplateCollection, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest +from monitoring.monitorlib.temporal import TestTimeContext from monitoring.monitorlib.transformations import Transformation @@ -30,9 +30,9 @@ class BasicFlightPlanInformationTemplate(ImplicitDict): area: Volume4DTemplateCollection """User intends to or may fly anywhere in this entire area.""" - def resolve(self, times: dict[TimeDuringTest, Time]) -> BasicFlightPlanInformation: + def resolve(self, context: TestTimeContext) -> BasicFlightPlanInformation: kwargs = {k: v for k, v in self.items()} - kwargs["area"] = Volume4DCollection([t.resolve(times) for t in self.area]) + kwargs["area"] = Volume4DCollection([t.resolve(context) for t in self.area]) return ImplicitDict.parse(kwargs, BasicFlightPlanInformation) @@ -41,21 +41,21 @@ class FlightInfoTemplate(ImplicitDict): basic_information: BasicFlightPlanInformationTemplate - astm_f3548_21: ASTMF354821OpIntentInformation | None + astm_f3548_21: Optional[ASTMF354821OpIntentInformation] - uspace_flight_authorisation: FlightAuthorisationData | None + uspace_flight_authorisation: Optional[FlightAuthorisationData] - rpas_operating_rules_2_6: RPAS26FlightDetails | None + rpas_operating_rules_2_6: Optional[RPAS26FlightDetails] - additional_information: dict | None + additional_information: Optional[dict] """Any information relevant to a particular jurisdiction or use case not described in the standard schema. The keys and values must be agreed upon between the test designers and USSs under test.""" - transformations: list[Transformation] | None + transformations: Optional[list[Transformation]] """If specified, transform this flight according to these transformations in order (after all templates are resolved).""" - def resolve(self, times: dict[TimeDuringTest, Time]) -> FlightInfo: + def resolve(self, context: TestTimeContext) -> FlightInfo: kwargs = {k: v for k, v in self.items() if k not in {"transformations"}} - basic_info = self.basic_information.resolve(times) + basic_info = self.basic_information.resolve(context) if "transformations" in self and self.transformations: for xform in self.transformations: basic_info.area = [v.transform(xform) for v in basic_info.area] @@ -63,11 +63,11 @@ def resolve(self, times: dict[TimeDuringTest, Time]) -> FlightInfo: return ImplicitDict.parse(kwargs, FlightInfo) def to_scd_inject_request( - self, times: dict[TimeDuringTest, Time] + self, context: TestTimeContext ) -> scd_api.InjectFlightRequest: """Render a legacy SCD injection API request object from this object.""" - info = self.resolve(times) + info = self.resolve(context) if "astm_f3548_21" not in info or not info.astm_f3548_21: raise ValueError( "Legacy SCD injection API requires astm_f3548_21 operational intent priority to be specified in FlightInfo" diff --git a/monitoring/monitorlib/clients/flight_planning/planning.py b/monitoring/monitorlib/clients/flight_planning/planning.py index 0d7413fd0a..5ecbd941b1 100644 --- a/monitoring/monitorlib/clients/flight_planning/planning.py +++ b/monitoring/monitorlib/clients/flight_planning/planning.py @@ -1,8 +1,8 @@ from __future__ import annotations -from enum import Enum +from enum import StrEnum -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from uas_standards.astm.f3548.v21 import api as f3548v21 from uas_standards.interuss.automated_testing.flight_planning.v1 import ( api as flight_planning_api, @@ -18,7 +18,7 @@ from monitoring.monitorlib.fetch import Query -class PlanningActivityResult(str, Enum): +class PlanningActivityResult(StrEnum): """The result of the flight planning operation.""" Completed = "Completed" @@ -34,7 +34,7 @@ class PlanningActivityResult(str, Enum): """The USS's implementation does not support the attempted interaction. For instance, if the request specified a high-priority flight and the USS does not support management of high-priority flights.""" -class FlightPlanStatus(str, Enum): +class FlightPlanStatus(StrEnum): """Status of a user's flight plan.""" NotPlanned = "NotPlanned" @@ -63,7 +63,7 @@ def from_flightinfo(info: FlightInfo | None) -> FlightPlanStatus: return FlightPlanStatus.Planned -class AdvisoryInclusion(str, Enum): +class AdvisoryInclusion(StrEnum): """Indication of whether any advisories or conditions were provided to the user along with the result of a flight planning attempt.""" Unknown = "Unknown" @@ -89,10 +89,17 @@ class PlanningActivityResponse(ImplicitDict): flight_plan_status: FlightPlanStatus """Status of the flight plan following the flight planning activity.""" - notes: str | None + as_planned: Optional[FlightInfo] + """The flight information, as it was actually planned (after any adjustments or adaptations). + + If the flight was planned or modified successfully but this field is not populated, the flight information was + accepted exactly as provided. + """ + + notes: Optional[str] """Any human-readable notes regarding the activity.""" - includes_advisories: AdvisoryInclusion | None = AdvisoryInclusion.Unknown + includes_advisories: Optional[AdvisoryInclusion] = AdvisoryInclusion.Unknown def to_inject_flight_response(self) -> scd_api.InjectFlightResponse: if self.activity_result == PlanningActivityResult.Completed: @@ -135,13 +142,13 @@ class ClearAreaResponse(ImplicitDict): flight_deletion_errors: dict[FlightID, dict] """When an error was encountered deleting a particular flight, information about that error.""" - op_intents_removed: list[f3548v21.EntityOVN] + op_intents_removed: list[f3548v21.EntityID] """List of IDs of ASTM F3548-21 operational intent references that were removed during this area clearing operation.""" - op_intent_removal_errors: dict[f3548v21.EntityOVN, dict] + op_intent_removal_errors: dict[f3548v21.EntityID, dict] """When an error was encountered removing a particular operational intent reference, information about that error.""" - error: dict | None = None + error: Optional[dict] = None """If an error was encountered that could not be linked to a specific flight or operational intent, information about it will be populated here.""" @property @@ -153,7 +160,7 @@ def success(self) -> bool: ) -class Conflict(str, Enum): +class Conflict(StrEnum): """Conflict status as indicated in the notification.""" Unknown = "Unknown" diff --git a/monitoring/monitorlib/clients/flight_planning/telemetry.py b/monitoring/monitorlib/clients/flight_planning/telemetry.py new file mode 100644 index 0000000000..59fa00a5e2 --- /dev/null +++ b/monitoring/monitorlib/clients/flight_planning/telemetry.py @@ -0,0 +1,148 @@ +from enum import StrEnum + +from implicitdict import ImplicitDict + +from monitoring.monitorlib.geo import Altitude, LatLngPoint +from monitoring.monitorlib.temporal import Time + + +class HorizontalAccuracy(StrEnum): + """This is the NACp enumeration from ADS-B, plus 1m for a more complete range for UAs.""" + + HAUnknown = "HAUnknown" + HA10NMPlus = "HA10NMPlus" + HA10NM = "HA10NM" + HA4NM = "HA4NM" + HA2NM = "HA2NM" + HA1NM = "HA1NM" + HA05NM = "HA05NM" + HA03NM = "HA03NM" + HA01NM = "HA01NM" + HA005NM = "HA005NM" + HA30m = "HA30m" + HA10m = "HA10m" + HA3m = "HA3m" + HA1m = "HA1m" + + +class VerticalAccuracy(StrEnum): + """This is the GVA enumeration from ADS-B, plus some finer values for UAs.""" + + VAUnknown = "VAUnknown" + VA150mPlus = "VA150mPlus" + VA150m = "VA150m" + VA45m = "VA45m" + VA25m = "VA25m" + VA10m = "VA10m" + VA3m = "VA3m" + VA1m = "VA1m" + + +class SpeedAccuracy(StrEnum): + """This is the same enumeration scale and values from ADS-B NACv.""" + + SAUnknown = "SAUnknown" + SA10mpsPlus = "SA10mpsPlus" + SA10mps = "SA10mps" + SA3mps = "SA3mps" + SA1mps = "SA1mps" + SA03mps = "SA03mps" + + +class OperationalStatus(StrEnum): + """Indicates operational status of associated aircraft. + * `Undeclared`: The system does not support acquisition of knowledge about the status of the aircraft. + * `Ground`: The aircraft is reporting status but is not airborne. + * `Airborne`: The aircraft is, or should be considered as, being airborne. + * `Emergency`: The aircraft is reporting an emergency. + * `SystemFailure`: Some aspect of the reporting/telemetry system has failed, but the aircraft is not in emergency. + * `Unknown`: The system supports acquisition of knowledge about the status of the aircraft, but the status cannot currently be determined. + """ + + Undeclared = "Undeclared" + Ground = "Ground" + Airborne = "Airborne" + Emergency = "Emergency" + SystemFailure = "SystemFailure" + Unknown = "Unknown" + + +class FunctionalState(StrEnum): + """Functional state of the user's UAS associated with this flight plan. + + - `Nominal`: The user or UAS reports or implies that it is performing nominally, or has not indicated + `OffNominal` or `Contingent`. + + - `OffNominal`: The user or UAS reports or implies that it is temporarily not performing nominally, but + may expect to be able to recover to normal operation. + + - `Contingent`: The user or UAS reports or implies that it is not performing nominally and may be unable + to recover to normal operation. + + - `NotSpecified`: The UAS status is not currently available or known (for instance, if the flight is + planned in the future and the UAS that will be flying has not yet connected to the system). + """ + + Nominal = "Nominal" + OffNominal = "OffNominal" + Contingent = "Contingent" + NotSpecified = "NotSpecified" + + +class AircraftPosition(ImplicitDict): + """Reported or actual position of an aircraft at a particular time.""" + + location: LatLngPoint | None + + altitudes: list[Altitude] | None + """The single vertical location of the aircraft, potentially reported relative to multiple datums.""" + + accuracy_h: HorizontalAccuracy | None + """Horizontal error that may be be present in this reported position.""" + + accuracy_v: VerticalAccuracy | None + """Vertical error that may be present in this reported position.""" + + pressure_altitude: float | None = 0.0 + """The uncorrected altitude (based on reference standard 29.92 inHg, 1013.25 mb) provides a reference for algorithms that utilize "altitude deltas" between aircraft. This value is provided in meters.""" + + +class AircraftState(ImplicitDict): + """State of an aircraft for the purposes of simulating the execution of a flight plan.""" + + id: str + """Unique identifier for this aircraft state/telemetry. The content of an AircraftState with a given ID must not change over time. Therefore, if a USS has already queued an aircraft state with this ID as telemetry to be delivered, this state may be ignored.""" + + timestamp: Time + """Time at which this state is valid. This is equivalent to the time of sensor measurement, so the USS's primary system under test should not be aware of this state until after this time.""" + + timestamp_accuracy: float | None = 0.0 + """Declaration of timestamp accuracy, which is the one-sided width of the 95%-confidence interval around `timestamp` for the true time of applicability for any of the data fields.""" + + position: AircraftPosition | None + + operational_status: OperationalStatus | None + + uas_state: FunctionalState | None + """State of the user's UAS associated with this flight plan as reported by the operator. If the UAS reports its own state, that report will be reflected here. If the operator reports the UAS state, that report will be reflected in the BasicFlightPlanInformation. + If a system accepts UAS state reports from both the operator and UAS, it is possible the reported state may differ between those two sources. + """ + + track: float | None = 0.0 + """Direction of flight expressed as a "True North-based" ground track angle. This value is provided in degrees East of North. A value of 360 indicates that the true value is unavailable.""" + + speed: float | None = 0.0 + """Ground speed of flight in meters per second.""" + + speed_accuracy: SpeedAccuracy | None + """Accuracy of horizontal ground speed.""" + + vertical_speed: float | None = 0.0 + """Geodetic vertical speed upward. Units of meters per second.""" + + +class FlightTelemetry(ImplicitDict): + """When this information is present and the USS supports telemetry ingestion for a flight, the USS receiving this information should enqueue these telemetry reports to be delivered to the system, as if they were coming from the user, at (or soon after) the specified times.""" + + states: list[AircraftState] | None = [] + """The set of telemetry data that should be injected into the system (as if reported by the user or the user's system) at the appropriate times (and not before) for this flight.""" diff --git a/monitoring/monitorlib/clients/flight_planning/test_preparation.py b/monitoring/monitorlib/clients/flight_planning/test_preparation.py index f9134956a7..c01ac7836e 100644 --- a/monitoring/monitorlib/clients/flight_planning/test_preparation.py +++ b/monitoring/monitorlib/clients/flight_planning/test_preparation.py @@ -1,13 +1,13 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.fetch import Query class ClearAreaOutcome(ImplicitDict): - success: bool | None = False + success: Optional[bool] = False """True if, and only if, all flight plans in the specified area managed by the USS were canceled and removed.""" - message: str | None + message: Optional[str] """If the USS was unable to clear the entire area, this message can provide information on the problem encountered.""" @@ -16,7 +16,7 @@ class ClearAreaResponse(ImplicitDict): class TestPreparationActivityResponse(ImplicitDict): - errors: list[str] | None = None + errors: Optional[list[str]] = None """If any errors occurred during this activity, a list of those errors.""" queries: list[Query] diff --git a/monitoring/monitorlib/clients/geospatial_info/querying.py b/monitoring/monitorlib/clients/geospatial_info/querying.py index 7880b67ab2..424e36e797 100644 --- a/monitoring/monitorlib/clients/geospatial_info/querying.py +++ b/monitoring/monitorlib/clients/geospatial_info/querying.py @@ -1,6 +1,6 @@ -from enum import Enum +from enum import StrEnum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.interuss.automated_testing.geospatial_map.v1 import ( api as geospatial_map_api, ) @@ -9,7 +9,7 @@ from monitoring.monitorlib.geotemporal import Volume4D -class OperationalImpact(str, Enum): +class OperationalImpact(StrEnum): """The specified outcome if a user attempted to plan a flight.""" Block = "Block" @@ -27,18 +27,18 @@ class GeospatialFeatureFilter(ImplicitDict): # TODO: Add position - volumes4d: list[Volume4D] | None + volumes4d: Optional[list[Volume4D]] """If specified, only select geospatial features at least partially intersecting one or more of these volumes.""" # TODO: Add after & before - restriction_source: str | None + restriction_source: Optional[str] """If specified, only select geospatial features originating from the named source. The acceptable values for this field will be established by the test designers and will generally be used to limit responses to only the intended datasets under test even when the USS may have more additional geospatial features from other sources that may otherwise be relevant.""" - operation_rule_set: str | None + operation_rule_set: Optional[str] """If specified, only select geospatial features that would be relevant when planning an operation under the specified rule set. The acceptable values for this field will be established by the test designers and will generally correspond to sets of rules under which the system under test plans operations.""" - resulting_operational_impact: OperationalImpact | None + resulting_operational_impact: Optional[OperationalImpact] """If specified, only select geospatial features that would cause the specified outcome if a user attempted to plan a flight applicable to all the other criteria in this filter set.""" def to_geospatial_map(self) -> geospatial_map_api.GeospatialFeatureFilterSet: @@ -59,7 +59,7 @@ def to_geospatial_map(self) -> geospatial_map_api.GeospatialFeatureFilterSet: class GeospatialFeatureCheck(ImplicitDict): - filter_sets: list[GeospatialFeatureFilter] | None + filter_sets: Optional[list[GeospatialFeatureFilter]] """Select geospatial features which match any of the specified filter sets.""" def to_geospatial_map(self) -> geospatial_map_api.GeospatialMapCheck: @@ -68,7 +68,7 @@ def to_geospatial_map(self) -> geospatial_map_api.GeospatialMapCheck: ) -class SelectionOutcome(str, Enum): +class SelectionOutcome(StrEnum): """Indication of whether one or more applicable geospatial features were selected.""" Present = "Present" @@ -88,7 +88,7 @@ class GeospatialFeatureCheckResult(ImplicitDict): features_selection_outcome: SelectionOutcome """Indication of whether one or more applicable geospatial features were selected according to the selection criteria of the corresponding check.""" - message: str | None + message: Optional[str] """A human-readable description of why the unsuccessful `features_selection_outcome` was reported. Should only be populated when appropriate according to the value of the `features_selection_outcome` field.""" diff --git a/monitoring/monitorlib/clients/mock_uss/interactions.py b/monitoring/monitorlib/clients/mock_uss/interactions.py index 85e0ceecad..8f5a3196e2 100644 --- a/monitoring/monitorlib/clients/mock_uss/interactions.py +++ b/monitoring/monitorlib/clients/mock_uss/interactions.py @@ -1,14 +1,12 @@ from datetime import datetime -from enum import Enum +from enum import StrEnum -import yaml from implicitdict import ImplicitDict -from yaml.representer import Representer from monitoring.monitorlib.fetch import Query -class QueryDirection(str, Enum): +class QueryDirection(StrEnum): Incoming = "Incoming" """The query originated from a remote client and was handled by the system reporting the interaction.""" @@ -42,8 +40,5 @@ def interaction_time(self) -> datetime: ) -yaml.add_representer(Interaction, Representer.represent_dict) - - class ListLogsResponse(ImplicitDict): interactions: list[Interaction] diff --git a/monitoring/monitorlib/clients/mock_uss/mock_uss_scd_injection_api.py b/monitoring/monitorlib/clients/mock_uss/mock_uss_scd_injection_api.py index 526a157435..3a3bb15abb 100644 --- a/monitoring/monitorlib/clients/mock_uss/mock_uss_scd_injection_api.py +++ b/monitoring/monitorlib/clients/mock_uss/mock_uss_scd_injection_api.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.interuss.automated_testing.flight_planning.v1.api import ( UpsertFlightPlanRequest, ) @@ -29,10 +29,10 @@ class MockUssFlightBehavior(ImplicitDict): class MockUSSInjectFlightRequest(InjectFlightRequest): """InjectFlightRequest sent to mock_uss, which looks for the optional additional fields below.""" - behavior: MockUssFlightBehavior | None + behavior: Optional[MockUssFlightBehavior] class MockUSSUpsertFlightPlanRequest(UpsertFlightPlanRequest): """UpsertFlightPlanRequest sent to mock_uss, which looks for the optional additional fields below.""" - behavior: MockUssFlightBehavior | None + behavior: Optional[MockUssFlightBehavior] diff --git a/monitoring/monitorlib/clients/versioning/client_interuss.py b/monitoring/monitorlib/clients/versioning/client_interuss.py old mode 100644 new mode 100755 index b7abd515a2..f1a267c67e --- a/monitoring/monitorlib/clients/versioning/client_interuss.py +++ b/monitoring/monitorlib/clients/versioning/client_interuss.py @@ -43,6 +43,17 @@ def get_version(self, version_type: str | None) -> GetVersionResponse: raise VersionQueryError( f"Response to get version could not be parsed: {str(e)}", query ) + + if not resp.has_field_with_value("system_identity"): + raise VersionQueryError( + "Response to get version didn't return a system identity" + ) + + if not resp.has_field_with_value("system_version"): + raise VersionQueryError( + "Response to get version didn't return a system version" + ) + if resp.system_identity != version_type: raise VersionQueryError( f"Response to get version indicated version for system '{resp.system_identity}' when the version for system '{version_type}' was requested" diff --git a/monitoring/monitorlib/clients/versioning/client_interuss_test.py b/monitoring/monitorlib/clients/versioning/client_interuss_test.py new file mode 100644 index 0000000000..751e22240f --- /dev/null +++ b/monitoring/monitorlib/clients/versioning/client_interuss_test.py @@ -0,0 +1,110 @@ +from datetime import datetime + +import pytest +from implicitdict import StringBasedDateTime + +from monitoring.monitorlib.fetch import ( + Query, + RequestDescription, + ResponseDescription, +) +from monitoring.monitorlib.infrastructure import ( + utm_client_session_factory, +) +from monitoring.uss_qualifier.configurations.configuration import ParticipantID + +from .client_interuss import InterUSSVersioningClient, VersionQueryError + + +@pytest.fixture +def client(): + return InterUSSVersioningClient( + utm_client_session_factory.get_session(prefix_url="/"), ParticipantID() + ) + + +def build_query_response(code, data): + return Query( + request=RequestDescription( + method=None, + url=None, + initiated_at=StringBasedDateTime(datetime.fromtimestamp(0)), + ), + response=ResponseDescription(elapsed_s=0, reported=None, code=code, json=data), + ) + + +def test_get_version_nominal(mocker, client): + mocker.patch( + "monitoring.monitorlib.clients.versioning.client_interuss.query_and_describe", + return_value=build_query_response( + 200, {"system_identity": "test", "system_version": "test_version"} + ), + ) + + assert client.get_version("test").version == "test_version" + + +def test_get_version_non_200(mocker, client): + mocker.patch( + "monitoring.monitorlib.clients.versioning.client_interuss.query_and_describe", + return_value=build_query_response( + 500, {"system_identity": "test", "system_version": "test_version"} + ), + ) + + with pytest.raises(VersionQueryError, match="rather than 200 as expected"): + client.get_version("test") + + +def test_get_version_wrong_body(mocker, client): + mocker.patch( + "monitoring.monitorlib.clients.versioning.client_interuss.query_and_describe", + return_value=build_query_response(200, None), + ) + + with pytest.raises( + VersionQueryError, match="Response to get version could not be parsed" + ): + client.get_version("test") + + +def test_get_version_no_system_identity(mocker, client): + mocker.patch( + "monitoring.monitorlib.clients.versioning.client_interuss.query_and_describe", + return_value=build_query_response(200, {"system_version": "test_version"}), + ) + + with pytest.raises( + VersionQueryError, + match="Response to get version didn't return a system identity", + ): + client.get_version("test") + + +def test_get_version_no_system_version(mocker, client): + mocker.patch( + "monitoring.monitorlib.clients.versioning.client_interuss.query_and_describe", + return_value=build_query_response(200, {"system_identity": "test"}), + ) + + with pytest.raises( + VersionQueryError, + match="Response to get version didn't return a system version", + ): + client.get_version("test") + + +def test_get_version_another_identity(mocker, client): + mocker.patch( + "monitoring.monitorlib.clients.versioning.client_interuss.query_and_describe", + return_value=build_query_response( + 200, {"system_identity": "test2", "system_version": "test_version"} + ), + ) + + with pytest.raises( + VersionQueryError, + match="Response to get version indicated version for system 'test2'", + ): + client.get_version("test") diff --git a/monitoring/monitorlib/fetch/__init__.py b/monitoring/monitorlib/fetch/__init__.py index 455c9417d8..306a466757 100644 --- a/monitoring/monitorlib/fetch/__init__.py +++ b/monitoring/monitorlib/fetch/__init__.py @@ -1,24 +1,29 @@ +import copy import datetime import json import os import traceback import uuid from dataclasses import dataclass -from enum import Enum -from typing import TypeVar +from enum import StrEnum +from http.client import RemoteDisconnected +from typing import Optional, Self, TypeVar from urllib.parse import urlparse import flask import jwt import requests import urllib3 -import yaml -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ( + ImplicitDict, + StringBasedDateTime, + StringBasedTimeDelta, +) from loguru import logger -from yaml.representer import Representer from monitoring.monitorlib import infrastructure from monitoring.monitorlib.errors import stacktrace_string +from monitoring.monitorlib.infrastructure import AUTHORIZATION_DT from monitoring.monitorlib.rid import RIDVersion @@ -47,12 +52,15 @@ class Settings: class RequestDescription(ImplicitDict): method: str url: str - headers: dict | None - json: dict | None = None - body: str | None = None + headers: Optional[dict] + json: Optional[dict] = None + body: Optional[str] = None - initiated_at: StringBasedDateTime | None - received_at: StringBasedDateTime | None + initiated_at: Optional[StringBasedDateTime] + received_at: Optional[StringBasedDateTime] + + auth_dt: Optional[StringBasedTimeDelta] + """Amount of time required to obtain authorization before performing the primary query (de minimus or unknown by default).""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -95,9 +103,6 @@ def content(self) -> str | None: return self.body -yaml.add_representer(RequestDescription, Representer.represent_dict) - - def describe_flask_request(request: flask.Request) -> RequestDescription: headers = {k: v for k, v in request.headers} kwargs = { @@ -127,6 +132,11 @@ def describe_request( "initiated_at": StringBasedDateTime(initiated_at), "headers": headers, } + authorization_dt: datetime.timedelta | None = getattr(req, AUTHORIZATION_DT, None) + if authorization_dt: + kwargs["auth_dt"] = StringBasedTimeDelta( + f"{authorization_dt.total_seconds():.4g}s" + ) body = req.body.decode("utf-8") if req.body else None try: if body: @@ -139,13 +149,13 @@ def describe_request( class ResponseDescription(ImplicitDict): - code: int | None = None - failure: str | None - headers: dict | None + code: Optional[int] = None + failure: Optional[str] + headers: Optional[dict] elapsed_s: float reported: StringBasedDateTime - json: dict | None = None - body: str | None = None + json: Optional[dict] = None + body: Optional[str] = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -164,9 +174,6 @@ def content(self) -> str | None: return self.body -yaml.add_representer(ResponseDescription, Representer.represent_dict) - - def describe_response(resp: requests.Response) -> ResponseDescription: headers = {k: v for k, v in resp.headers.items()} kwargs = { @@ -223,7 +230,9 @@ def describe_flask_response(resp: flask.Response, elapsed_s: float): return ResponseDescription(**kwargs) -class QueryType(str, Enum): +class QueryType(StrEnum): + Unknown = "unknown" + # ASTM F3411-19 and F3411-22a (RID) # DSS endpoints F3411v19DSSSearchIdentificationServiceAreas = ( @@ -389,6 +398,7 @@ class QueryType(str, Enum): ) # InterUSS mock_uss + InterUSSMockUSSGetClock = "interuss.mock_uss.clock" InterUSSMockUSSGetLogs = "interuss.mock_uss.logging.interaction_logs" InterUSSMockUSSGetLocality = "interuss.mock_uss.locality.locality_get" InterUSSMockUSSSetLocality = "interuss.mock_uss.locality.locality_set" @@ -452,12 +462,15 @@ class Query(ImplicitDict): request: RequestDescription response: ResponseDescription - participant_id: str | None + participant_id: Optional[str] """If specified, identifier of the USS/participant hosting the server involved in this query.""" - query_type: QueryType | None + query_type: Optional[QueryType] """If specified, the recognized type of this query.""" + _previous_query: Self | None + """If specified, the previous, failling query that generated this query as a retry""" + @property def timestamp(self) -> datetime.datetime: """Safety property to prevent crashes when Query.timestamp is accessed. @@ -569,20 +582,17 @@ def stacktrace(self) -> str: return stacktrace_string(self) -yaml.add_representer(Query, Representer.represent_dict) - -yaml.add_representer(StringBasedDateTime, Representer.represent_str) - - def describe_query( resp: requests.Response, initiated_at: datetime.datetime, query_type: QueryType | None = None, participant_id: str | None = None, + previous_query: Query | None = None, ) -> Query: query = Query( request=describe_request(resp.request, initiated_at), response=describe_response(resp), + _previous_query=previous_query, ) if query_type is not None: query.query_type = query_type @@ -618,10 +628,9 @@ def query_and_describe( Query object describing the request and response/result. """ if client is None: - utm_session = False - client = requests.session() + _client = requests.session() else: - utm_session = True + _client = client req_kwargs = kwargs.copy() if "timeout" not in req_kwargs: req_kwargs["timeout"] = ( @@ -655,6 +664,36 @@ def get_location() -> str: .strip() ) + previous_query = None + + def build_failing_query(t0) -> Query: + _req_kwargs = copy.deepcopy(req_kwargs) + + if isinstance(_client, infrastructure.UTMClientSession): + _req_kwargs = _client.adjust_request_kwargs(_req_kwargs) + del _req_kwargs["timeout"] + + req = requests.Request(verb, url, **_req_kwargs) + prepped_req = _client.prepare_request(req) + + t1 = datetime.datetime.now(datetime.UTC) + + query = Query( + request=describe_request(prepped_req, t0), + response=ResponseDescription( + code=None, + failure="\n".join(failures), + elapsed_s=(t1 - t0).total_seconds(), + reported=StringBasedDateTime(t1), + ), + participant_id=participant_id, + _previous_query=previous_query, + ) + if query_type is not None: + query.query_type = query_type + + return query + # Note: retry logic could be attached to the `client` Session by `mount`ing an HTTPAdapter with custom # `max_retries`, however we do not want to mutate the provided Session. Instead, retry only on errors we explicitly # consider retryable. @@ -664,13 +703,14 @@ def get_location() -> str: if is_netloc_fake: failure_message = f"query_and_describe attempt {attempt + 1} from PID {os.getpid()} to {verb} {url} was not attempted because network location of {url} was identified as fake: {settings.fake_netlocs}\nAt {get_location()}" failures.append(failure_message) - break + return build_failing_query(t0) return describe_query( - client.request(verb, url, **req_kwargs), + _client.request(verb, url, **req_kwargs), t0, query_type=query_type, participant_id=participant_id, + previous_query=previous_query, ) except (requests.Timeout, urllib3.exceptions.ReadTimeoutError) as e: failure_message = f"query_and_describe attempt {attempt + 1} from PID {os.getpid()} to {verb} {url} failed with timeout {type(e).__name__}: {str(e)}\nAt {get_location()}" @@ -678,10 +718,21 @@ def get_location() -> str: logger.warning(failure_message) failures.append(failure_message) except requests.ConnectionError as e: - if "RemoteDisconnected" in str(e): - # This error manifests as: - # ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) - # ...and this may be retryable + + def context_contains(exception, types) -> bool: + if ( + exception.__class__ in types + ): # We don't use isinstance, we want exact type + return True + + parent = getattr(exception, "__context__", None) + + if parent: + return context_contains(parent, types) + else: + return False + + if context_contains(e, (RemoteDisconnected, ConnectionResetError)): retryable = True else: retryable = False @@ -689,38 +740,28 @@ def get_location() -> str: if not expect_failure: logger.warning(failure_message) failures.append(failure_message) + if not retryable: - break + return build_failing_query(t0) + except requests.RequestException as e: failure_message = f"query_and_describe attempt {attempt + 1} from PID {os.getpid()} to {verb} {url} failed with non-retryable RequestException {type(e).__name__}: {str(e)}\nAt {get_location()}" if not expect_failure: logger.warning(failure_message) failures.append(failure_message) - break - finally: - t1 = datetime.datetime.now(datetime.UTC) - - # Reconstruct request similar to the one in the query (which is not - # accessible at this point) - if utm_session: - req_kwargs = client.adjust_request_kwargs(req_kwargs) - del req_kwargs["timeout"] - req = requests.Request(verb, url, **req_kwargs) - prepped_req = client.prepare_request(req) - result = Query( - request=describe_request(prepped_req, t0), - response=ResponseDescription( - code=None, - failure="\n".join(failures), - elapsed_s=(t1 - t0).total_seconds(), - reported=StringBasedDateTime(t1), - ), - participant_id=participant_id, - ) - if query_type is not None: - result.query_type = query_type - return result + return build_failing_query(t0) + + previous_query = build_failing_query( + t0 + ) # If we arrive there, query failled, but is retriable + + if not previous_query: + raise Exception( + "Internal error: arrived after retried without any expected failled query" + ) + + return previous_query # Previous query is the last failled one def describe_flask_query( diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 999873f3fd..672a435d0c 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -4,8 +4,7 @@ from typing import Any import s2sphere -import yaml -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v19, v22a from uas_standards.astm.f3411.v22a.api import ( @@ -13,7 +12,6 @@ RIDHeight, VerticalAccuracy, ) -from yaml.representer import Representer from monitoring.monitorlib import fetch, geo, rid_v1, rid_v2 from monitoring.monitorlib.fetch import Query, QueryType @@ -24,8 +22,8 @@ class ISA(ImplicitDict): """Version-independent representation of a F3411 identification service area.""" - v19_value: v19.api.IdentificationServiceArea | None = None - v22a_value: v22a.api.IdentificationServiceArea | None = None + v19_value: Optional[v19.api.IdentificationServiceArea] = None + v22a_value: Optional[v22a.api.IdentificationServiceArea] = None @property def rid_version(self) -> RIDVersion: @@ -152,16 +150,16 @@ class Position(ImplicitDict): time: datetime.datetime """Timestamp for the position.""" - height: RIDHeight | None + height: Optional[RIDHeight] - accuracy_v: ( - VerticalAccuracy | None - ) # Note: we use the enum defined in the v2 API as it is equivalent (and thus compatible) to the v19 one + accuracy_v: Optional[ + VerticalAccuracy + ] # Note: we use the enum defined in the v2 API as it is equivalent (and thus compatible) to the v19 one """Vertical error that is likely to be present in this reported position""" - accuracy_h: ( - HorizontalAccuracy | None - ) # Note: we use the enum defined in the v2 API as it is equivalent (and thus compatible) to the v19 one + accuracy_h: Optional[ + HorizontalAccuracy + ] # Note: we use the enum defined in the v2 API as it is equivalent (and thus compatible) to the v19 one """Horizontal error that is likely to be present in this reported position.""" @staticmethod @@ -198,8 +196,8 @@ def from_v22a_rid_aircraft_position( class Flight(ImplicitDict): """Version-independent representation of a F3411 flight.""" - v19_value: v19.api.RIDFlight | None = None - v22a_value: v22a.api.RIDFlight | None = None + v19_value: Optional[v19.api.RIDFlight] = None + v22a_value: Optional[v22a.api.RIDFlight] = None @property def rid_version(self) -> RIDVersion: @@ -488,8 +486,8 @@ def errors(self) -> list[str]: class FlightDetails(ImplicitDict): """Version-independent representation of details for a F3411 flight.""" - v19_value: v19.api.RIDFlightDetails | None = None - v22a_value: v22a.api.RIDFlightDetails | None = None + v19_value: Optional[v19.api.RIDFlightDetails] = None + v22a_value: Optional[v22a.api.RIDFlightDetails] = None @property def rid_version(self) -> RIDVersion: @@ -673,8 +671,8 @@ def registration_id( class Subscription(ImplicitDict): """Version-independent representation of a F3411 subscription.""" - v19_value: v19.api.Subscription | None = None - v22a_value: v22a.api.Subscription | None = None + v19_value: Optional[v19.api.Subscription] = None + v22a_value: Optional[v22a.api.Subscription] = None @property def duration(self) -> datetime.timedelta | None: @@ -734,7 +732,7 @@ def version(self) -> str: return self.raw.version @property - def time_start(self) -> datetime: + def time_start(self) -> datetime.datetime: if self.rid_version == RIDVersion.f3411_19: return self.v19_value.time_start.datetime elif self.rid_version == RIDVersion.f3411_22a: @@ -745,7 +743,7 @@ def time_start(self) -> datetime: ) @property - def time_end(self) -> datetime: + def time_end(self) -> datetime.datetime: if self.rid_version == RIDVersion.f3411_19: return self.v19_value.time_end.datetime elif self.rid_version == RIDVersion.f3411_22a: @@ -793,8 +791,8 @@ def owner(self) -> str: class RIDQuery(ImplicitDict): - v19_query: Query | None = None - v22a_query: Query | None = None + v19_query: Optional[Query] = None + v22a_query: Optional[Query] = None @property def rid_version(self) -> RIDVersion: @@ -1625,12 +1623,3 @@ def subscriptions( raise NotImplementedError( f"Cannot query DSS for subscriptions using RID version {rid_version}" ) - - -yaml.add_representer(FetchedISA, Representer.represent_dict) -yaml.add_representer(FetchedISAs, Representer.represent_dict) -yaml.add_representer(FetchedUSSFlights, Representer.represent_dict) -yaml.add_representer(FetchedUSSFlightDetails, Representer.represent_dict) -yaml.add_representer(FetchedFlights, Representer.represent_dict) -yaml.add_representer(FetchedSubscription, Representer.represent_dict) -yaml.add_representer(FetchedSubscriptions, Representer.represent_dict) diff --git a/monitoring/monitorlib/fetch/scd.py b/monitoring/monitorlib/fetch/scd.py index e64fce1999..0cc21a9af5 100644 --- a/monitoring/monitorlib/fetch/scd.py +++ b/monitoring/monitorlib/fetch/scd.py @@ -1,8 +1,7 @@ import datetime import s2sphere -import yaml -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.astm.f3548.v21.api import ( OPERATIONS, OperationID, @@ -10,7 +9,6 @@ Subscription, ) from uas_standards.astm.f3548.v21.api import Volume4D as SCDVolume4D -from yaml.representer import Representer from monitoring.monitorlib import fetch, infrastructure, scd from monitoring.monitorlib.fetch import QueryType @@ -21,7 +19,7 @@ class FetchedEntityReferences(fetch.Query): """Wrapper to interpret a DSS Entity query as a set of Entity references.""" - entity_type: str | None = None + entity_type: Optional[str] = None @property def success(self) -> bool: @@ -72,9 +70,6 @@ def has_different_content_than(self, other): return False -yaml.add_representer(FetchedEntityReferences, Representer.represent_dict) - - def _entity_references( dss_resource_name: str, utm_client: infrastructure.UTMClientSession, @@ -125,8 +120,8 @@ def operational_intent_references( class FetchedEntity(fetch.Query): - id_requested: str | None = None - entity_type: str | None = None + id_requested: Optional[str] = None + entity_type: Optional[str] = None @property def success(self) -> bool: @@ -176,9 +171,6 @@ def has_different_content_than(self, other): return self.error != other.error -yaml.add_representer(FetchedEntity, Representer.represent_dict) - - def _full_entity( uss_resource_name: str, uss_base_url: str, @@ -254,9 +246,6 @@ def has_different_content_than(self, other): return True -yaml.add_representer(FetchedEntities, Representer.represent_dict) - - class CachedEntity(ImplicitDict): reference: dict uss_query: FetchedEntity @@ -404,9 +393,6 @@ def subscription(self) -> Subscription | None: return None -yaml.add_representer(FetchedSubscription, Representer.represent_dict) - - class FetchedSubscriptions(fetch.Query): @property def success(self) -> bool: @@ -442,9 +428,6 @@ def subscriptions(self) -> dict[str, Subscription]: return {sub.id: sub for sub in self._subscriptions} -yaml.add_representer(FetchedSubscriptions, Representer.represent_dict) - - def get_subscription( utm_client: infrastructure.UTMClientSession, subscription_id: str, diff --git a/monitoring/monitorlib/geo.py b/monitoring/monitorlib/geo.py index 1e5292735a..defe408c90 100644 --- a/monitoring/monitorlib/geo.py +++ b/monitoring/monitorlib/geo.py @@ -2,12 +2,13 @@ import math import os -from enum import Enum +from enum import StrEnum import numpy as np +import pyproj import s2sphere import shapely.geometry -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from s2sphere import LatLng from scipy.interpolate import RectBivariateSpline as Spline from uas_standards.astm.f3411.v19 import api as f3411v19 @@ -37,7 +38,7 @@ COORD_TOLERANCE_DEG = 360 / EARTH_CIRCUMFERENCE_M * DISTANCE_TOLERANCE_M -class DistanceUnits(str, Enum): +class DistanceUnits(StrEnum): M = "M" """Meters""" @@ -109,7 +110,7 @@ def in_meters(self) -> float: class Polygon(ImplicitDict): - vertices: list[LatLngPoint] | None + vertices: Optional[list[LatLngPoint]] def vertex_average(self) -> LatLngPoint: lat = sum(p.lat for p in self.vertices) / len(self.vertices) @@ -157,6 +158,28 @@ def from_f3548v21(vol: f3548v21.Polygon | dict) -> Polygon: vertices=[ImplicitDict.parse(p, LatLngPoint) for p in vol.vertices] ) + def is_equivalent(self, other: Polygon) -> bool: + if "vertices" not in self and "vertices" not in other: + return True + elif "vertices" not in self or "vertices" not in other: + return False + + if self.vertices == other.vertices: + # covers both None and exact equality + return True + elif not self.vertices or not other.vertices: + # covers one being None + return False + elif len(self.vertices) != len(other.vertices): + return False + + return all( + [ + vertices[0].match(vertices[1]) + for vertices in zip(self.vertices, other.vertices) + ] + ) + class Circle(ImplicitDict): center: LatLngPoint @@ -180,8 +203,17 @@ def from_f3548v21(vol: f3548v21.Circle | dict) -> Circle: radius=ImplicitDict.parse(vol.radius, Radius), ) + def is_equivalent(self, other: Circle) -> bool: + if not self.center.match(other.center): + return False + + return ( + abs(self.radius.in_meters() - other.radius.in_meters()) + <= DISTANCE_TOLERANCE_M + ) -class AltitudeDatum(str, Enum): + +class AltitudeDatum(StrEnum): W84 = "W84" """WGS84 reference ellipsoid""" @@ -207,16 +239,36 @@ def to_flight_planning_api(self) -> fp_api.Altitude: units=fp_api.AltitudeUnits.M, ) + def to_w84_m(self) -> float: + """This altitude expressed in WGS84 meters, if possible to convert to it.""" + if self.reference != AltitudeDatum.W84: + raise NotImplementedError( + f"Cannot convert altitude with reference {self.reference} to WGS84 meters" + ) + if self.units != DistanceUnits.M: + raise NotImplementedError( + f"Cannot convert altitude with units {self.units} to WGS84 meters" + ) + return self.value + @staticmethod def from_f3548v21(vol: f3548v21.Altitude | dict) -> Altitude: return ImplicitDict.parse(vol, Altitude) + def is_equivalent(self, other: Altitude) -> bool: + if self.reference != other.reference: + return False + return ( + abs(self.units.in_meters(self.value) - other.units.in_meters(other.value)) + <= DISTANCE_TOLERANCE_M + ) + class Volume3D(ImplicitDict): - outline_circle: Circle | None = None - outline_polygon: Polygon | None = None - altitude_lower: Altitude | None = None - altitude_upper: Altitude | None = None + outline_circle: Optional[Circle] = None + outline_polygon: Optional[Polygon] = None + altitude_lower: Optional[Altitude] = None + altitude_upper: Optional[Altitude] = None def altitude_lower_wgs84_m(self, default_value: float | None = None) -> float: if self.altitude_lower is None: @@ -297,7 +349,7 @@ def intersects_vol3(self, vol3_2: Volume3D) -> bool: return footprint1.intersects(footprint2) - def transform(self, transformation: Transformation): + def transform(self, transformation: Transformation) -> Volume3D: if ( "relative_translation" in transformation and transformation.relative_translation @@ -434,6 +486,38 @@ def s2_vertices(self) -> list[s2sphere.LatLng]: else: return get_latlngrect_vertices(make_latlng_rect(self)) + def is_equivalent( + self, + other: Volume3D, + ) -> bool: + if self.altitude_lower and other.altitude_lower: + if not self.altitude_lower.is_equivalent(other.altitude_lower): + return False + elif self.altitude_lower or other.altitude_lower: + return False + + if self.altitude_upper and other.altitude_upper: + if not self.altitude_upper.is_equivalent(other.altitude_upper): + return False + elif self.altitude_upper or other.altitude_upper: + return False + + if self.outline_polygon and other.outline_polygon: + if not self.outline_polygon.is_equivalent(other.outline_polygon): + return False + elif self.outline_circle and other.outline_circle: + if not self.outline_circle.is_equivalent(other.outline_circle): + return False + elif ( + self.outline_circle + or self.outline_polygon + or other.outline_circle + or other.outline_polygon + ): + return False + + return True + def make_latlng_rect(area) -> s2sphere.LatLngRect: """Make an S2 LatLngRect from the provided input. @@ -684,6 +768,33 @@ def egm96_geoid_offset(p: s2sphere.LatLng) -> float: return _egm96.ev(-lat, lng) +def egm2008_geoid_offset(p: s2sphere.LatLng) -> float: + """Estimate the EGM2008 geoid height above the WGS84 ellipsoid. + + Args: + p: Point where offset should be estimated. + + Returns: Meters above WGS84 ellipsoid of the EGM2008 geoid at p. + """ + + if not pyproj.network.is_network_enabled(): # pyright:ignore[reportAttributeAccessIssue] + raise Exception(""" +To enable EGM2008 conversions, you must allow pyproj to download files to do the conversion. For that, please set PROJ_NETWORK=TRUE in your environment variables. + +Please ensure this comply with your policies, and avoid multiple downloads by mounting the directoy '/root/.local/share/proj/' to a docker volume. +""") + + transformer = pyproj.Transformer.from_crs( + "EPSG:4979", # WGS 84 -- 3D + "EPSG:4326+3855", # WGS 84 (2D) + EGM2008 height (vertical) + always_xy=True, + ) + + _, _, ortho_height = transformer.transform(p.lng().degrees, p.lat().degrees, 0) + + return -ortho_height + + def center_of_mass(in_points: list[LatLng]) -> LatLng: """Compute the center of mass of a polygon defined by a list of points.""" if len(in_points) == 0: diff --git a/monitoring/monitorlib/geo_test.py b/monitoring/monitorlib/geo_test.py index a8396638dd..58938ed25c 100644 --- a/monitoring/monitorlib/geo_test.py +++ b/monitoring/monitorlib/geo_test.py @@ -1,6 +1,16 @@ +import unittest + from s2sphere import LatLng from monitoring.monitorlib.geo import ( + Altitude, + AltitudeDatum, + Circle, + DistanceUnits, + LatLngPoint, + Polygon, + Radius, + Volume3D, generate_area_in_vicinity, generate_slight_overlap_area, ) @@ -12,6 +22,196 @@ def _points(in_points: list[tuple[float, float]]) -> list[LatLng]: return [LatLng.from_degrees(*p) for p in in_points] +class AltitudeIsEquivalentTest(unittest.TestCase): + def setUp(self): + self.alt1 = Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ) + + def test_equivalent_altitudes(self): + alt2 = Altitude(value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M) + self.assertTrue(self.alt1.is_equivalent(alt2)) + + def test_equivalent_altitudes_within_tolerance(self): + alt2 = Altitude( + value=100.000001, reference=AltitudeDatum.W84, units=DistanceUnits.M + ) + self.assertTrue(self.alt1.is_equivalent(alt2)) + + def test_equivalent_altitudes_different_units(self): + alt2 = Altitude( + value=328.084, reference=AltitudeDatum.W84, units=DistanceUnits.FT + ) + self.assertTrue(self.alt1.is_equivalent(alt2)) + + def test_nonequivalent_altitudes_different_value(self): + alt2 = Altitude(value=101, reference=AltitudeDatum.W84, units=DistanceUnits.M) + self.assertFalse(self.alt1.is_equivalent(alt2)) + + def test_nonequivalent_altitudes_different_reference(self): + alt2 = Altitude(value=100, reference=AltitudeDatum.SFC, units=DistanceUnits.M) + self.assertFalse(self.alt1.is_equivalent(alt2)) + + +class PolygonIsEquivalentTest(unittest.TestCase): + def setUp(self): + self.poly1 = Polygon( + vertices=[ + LatLngPoint(lat=10, lng=10), + LatLngPoint(lat=11, lng=10), + LatLngPoint(lat=11, lng=11), + LatLngPoint(lat=10, lng=11), + ] + ) + + def test_equivalent_polygons(self): + poly2 = Polygon( + vertices=[ + LatLngPoint(lat=10, lng=10), + LatLngPoint(lat=11, lng=10), + LatLngPoint(lat=11, lng=11), + LatLngPoint(lat=10, lng=11), + ] + ) + self.assertTrue(self.poly1.is_equivalent(poly2)) + + def test_equivalent_polygons_within_tolerance(self): + poly2 = Polygon( + vertices=[ + LatLngPoint(lat=10.00000001, lng=10.00000001), + LatLngPoint(lat=11.00000001, lng=10.00000001), + LatLngPoint(lat=11.00000001, lng=11.00000001), + LatLngPoint(lat=10.00000001, lng=11.00000001), + ] + ) + self.assertTrue(self.poly1.is_equivalent(poly2)) + + def test_nonequivalent_polygons(self): + poly2 = Polygon( + vertices=[ + LatLngPoint(lat=10, lng=10), + LatLngPoint(lat=12, lng=10), + LatLngPoint(lat=12, lng=11), + LatLngPoint(lat=10, lng=11), + ] + ) + self.assertFalse(self.poly1.is_equivalent(poly2)) + + def test_equivalent_polygons_none(self): + poly1 = Polygon(vertices=None) + poly2 = Polygon(vertices=None) + self.assertTrue(poly1.is_equivalent(poly2)) + + def test_nonequivalent_polygons_one_none(self): + poly1 = Polygon(vertices=[]) + poly2 = Polygon(vertices=None) + self.assertFalse(poly1.is_equivalent(poly2)) + + +class Volume3DIsEquivalentTest(unittest.TestCase): + def setUp(self): + self.vol_poly = Volume3D( + outline_polygon=Polygon( + vertices=[ + LatLngPoint(lat=10, lng=10), + LatLngPoint(lat=11, lng=10), + LatLngPoint(lat=11, lng=11), + LatLngPoint(lat=10, lng=11), + ] + ), + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + altitude_upper=Altitude( + value=200, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + ) + self.vol_circle = Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10, lng=10), + radius=Radius(value=100, units=DistanceUnits.M), + ), + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + altitude_upper=Altitude( + value=200, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + ) + + def test_equivalent_volumes_polygon(self): + vol2 = Volume3D( + outline_polygon=Polygon( + vertices=[ + LatLngPoint(lat=10, lng=10), + LatLngPoint(lat=11, lng=10), + LatLngPoint(lat=11, lng=11), + LatLngPoint(lat=10, lng=11), + ] + ), + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + altitude_upper=Altitude( + value=200, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + ) + self.assertTrue(self.vol_poly.is_equivalent(vol2)) + + def test_equivalent_volumes_circle(self): + vol2 = Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10, lng=10), + radius=Radius(value=100, units=DistanceUnits.M), + ), + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + altitude_upper=Altitude( + value=200, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + ) + self.assertTrue(self.vol_circle.is_equivalent(vol2)) + + def test_nonequivalent_volumes_circle(self): + vol2 = Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10, lng=10), + radius=Radius(value=200, units=DistanceUnits.M), + ), + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + altitude_upper=Altitude( + value=200, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + ) + self.assertFalse(self.vol_circle.is_equivalent(vol2)) + + def test_nonequivalent_volumes_different_shape(self): + vol2 = Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10.5, lng=10.5), + radius=Radius(value=50000, units=DistanceUnits.M), + ) + ) + self.assertFalse(self.vol_poly.is_equivalent(vol2)) + + def test_equivalent_volumes_none_fields(self): + vol1 = Volume3D() + vol2 = Volume3D() + self.assertTrue(vol1.is_equivalent(vol2)) + + def test_nonequivalent_volumes_one_none_field(self): + vol1 = Volume3D( + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ) + ) + vol2 = Volume3D() + self.assertFalse(vol1.is_equivalent(vol2)) + + def test_generate_slight_overlap_area(): # Square around 0,0 of edge length 2 -> first corner at 1,1 -> expect a square with overlapping corner at 1,1 assert generate_slight_overlap_area( diff --git a/monitoring/monitorlib/geotemporal.py b/monitoring/monitorlib/geotemporal.py index 37f818e042..66e24138ae 100644 --- a/monitoring/monitorlib/geotemporal.py +++ b/monitoring/monitorlib/geotemporal.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import s2sphere as s2sphere -from implicitdict import ImplicitDict, StringBasedTimeDelta +from implicitdict import ImplicitDict, Optional, StringBasedTimeDelta from uas_standards.astm.f3548.v21 import api as f3548v21 from uas_standards.interuss.automated_testing.flight_planning.v1 import api as fp_api from uas_standards.interuss.automated_testing.geospatial_map.v1 import ( @@ -13,39 +13,51 @@ from uas_standards.interuss.automated_testing.scd.v1 import api as interuss_scd_api from monitoring.monitorlib import geo -from monitoring.monitorlib.geo import Altitude, Circle, LatLngPoint, Polygon, Volume3D -from monitoring.monitorlib.temporal import TestTime, Time, TimeDuringTest +from monitoring.monitorlib.geo import ( + Altitude, + AltitudeDatum, + Circle, + DistanceUnits, + LatLngPoint, + Polygon, + Volume3D, +) +from monitoring.monitorlib.temporal import ( + TestTime, + TestTimeContext, + Time, +) from monitoring.monitorlib.transformations import Transformation +TIME_TOLERANCE = timedelta(milliseconds=10) + class Volume4DTemplate(ImplicitDict): - outline_polygon: Polygon | None = None + outline_polygon: Optional[Polygon] = None """Polygonal 2D outline/footprint of the specified area. May not be defined if outline_circle is defined.""" - outline_circle: Circle | None = None + outline_circle: Optional[Circle] = None """Circular outline/footprint of the specified area. May not be defined if outline_polygon is defined.""" - start_time: TestTime | None = None + start_time: Optional[TestTime] = None """The time at which the virtual user may start using the specified geospatial area for their flight. May not be defined if duration and end_time are defined.""" - end_time: TestTime | None = None + end_time: Optional[TestTime] = None """The time at which the virtual user will be finished using the specified geospatial area for their flight. May not be defined if duration and start_time are defined.""" - duration: StringBasedTimeDelta | None = None + duration: Optional[StringBasedTimeDelta] = None """If only one of start_time and end_time is specified, then the other time should be separated from the specified time by this amount. May not be defined in both start_time and end_time are defined.""" - altitude_lower: Altitude | None = None + altitude_lower: Optional[Altitude] = None """The minimum altitude at which the virtual user will fly while using this volume for their flight.""" - altitude_upper: Altitude | None = None + altitude_upper: Optional[Altitude] = None """The maximum altitude at which the virtual user will fly while using this volume for their flight.""" - transformations: list[Transformation] | None = None + transformations: Optional[list[Transformation]] = None """If specified, transform this volume according to these transformations in order.""" - def resolve(self, times: dict[TimeDuringTest, Time]) -> Volume4D: - """Resolve Volume4DTemplate into concrete Volume4D.""" - # Make 3D volume + def _get_volume(self) -> Volume3D: kwargs = {} if self.outline_circle is not None: kwargs["outline_circle"] = self.outline_circle @@ -55,18 +67,27 @@ def resolve(self, times: dict[TimeDuringTest, Time]) -> Volume4D: kwargs["altitude_lower"] = self.altitude_lower if self.altitude_upper is not None: kwargs["altitude_upper"] = self.altitude_upper - volume = Volume3D(**kwargs) + return Volume3D(**kwargs) - # Make 4D volume - kwargs = {"volume": volume} + def resolve_3d(self) -> Volume3D: + """Resolve Volume4DTemplate into concrete Volume3D.""" + result = self._get_volume() + if self.transformations: + for xform in self.transformations: + result = result.transform(xform) + return result + def resolve_times( + self, context: TestTimeContext + ) -> tuple[Time | None, Time | None]: + """Resolve Volume4DTemplate into concrete temporal bounds (start, end)""" if self.start_time is not None: - time_start = self.start_time.resolve(times) + time_start = self.start_time.resolve(context) else: time_start = None if self.end_time is not None: - time_end = self.end_time.resolve(times) + time_end = self.end_time.resolve(context) else: time_end = None @@ -84,26 +105,55 @@ def resolve(self, times: dict[TimeDuringTest, Time]) -> Volume4D: if time_end is None: time_end = Time(time_start.datetime + self.duration.timedelta) + return time_start, time_end + + def resolve(self, context: TestTimeContext) -> Volume4D: + """Resolve Volume4DTemplate into concrete Volume4D.""" + volume = self.resolve_3d() + + # Make 4D volume + kwargs = {"volume": volume} + + time_start, time_end = self.resolve_times(context) + if time_start is not None: kwargs["time_start"] = time_start if time_end is not None: kwargs["time_end"] = time_end - result = Volume4D(**kwargs) - - if self.transformations: - for xform in self.transformations: - result = result.transform(xform) - - return result + return Volume4D(**kwargs) class Volume4D(ImplicitDict): """Generic representation of a 4D volume, usable across multiple standards and formats.""" volume: Volume3D - time_start: Time | None = None - time_end: Time | None = None + time_start: Optional[Time] = None + time_end: Optional[Time] = None + + def is_equivalent( + self, + other: Volume4D, + ) -> bool: + if not self.volume.is_equivalent(other.volume): + return False + + if (self.time_start is None) != (other.time_start is None): + return False + if self.time_start and other.time_start: + if ( + abs(self.time_start.datetime - other.time_start.datetime) + > TIME_TOLERANCE + ): + return False + + if (self.time_end is None) != (other.time_end is None): + return False + if self.time_end and other.time_end: + if abs(self.time_end.datetime - other.time_end.datetime) > TIME_TOLERANCE: + return False + + return True def offset_time(self, dt: timedelta) -> Volume4D: kwargs = {"volume": self.volume} @@ -272,6 +322,26 @@ def __iadd__(self, other): f"Cannot iadd {type(other).__name__} to {type(self).__name__}" ) + def is_equivalent( + self, + other: Volume4DCollection, + ) -> bool: + if len(self) != len(other): + return False + + # different order is acceptable + other_copy = list(other) + for vol in self: + found = False + for i, other_vol in enumerate(other_copy): + if vol.is_equivalent(other_vol): + other_copy.pop(i) + found = True + break + if not found: + return False + return True + @property def time_start(self) -> Time | None: return ( @@ -288,6 +358,26 @@ def time_end(self) -> Time | None: else None ) + @property + def altitude_lower(self) -> Altitude | None: + return Altitude( + value=min(v.volume.altitude_lower_wgs84_m() for v in self) + if all(v.volume.altitude_lower_wgs84_m() is not None for v in self) + else None, + reference=AltitudeDatum.W84, + units=DistanceUnits.M, + ) + + @property + def altitude_upper(self) -> Altitude | None: + return Altitude( + value=min(v.volume.altitude_upper_wgs84_m() for v in self) + if all(v.volume.altitude_upper_wgs84_m() is not None for v in self) + else None, + reference=AltitudeDatum.W84, + units=DistanceUnits.M, + ) + def offset_times(self, dt: timedelta) -> Volume4DCollection: return Volume4DCollection([v.offset_time(dt) for v in self]) diff --git a/monitoring/monitorlib/geotemporal_test.py b/monitoring/monitorlib/geotemporal_test.py new file mode 100644 index 0000000000..52de25c148 --- /dev/null +++ b/monitoring/monitorlib/geotemporal_test.py @@ -0,0 +1,138 @@ +import unittest +from datetime import datetime, timedelta + +from monitoring.monitorlib.geo import ( + Altitude, + AltitudeDatum, + Circle, + DistanceUnits, + LatLngPoint, + Radius, + Volume3D, +) +from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection +from monitoring.monitorlib.temporal import Time + + +class Volume4DIsEquivalentTest(unittest.TestCase): + def setUp(self): + self.t0 = datetime.now() + self.t1 = self.t0 + timedelta(minutes=10) + self.vol_3d = Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10, lng=10), + radius=Radius(value=100, units=DistanceUnits.M), + ), + altitude_lower=Altitude( + value=100, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + altitude_upper=Altitude( + value=200, reference=AltitudeDatum.W84, units=DistanceUnits.M + ), + ) + self.vol1 = Volume4D( + volume=self.vol_3d, + time_start=Time(self.t0), + time_end=Time(self.t1), + ) + + def test_equivalent_volume4d(self): + vol2 = Volume4D( + volume=self.vol_3d, + time_start=Time(self.t0), + time_end=Time(self.t1), + ) + self.assertTrue(self.vol1.is_equivalent(vol2)) + + def test_equivalent_volume4d_within_tolerance(self): + # Time within tolerance (default is 10ms) + vol2 = Volume4D( + volume=self.vol_3d, + time_start=Time(self.t0 + timedelta(milliseconds=3)), + time_end=Time(self.t1 - timedelta(milliseconds=3)), + ) + self.assertTrue(self.vol1.is_equivalent(vol2)) + + def test_nonequivalent_volume4d_time_outside_tolerance(self): + vol2 = Volume4D( + volume=self.vol_3d, + time_start=Time(self.t0 + timedelta(seconds=2)), + time_end=Time(self.t1), + ) + self.assertFalse(self.vol1.is_equivalent(vol2)) + + def test_nonequivalent_volume4d_different_volume3d(self): + vol2 = Volume4D( + volume=Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10, lng=10), + radius=Radius(value=200, units=DistanceUnits.M), + ), + altitude_lower=self.vol_3d.altitude_lower, + altitude_upper=self.vol_3d.altitude_upper, + ), + time_start=Time(self.t0), + time_end=Time(self.t1), + ) + self.assertFalse(self.vol1.is_equivalent(vol2)) + + def test_equivalent_volume4d_none_times(self): + vol1_no_time = Volume4D(volume=self.vol_3d) + vol2_no_time = Volume4D(volume=self.vol_3d) + self.assertTrue(vol1_no_time.is_equivalent(vol2_no_time)) + + def test_nonequivalent_volume4d_one_none_time(self): + vol2 = Volume4D(volume=self.vol_3d, time_start=Time(self.t0)) + self.assertFalse(self.vol1.is_equivalent(vol2)) + + +class Volume4DCollectionIsEquivalentTest(unittest.TestCase): + def setUp(self): + self.t0 = datetime.now() + self.v1 = Volume4D( + volume=Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=10, lng=10), + radius=Radius(value=100, units=DistanceUnits.M), + ) + ), + time_start=Time(self.t0), + ) + self.v2 = Volume4D( + volume=Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=20, lng=20), + radius=Radius(value=200, units=DistanceUnits.M), + ) + ), + time_start=Time(self.t0), + ) + self.v3 = Volume4D( + volume=Volume3D( + outline_circle=Circle( + center=LatLngPoint(lat=30, lng=30), + radius=Radius(value=300, units=DistanceUnits.M), + ) + ), + time_start=Time(self.t0), + ) + + def test_equivalent_collection_same_order(self): + c1 = Volume4DCollection([self.v1, self.v2]) + c2 = Volume4DCollection([self.v1, self.v2]) + self.assertTrue(c1.is_equivalent(c2)) + + def test_equivalent_collection_different_order(self): + c1 = Volume4DCollection([self.v1, self.v2]) + c2 = Volume4DCollection([self.v2, self.v1]) + self.assertTrue(c1.is_equivalent(c2)) + + def test_nonequivalent_collection_different_lengths(self): + c1 = Volume4DCollection([self.v1]) + c2 = Volume4DCollection([]) + self.assertFalse(c1.is_equivalent(c2)) + + def test_nonequivalent_collection_different_content(self): + c1 = Volume4DCollection([self.v1, self.v2]) + c2 = Volume4DCollection([self.v1, self.v3]) + self.assertFalse(c1.is_equivalent(c2)) diff --git a/monitoring/monitorlib/idempotency.py b/monitoring/monitorlib/idempotency.py index 60849bf122..b4c4820fcc 100644 --- a/monitoring/monitorlib/idempotency.py +++ b/monitoring/monitorlib/idempotency.py @@ -6,7 +6,7 @@ import arrow import flask -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from loguru import logger from monitoring.monitorlib.multiprocessing import SynchronizedValue @@ -21,8 +21,8 @@ class Response(ImplicitDict): Note that this object is never actually used (in order to maximize performance); instead it serves as documentation of the structure of the fields within a plain JSON dict/object.""" - json: dict | None - body: str | None + json: Optional[dict] + body: Optional[str] code: int timestamp: StringBasedDateTime @@ -51,7 +51,7 @@ def _set_responses(responses: dict[str, Response]) -> bytes: return s.encode("utf-8") -_fulfilled_requests = SynchronizedValue( +_fulfilled_requests = SynchronizedValue[dict[str, Response]]( {}, decoder=_get_responses, encoder=_set_responses, @@ -59,7 +59,7 @@ def _set_responses(responses: dict[str, Response]) -> bytes: ) -def get_hashed_request_id() -> str | None: +def get_hashed_request_id() -> str: """Retrieves an identifier for the request by hashing key characteristics of the request.""" characteristics = flask.request.method + flask.request.url if flask.request.json: @@ -71,7 +71,7 @@ def get_hashed_request_id() -> str | None: ).decode("utf-8") -def idempotent_request(get_request_id: Callable[[], str | None] | None = None): +def idempotent_request(get_request_id: Callable[[], str] | None = None): """Decorator for idempotent Flask view handlers. When subsequent requests are received with the same request identifier, this decorator will use a recent cached @@ -104,20 +104,20 @@ def wrapper(*args, **kwargs): request_id, ) response = cached_requests[request_id] - if response["body"] is not None: - return response["body"], response["code"] + if response.body is not None: + return response.body, response.code else: - return flask.jsonify(response["json"]), response["code"] + return flask.jsonify(response.json), response.code result = fn(*args, **kwargs) to_return = result - response = { - "timestamp": arrow.utcnow().isoformat(), - "code": 200, - "body": None, - "json": None, - } + response = Response( + timestamp=arrow.utcnow().isoformat(), + code=200, + body=None, + json=None, + ) keep_code = False if isinstance(result, tuple): if len(result) == 2: @@ -125,7 +125,7 @@ def wrapper(*args, **kwargs): raise NotImplementedError( f"Unable to cache Flask view handler result where the second 2-tuple element is a '{type(result[1]).__name__}'" ) - response["code"] = result[1] + response.code = result[1] keep_code = True result = result[0] else: @@ -134,22 +134,22 @@ def wrapper(*args, **kwargs): ) if isinstance(result, str): - response["body"] = result - response["json"] = None + response.body = result + response.json = None elif isinstance(result, flask.Response): try: - response["json"] = result.get_json() + response.json = result.get_json() except ValueError: - response["body"] = result.get_data(as_text=True) + response.body = result.get_data(as_text=True) if not keep_code: - response["code"] = result.status_code + response.code = result.status_code else: raise NotImplementedError( f"Unable to cache Flask view handler result of type '{type(result).__name__}'" ) - with _fulfilled_requests as cached_requests: - cached_requests[request_id] = response + with _fulfilled_requests.transact() as cached_requests_tx: + cached_requests_tx.value[request_id] = response return to_return diff --git a/monitoring/monitorlib/infrastructure.py b/monitoring/monitorlib/infrastructure.py index 8c38434f13..8464f80f9f 100644 --- a/monitoring/monitorlib/infrastructure.py +++ b/monitoring/monitorlib/infrastructure.py @@ -1,12 +1,19 @@ +from __future__ import annotations + import asyncio import datetime import functools +import threading +import time import urllib.parse +import weakref +from dataclasses import dataclass from enum import Enum import jwt import requests from aiohttp import ClientSession +from loguru import logger ALL_SCOPES = [ "dss.write.identification_service_areas", @@ -20,12 +27,22 @@ EPOCH = datetime.datetime.fromtimestamp(0, datetime.UTC) TOKEN_REFRESH_MARGIN = datetime.timedelta(seconds=15) CLIENT_TIMEOUT = 10 # seconds +SOCKET_KEEP_ALIVE_LIMIT = 57 # seconds. + +AUTHORIZATION_DT = "authorization_dt" +"""This attribute may be added to a PreparedRequest indicating the timedelta required to obtain authorization""" AuthSpec = str """Specification for means by which to obtain access tokens.""" +@dataclass +class AdditionalHeaders: + headers: dict[str, str] + token_issuance_seconds: float | None = None + + class AuthAdapter: """Base class for an adapter that add JWTs to requests.""" @@ -37,33 +54,48 @@ def issue_token(self, intended_audience: str, scopes: list[str]) -> str: raise NotImplementedError() - def get_headers(self, url: str, scopes: list[str] | None = None) -> dict[str, str]: + def get_headers( + self, url: str, scopes: list[str] | None = None + ) -> AdditionalHeaders: if scopes is None: scopes = ALL_SCOPES scopes = [s.value if isinstance(s, Enum) else s for s in scopes] intended_audience = urllib.parse.urlparse(url).hostname if not intended_audience: - return {} + return AdditionalHeaders(headers={}) scope_string = " ".join(scopes) if intended_audience not in self._tokens: self._tokens[intended_audience] = {} if scope_string not in self._tokens[intended_audience]: + t0 = time.monotonic() token = self.issue_token(intended_audience, scopes) + dt_s = time.monotonic() - t0 else: token = self._tokens[intended_audience][scope_string] + dt_s = None payload = jwt.decode(token, options={"verify_signature": False}) expires = EPOCH + datetime.timedelta(seconds=payload["exp"]) if datetime.datetime.now(datetime.UTC) > expires - TOKEN_REFRESH_MARGIN: + t0 = time.monotonic() token = self.issue_token(intended_audience, scopes) + dt_s = (dt_s or 0) + (time.monotonic() - t0) self._tokens[intended_audience][scope_string] = token - return {"Authorization": "Bearer " + token} + return AdditionalHeaders( + headers={"Authorization": "Bearer " + token}, token_issuance_seconds=dt_s + ) - def add_headers(self, request: requests.PreparedRequest, scopes: list[str]): + def add_headers( + self, request: requests.PreparedRequest, scopes: list[str] + ) -> AdditionalHeaders: if request.url: - for k, v in self.get_headers(request.url, scopes).items(): + additional_headers = self.get_headers(request.url, scopes) + for k, v in additional_headers.headers.items(): request.headers[k] = v + return additional_headers + else: + return AdditionalHeaders(headers={}) def get_sub(self) -> str | None: """Retrieve `sub` claim from one of the existing tokens""" @@ -84,20 +116,88 @@ class UTMClientSession(requests.Session): If the URL starts with '/', then automatically prefix the URL with the `prefix_url` specified on construction (this is usually the base URL of the DSS). + + When possible, a UTMClientSession should be reused rather than creating a + new one because an excessive number of UTMClientSessions can exhaust the + number of connections allowed by the system (see #1407). """ + _next_session_id: int = 1 + _session_id: int + _session_id_lock = threading.Lock() + + _closure_timer: threading.Timer | None = None + _closure_lock: threading.Lock + _last_used: float | None = None + def __init__( self, prefix_url: str, auth_adapter: AuthAdapter | None = None, timeout_seconds: float | None = None, ): + """Instances should usually be constructed using a factory to avoid unnecessary duplication.""" + super().__init__() + with UTMClientSession._session_id_lock: + self._session_id = UTMClientSession._next_session_id + UTMClientSession._next_session_id += 1 + self._prefix_url = prefix_url[0:-1] if prefix_url[-1] == "/" else prefix_url self.auth_adapter = auth_adapter self.default_scopes: list[str] | None = None self.timeout_seconds = timeout_seconds or CLIENT_TIMEOUT + self._closure_lock = threading.Lock() + + UTMClientSession._start_closure_timer(weakref.ref(self)) + + @staticmethod + def _start_closure_timer(wref: weakref.ReferenceType[UTMClientSession]) -> None: + def wrapper(): + weak_self_final = wref() + if weak_self_final is not None: + try: + weak_self_final.close_if_idle() + finally: + UTMClientSession._start_closure_timer(wref) + + weak_self_initial = wref() + if weak_self_initial: + weak_self_initial._closure_timer = threading.Timer( + weak_self_initial.seconds_until_idle(), wrapper + ) + weak_self_initial._closure_timer.daemon = True + weak_self_initial._closure_timer.start() + + def close_if_idle(self) -> None: + with self._closure_lock: + if ( + self._last_used + and time.monotonic() - self._last_used > SOCKET_KEEP_ALIVE_LIMIT + ): + logger.debug( + "Closing idle UTMClientSession {} to {} with default scopes {} last used {} (now {})", + self._session_id, + self._prefix_url, + ", ".join(self.default_scopes) if self.default_scopes else "", + self._last_used, + time.monotonic(), + ) + self.close() + self._last_used = None + + def seconds_until_idle(self) -> float: + if self._last_used is None: + return SOCKET_KEEP_ALIVE_LIMIT + else: + return max( + 0.0, SOCKET_KEEP_ALIVE_LIMIT - (time.monotonic() - self._last_used) + ) + + def __del__(self): + if timer := self._closure_timer: + timer.cancel() # Overrides method on requests.Session def prepare_request(self, request, **kwargs): @@ -107,6 +207,21 @@ def prepare_request(self, request, **kwargs): return super().prepare_request(request, **kwargs) + def add_auth( + self, prepared_request: requests.PreparedRequest, scopes: list[str] | None + ) -> requests.PreparedRequest: + if scopes and self.auth_adapter: + additional_headers = self.auth_adapter.add_headers(prepared_request, scopes) + if additional_headers.token_issuance_seconds: + setattr( + prepared_request, + AUTHORIZATION_DT, + datetime.timedelta( + seconds=additional_headers.token_issuance_seconds + ), + ) + return prepared_request + def adjust_request_kwargs(self, kwargs): if self.auth_adapter: scopes = None @@ -119,14 +234,7 @@ def adjust_request_kwargs(self, kwargs): if scopes is None: scopes = self.default_scopes - def auth( - prepared_request: requests.PreparedRequest, - ) -> requests.PreparedRequest: - if scopes and self.auth_adapter: - self.auth_adapter.add_headers(prepared_request, scopes) - return prepared_request - - kwargs["auth"] = auth + kwargs["auth"] = lambda req: self.add_auth(req, scopes) if "timeout" not in kwargs: kwargs["timeout"] = self.timeout_seconds return kwargs @@ -135,7 +243,10 @@ def request(self, method, url, *args, **kwargs): if "auth" not in kwargs: kwargs = self.adjust_request_kwargs(kwargs) - return super().request(method, url, *args, **kwargs) + with self._closure_lock: + result = super().request(method, url, *args, **kwargs) + self._last_used = time.monotonic() + return result def get_prefix_url(self): return self._prefix_url @@ -147,6 +258,31 @@ def delete(self, *args, **kwargs): return super().delete(*args, **kwargs) +class UTMClientSessionFactory: + _sessions: dict[tuple, UTMClientSession] + + def __init__(self): + self._sessions = {} + + def get_session( + self, + prefix_url: str, + auth_adapter: AuthAdapter | None = None, + timeout_seconds: float | None = None, + ) -> UTMClientSession: + key = (prefix_url, auth_adapter, timeout_seconds) + if key not in self._sessions: + self._sessions[key] = UTMClientSession( + prefix_url=prefix_url, + auth_adapter=auth_adapter, + timeout_seconds=timeout_seconds, + ) + return self._sessions[key] + + +utm_client_session_factory = UTMClientSessionFactory() + + class AsyncUTMTestSession: """ Requests Asyncio client session that provides additional functionality for running DSS concurrency tests: @@ -192,10 +328,8 @@ def adjust_request_kwargs(self, url, method, kwargs): raise ValueError( "All tests must specify auth scope for all session requests. Either specify as an argument for each individual HTTP call, or decorate the test with @default_scope." ) - headers = {} - for k, v in self.auth_adapter.get_headers(url, scopes).items(): - headers[k] = v - kwargs["headers"] = headers + additional_headers = self.auth_adapter.get_headers(url, scopes) + kwargs["headers"] = additional_headers.headers if method == "PUT" and kwargs.get("data"): kwargs["json"] = kwargs["data"] del kwargs["data"] diff --git a/monitoring/monitorlib/inspection.py b/monitoring/monitorlib/inspection.py index 0350cdd196..8eb8c4314c 100644 --- a/monitoring/monitorlib/inspection.py +++ b/monitoring/monitorlib/inspection.py @@ -2,6 +2,8 @@ import inspect import pkgutil +_modules_imported = set() + def import_submodules(module) -> None: """Ensure that all descendant modules of a module are loaded. @@ -10,10 +12,13 @@ def import_submodules(module) -> None: :param module: Parent module from which to start explicitly importing modules. """ + if module in _modules_imported: + return for loader, module_name, is_pkg in pkgutil.walk_packages( module.__path__, module.__name__ + "." ): importlib.import_module(module_name) + _modules_imported.add(module) def get_module_object_by_name(parent_module, object_name: str): diff --git a/monitoring/monitorlib/kml/flight_planning.py b/monitoring/monitorlib/kml/flight_planning.py index 34e5cffa14..403e6b33e8 100644 --- a/monitoring/monitorlib/kml/flight_planning.py +++ b/monitoring/monitorlib/kml/flight_planning.py @@ -23,13 +23,17 @@ def upsert_flight_plan(req: UpsertFlightPlanRequest, resp: UpsertFlightPlanRespo f"Activity {resp.planning_result.value}, flight {resp.flight_plan_status.value}" ) ) + if "uas_state" in basic_info and basic_info.uas_state: + uas_state = basic_info.uas_state.value + else: + uas_state = None for i, v4_flight_planning in enumerate(basic_info.area or []): v4 = Volume4D.from_flight_planning_api(v4_flight_planning) folder.append( make_placemark_from_volume( v4, name=f"Volume {i}", - style_url=f"#{basic_info.usage_state.value}_{basic_info.uas_state.value}", + style_url=f"#{basic_info.usage_state.value}_{uas_state}", ) ) return folder diff --git a/monitoring/monitorlib/kml/generation.py b/monitoring/monitorlib/kml/generation.py index fd45630d84..6aa00175b3 100644 --- a/monitoring/monitorlib/kml/generation.py +++ b/monitoring/monitorlib/kml/generation.py @@ -1,4 +1,5 @@ import math +from datetime import datetime import s2sphere from pykml.factory import KML_ElementMaker as kml @@ -48,6 +49,37 @@ def _distance_value_of(distance: Altitude | Radius) -> float: raise NotImplementedError(f"Distance units {distance.units} not yet supported") +def make_basic_placemark( + name: str | None = None, + style_url: str | None = None, + description: str | None = None, + time_start: datetime | None = None, + time_end: datetime | None = None, +): + # Create placemark + args = [] + if name is not None: + args.append(kml.name(name)) + if style_url is not None: + args.append(kml.styleUrl(style_url)) + placemark = kml.Placemark(*args) + if description: + placemark.append(kml.description(description)) + + # Set time range + timespan = None + if time_start: + timespan = kml.TimeSpan(kml.begin(time_start.isoformat())) + if time_end: + if timespan is None: + timespan = kml.TimeSpan() + timespan.append(kml.end(time_end.isoformat())) + if timespan is not None: + placemark.append(timespan) + + return placemark + + def make_placemark_from_volume( v4: Volume4D, name: str | None = None, @@ -74,25 +106,15 @@ def make_placemark_from_volume( raise NotImplementedError("No vertices found") # Create placemark - args = [] - if name is not None: - args.append(kml.name(name)) - if style_url is not None: - args.append(kml.styleUrl(style_url)) - placemark = kml.Placemark(*args) - if description: - placemark.append(kml.description(description)) - - # Set time range - timespan = None - if "time_start" in v4 and v4.time_start: - timespan = kml.TimeSpan(kml.begin(v4.time_start.datetime.isoformat())) - if "time_end" in v4 and v4.time_end: - if timespan is None: - timespan = kml.TimeSpan() - timespan.append(kml.end(v4.time_end.datetime.isoformat())) - if timespan is not None: - placemark.append(timespan) + placemark = make_basic_placemark( + name=name, + style_url=style_url, + description=description, + time_start=v4.time_start.datetime + if "time_start" in v4 and v4.time_start + else None, + time_end=v4.time_end.datetime if "time_end" in v4 and v4.time_end else None, + ) # Create top and bottom of the volume avg = s2sphere.LatLng.from_degrees( @@ -192,6 +214,23 @@ def make_placemark_from_volume( return placemark +def add_point( + placemark, + lat_degrees: float, + lng_degrees: float, +) -> None: + placemark.append(kml.Point(kml.coordinates(f"{lng_degrees},{lat_degrees},0"))) + + +def add_linestring( + placemark, + lng_lat_alt: list[tuple[float, float, float]], +): + # Create the point + coord_string = " ".join(f"{lng},{lat},{alt}" for (lng, lat, alt) in lng_lat_alt) + placemark.append(kml.LineString(kml.tessellate(1), kml.coordinates(coord_string))) + + def query_styles() -> list: """Provides KML styles for query areas.""" return [ diff --git a/monitoring/monitorlib/kml/parsing.py b/monitoring/monitorlib/kml/parsing.py index 84e060779f..64fb678f82 100644 --- a/monitoring/monitorlib/kml/parsing.py +++ b/monitoring/monitorlib/kml/parsing.py @@ -33,12 +33,25 @@ def get_folder_details(folder_elem): polygons = placemark.xpath(".//kml:Polygon", namespaces=KML_NAMESPACE) if placemark_name == "operator_location": - operator_point = folder_elem.xpath( - ".//kml:Placemark/kml:Point/kml:coordinates", namespaces=KML_NAMESPACE - )[0] - if operator_point: - operator_point = str(operator_point).split(",") - operator_location = {"lng": operator_point[0], "lat": operator_point[1]} + point_elem = placemark.xpath(".//kml:Point", namespaces=KML_NAMESPACE) + if point_elem: + operator_point = point_elem[0].xpath( + ".//kml:coordinates", namespaces=KML_NAMESPACE + ) + if operator_point: + operator_point = str(operator_point[0]).split(",") + operator_location = { + "lng": operator_point[0], + "lat": operator_point[1], + } + + # parse the altitude only if it is above MSL + altitude_mode = point_elem[0].xpath( + ".//kml:altitudeMode", namespaces=KML_NAMESPACE + ) + if altitude_mode and altitude_mode[0] == "absolute": + operator_location["alt"] = operator_point[2] + if polygons: if placemark_name.startswith("alt:"): polygon_coords = get_coordinates_from_kml( diff --git a/monitoring/monitorlib/kml/rid.py b/monitoring/monitorlib/kml/rid.py new file mode 100644 index 0000000000..062e46e51b --- /dev/null +++ b/monitoring/monitorlib/kml/rid.py @@ -0,0 +1,149 @@ +from datetime import timedelta + +from pykml.factory import KML_ElementMaker as kml +from uas_standards.astm.f3411.v22a.api import GetFlightsResponse +from uas_standards.interuss.automated_testing.rid.v1.injection import ( + ChangeTestResponse, + CreateTestParameters, +) + +from monitoring.monitorlib.kml.generation import ( + add_linestring, + add_point, + make_basic_placemark, +) + + +def create_test(req: CreateTestParameters, resp: ChangeTestResponse) -> list: + result = [] + for test_flight in resp.injected_flights: + folder = kml.Folder(kml.name(f"Test flight {test_flight.injection_id}")) + requested_flight = None + for flight in req.requested_flights: + if flight.injection_id == test_flight.injection_id: + requested_flight = flight + for i, telemetry in enumerate(test_flight.telemetry): + if ( + "position" in telemetry + and telemetry.position + and "lat" in telemetry.position + and telemetry.position.lat + and "lng" in telemetry.position + and telemetry.position.lng + ): + style_url = "#modifiedtelemetry" + if requested_flight and len(requested_flight.telemetry) > i: + rt = requested_flight.telemetry[i] + if ( + "position" in rt + and rt.position + and "lat" in rt.position + and rt.position.lat == telemetry.position.lat + and "lng" in rt.position + and rt.position.lng == telemetry.position.lng + ): + style_url = "#unmodifiedtelemetry" + if "timestamp" in telemetry and telemetry.timestamp: + time_start = telemetry.timestamp.datetime + if i < len(test_flight.telemetry) - 1: + tt = test_flight.telemetry[i + 1] + if "timestamp" in tt and tt.timestamp: + time_end = tt.timestamp.datetime + else: + time_end = time_start + timedelta(seconds=1) + else: + time_end = time_start + timedelta(seconds=1) + else: + time_start = None + time_end = None + point = make_basic_placemark( + name=f"Telemetry {i}", + style_url=style_url, + time_start=time_start, + time_end=time_end, + ) + add_point(point, telemetry.position.lat, telemetry.position.lng) + folder.append(point) + result.append(folder) + return result + + +def get_flights_v22a(url: str, resp: GetFlightsResponse) -> list: + result = [] + # TODO: render request bounding box from query parameter in url + for flight in resp.flights or [] if "flights" in resp else []: + folder = kml.Folder(kml.name(flight.id)) + if ( + "current_state" in flight + and flight.current_state + and "lat" in flight.current_state.position + and flight.current_state.position.lat + and "lng" in flight.current_state.position + and flight.current_state.position.lng + ): + point = make_basic_placemark( + name=f"{flight.aircraft_type.name} {flight.id}{' SIMULATED' if flight.simulated else ''}", + style_url="#aircraft", + ) + add_point( + point, + flight.current_state.position.lat, + flight.current_state.position.lng, + ) + folder.append(point) + if "recent_positions" in flight and flight.recent_positions: + tail = make_basic_placemark( + name=f"{flight.id} recent positions", style_url="#ridtail" + ) + add_linestring( + tail, + [ + (rp.position.lng, rp.position.lat, 0) + for rp in flight.recent_positions + if "lng" in rp.position + and rp.position.lng + and "lat" in rp.position + and rp.position.lat + ], + ) + folder.append(tail) + + result.append(folder) + return result + + +def rid_styles() -> list: + """Provides KML styles used by RID visualizations above.""" + return [ + kml.Style( + kml.IconStyle( + kml.scale(1.4), + kml.Icon( + kml.href( + "http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png" + ) + ), + ), + id="unmodifiedtelemetry", + ), + kml.Style( + kml.IconStyle( + kml.scale(1.4), + kml.Icon( + kml.href( + "http://maps.google.com/mapfiles/kml/shapes/placemark_square.png" + ) + ), + ), + id="modifiedtelemetry", + ), + kml.Style( + kml.IconStyle( + kml.scale(1.4), + kml.Icon( + kml.href("http://maps.google.com/mapfiles/kml/shapes/airports.png") + ), + ), + id="aircraft", + ), + ] diff --git a/monitoring/monitorlib/locality.py b/monitoring/monitorlib/locality.py index a3b3fafb50..a9ceedb22b 100644 --- a/monitoring/monitorlib/locality.py +++ b/monitoring/monitorlib/locality.py @@ -37,6 +37,11 @@ def highest_priority(self) -> int: """Returns the highest priority level for ASTM F3548-21 defined by the regulator of this locality""" raise NotImplementedError(Locality._NOT_IMPLEMENTED_MSG) + @abstractmethod + def uses_cmsa(self) -> bool: + """Return true if the ecosystem supports CMSA operations""" + raise NotImplementedError(Locality._NOT_IMPLEMENTED_MSG) + def __str__(self): return self.__class__.__name__ @@ -69,6 +74,13 @@ def lowest_bound_priority(self) -> int: def highest_priority(self) -> int: return 100 + def uses_cmsa(self) -> bool: + # Return True for now as Switzerland didn't determined yet if CMSA is + # used. + # If switched to False, ensure tests have a locality with CMSA enabled. + # See https://github.com/interuss/monitoring/pull/1304#pullrequestreview-3594632974 + return True + class UnitedStatesIndustryCollaboration(Locality): @classmethod @@ -86,3 +98,6 @@ def lowest_bound_priority(self) -> int: def highest_priority(self) -> int: return 100 + + def uses_cmsa(self) -> bool: + return False diff --git a/monitoring/monitorlib/multiprocessing.py b/monitoring/monitorlib/multiprocessing.py index d90a39a300..d74efc6413 100644 --- a/monitoring/monitorlib/multiprocessing.py +++ b/monitoring/monitorlib/multiprocessing.py @@ -3,21 +3,92 @@ import multiprocessing.shared_memory from collections.abc import Callable from multiprocessing.synchronize import RLock as RLockT -from typing import Any +from typing import Generic, TypeVar +TValue = TypeVar("TValue") -class SynchronizedValue: + +# Note: attempts to change the below to SynchronizedValue[TValue] causes problems because IntelliJ does not reliably +# understand the newer syntax and therefore fails to provide contextual information for specific TValues. +# See: https://docs.astral.sh/ruff/rules/non-pep695-generic-class/#known-problems +class Transaction(Generic[TValue]): # noqa: UP046 + _lock: RLockT + _get_value: Callable[[], TValue] + _set_value: Callable[[TValue], None] + _value: TValue + """This field is only valid when _locked is True""" + + _locked: bool + """True when _lock is held and _value holds a valid TValue""" + + def __init__( + self, + lock: RLockT, + get_value: Callable[[], TValue], + set_value: Callable[[TValue], None], + ): + self._lock = lock + self._get_value = get_value + self._set_value = set_value + self._locked = False + + def __enter__(self): + if self._locked: + raise RuntimeError( + "SynchronizedValue Transaction started when Transaction was already in progress" + ) + self._lock.__enter__() + self._value = self._get_value() + self._locked = True + return self + + @property + def value(self) -> TValue: + """Value exposed for this transaction. Mutate or set it to make changes during the transaction.""" + if not self._locked: + raise RuntimeError( + "Transaction value accessed when transaction was not active (e.g., outside a `with` block)" + ) + return self._value + + @value.setter + def value(self, new_value: TValue): + if not self._locked: + raise RuntimeError( + "Transaction value set when transaction was not active (e.g., outside a `with` block)" + ) + self._value = new_value + + def abort(self): + """Do not save any changes made to the value during this transaction and release the lock on the synchronized value.""" + self._unlock() + + def _unlock(self, exc_type=None, exc_val=None, exc_tb=None): + self._lock.__exit__(exc_type, exc_val, exc_tb) + self._locked = False + + def __exit__(self, exc_type, exc_val, exc_tb): + if not self._locked: + return + try: + if exc_type is None: + self._set_value(self.value) + finally: + self._unlock() + + +class SynchronizedValue(Generic[TValue]): # noqa: UP046 (same reason as above) """Represents a value synchronized across multiple processes. The shared value can be read with .value or updated in a transaction. A - transaction is created using `with` which returns the current value. That + transaction is created using `transact` in a `with` block. The object is mutated in the transaction, and then committed when the `with` block is exited. Example: db = SynchronizedValue({'foo': 'bar'}) - with db as tx: - assert isinstance(tx, dict) - tx['foo'] = 'baz' + with db.transact() as tx: + assert isinstance(tx.value, dict) + tx.value['foo'] = 'baz' print(json.dumps(db.value)) > {"foo":"baz"} """ @@ -27,16 +98,16 @@ class SynchronizedValue: _lock: RLockT _shared_memory: multiprocessing.shared_memory.SharedMemory - _encoder: Callable[[Any], bytes] - _decoder: Callable[[bytes], Any] - _current_value: Any + _encoder: Callable[[TValue], bytes] + _decoder: Callable[[bytes], TValue] + _transaction: Transaction | None def __init__( self, - initial_value, + initial_value: TValue, capacity_bytes: int = 10000000, - encoder: Callable[[Any], bytes] | None = None, - decoder: Callable[[bytes], Any] | None = None, + encoder: Callable[[TValue], bytes] | None = None, + decoder: Callable[[bytes], TValue] | None = None, ): """Creates a value synchronized across multiple processes. @@ -57,10 +128,14 @@ def __init__( self._decoder = ( decoder if decoder is not None else lambda b: json.loads(b.decode("utf-8")) ) - self._current_value = None + self._transaction = None self._set_value(initial_value) - def _get_value(self): + def _get_value(self) -> TValue: + if self._shared_memory.buf is None: + raise RuntimeError( + "SynchronizedValue attempted to get value when shared memory buffer was None" + ) content_len = int.from_bytes( bytes(self._shared_memory.buf[0 : self.SIZE_BYTES]), "big" ) @@ -73,7 +148,11 @@ def _get_value(self): ) return self._decoder(content) - def _set_value(self, value): + def _set_value(self, value: TValue): + if self._shared_memory.buf is None: + raise RuntimeError( + "SynchronizedValue attempted to set value when shared memory buffer was None" + ) content = self._encoder(value) content_len = len(content) if content_len + self.SIZE_BYTES > self._shared_memory.size: @@ -88,18 +167,9 @@ def _set_value(self, value): ) @property - def value(self): + def value(self) -> TValue: with self._lock: return self._get_value() - def __enter__(self): - self._lock.__enter__() - self._current_value = self._get_value() - return self._current_value - - def __exit__(self, exc_type, exc_val, exc_tb): - try: - if exc_type is None: - self._set_value(self._current_value) - finally: - self._lock.__exit__(exc_type, exc_val, exc_tb) + def transact(self) -> Transaction[TValue]: + return Transaction[TValue](self._lock, self._get_value, self._set_value) diff --git a/monitoring/monitorlib/mutate/rid.py b/monitoring/monitorlib/mutate/rid.py index c6f3d0a459..55f0101647 100644 --- a/monitoring/monitorlib/mutate/rid.py +++ b/monitoring/monitorlib/mutate/rid.py @@ -6,10 +6,8 @@ import uas_standards.astm.f3411.v19.constants as v19_constants import uas_standards.astm.f3411.v22a.api as v22a_api import uas_standards.astm.f3411.v22a.constants as v22a_constants -import yaml -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards import Operation -from yaml.representer import Representer from monitoring.monitorlib import fetch, infrastructure, rid_v1, rid_v2 from monitoring.monitorlib.fetch import QueryType @@ -20,7 +18,7 @@ class ChangedSubscription(RIDQuery): """Version-independent representation of a subscription following a change in the DSS.""" - mutation: str | None = None + mutation: Optional[str] = None @property def _v19_response(self) -> v19_api.PutSubscriptionResponse: @@ -238,8 +236,8 @@ def errors(self) -> list[str]: class SubscriberToNotify(ImplicitDict): """Version-independent representation of a subscriber to notify of a change in the DSS.""" - v19_value: v19_api.SubscriberToNotify | None = None - v22a_value: v22a_api.SubscriberToNotify | None = None + v19_value: Optional[v19_api.SubscriberToNotify] = None + v22a_value: Optional[v22a_api.SubscriberToNotify] = None @property def rid_version(self) -> RIDVersion: @@ -323,7 +321,7 @@ def url(self) -> str: class ChangedISA(RIDQuery): """Version-independent representation of a changed F3411 identification service area.""" - mutation: str | None = None + mutation: Optional[str] = None @property def _v19_response( @@ -728,8 +726,3 @@ def isa_id(self) -> str: f"Cannot retrieve ISA ID using RID version {self.rid_version}" ) return url.split("?")[0].split("/")[-1] - - -yaml.add_representer(ChangedSubscription, Representer.represent_dict) -yaml.add_representer(ChangedISA, Representer.represent_dict) -yaml.add_representer(ISAChange, Representer.represent_dict) diff --git a/monitoring/monitorlib/mutate/scd.py b/monitoring/monitorlib/mutate/scd.py index 2f6a4b9259..3c102b5040 100644 --- a/monitoring/monitorlib/mutate/scd.py +++ b/monitoring/monitorlib/mutate/scd.py @@ -1,8 +1,7 @@ import datetime import s2sphere -import yaml -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.astm.f3548.v21.api import ( OPERATIONS, OperationalIntentReference, @@ -10,7 +9,6 @@ PutSubscriptionParameters, Subscription, ) -from yaml.representer import Representer from monitoring.monitorlib import fetch, infrastructure, scd from monitoring.monitorlib.fetch import QueryType @@ -19,7 +17,7 @@ class MutatedSubscription(fetch.Query): - mutation: str | None = None + mutation: Optional[str] = None @property def success(self) -> bool: @@ -64,9 +62,6 @@ def operational_intent_references(self) -> list[OperationalIntentReference]: return [] -yaml.add_representer(MutatedSubscription, Representer.represent_dict) - - def upsert_subscription( utm_client: infrastructure.UTMClientSession, area: s2sphere.LatLngRect, diff --git a/monitoring/monitorlib/rid.py b/monitoring/monitorlib/rid.py index decb400d29..f78ba29c38 100644 --- a/monitoring/monitorlib/rid.py +++ b/monitoring/monitorlib/rid.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from enum import Enum +from enum import StrEnum import arrow import uas_standards.astm.f3411.v19.api as v19_api @@ -10,7 +10,7 @@ from monitoring.monitorlib import schema_validation -class RIDVersion(str, Enum): +class RIDVersion(StrEnum): f3411_19 = "F3411-19" """ASTM F3411-19 (first version, v1)""" diff --git a/monitoring/monitorlib/rid_automated_testing/injection_api.py b/monitoring/monitorlib/rid_automated_testing/injection_api.py index ef346f2385..9848f319fe 100644 --- a/monitoring/monitorlib/rid_automated_testing/injection_api.py +++ b/monitoring/monitorlib/rid_automated_testing/injection_api.py @@ -1,7 +1,9 @@ import datetime +from collections import defaultdict import arrow import s2sphere +from implicitdict import Optional from uas_standards.interuss.automated_testing.rid.v1 import injection from uas_standards.interuss.automated_testing.rid.v1.injection import ( UASID, @@ -30,7 +32,7 @@ class TestFlight(injection.TestFlight): - raw_telemetry: list[RIDAircraftState] | None + raw_telemetry: Optional[list[RIDAircraftState]] """Copy of original telemetry with potential invalid data""" def __init__(self, *args, **kwargs): @@ -176,9 +178,9 @@ def get_aircraft_type(self, rid_version: RIDVersion) -> UAType: def order_telemetry(self): self.telemetry = sorted( self.telemetry, - key=lambda telemetry: telemetry.timestamp.datetime - if telemetry.timestamp - else 0, + key=lambda telemetry: ( + telemetry.timestamp.datetime if telemetry.timestamp else 0 + ), ) def select_relevant_states( @@ -225,10 +227,9 @@ def get_rect(self) -> s2sphere.LatLngRect | None: ] ) - def get_mean_update_rate_hz(self) -> float | None: - """ - Calculate the mean update rate of the telemetry in Hz - """ + def get_update_rates(self) -> list[int] | None: + """Return the update rate for every second, relative to the start of the flight, with a moving windows of 3 seconds.""" + if not self.telemetry or len(self.telemetry) == 1: return None # TODO check if required or not (may have been called earlier?) @@ -238,7 +239,26 @@ def get_mean_update_rate_hz(self) -> float | None: return start = self.telemetry[0].timestamp.datetime end = self.telemetry[-1].timestamp.datetime - return (len(self.telemetry) - 1) / (end - start).seconds + + buckets = defaultdict(int) + + for frame in self.telemetry: + if frame.timestamp is not None: + bucket = int((frame.timestamp.datetime - start).total_seconds()) + buckets[bucket] += 1 + + rates = [] + + last_bucket = int((end - start).total_seconds()) + bucket = 2 + + while bucket <= last_bucket: + rates.append( + (buckets[bucket] + buckets[bucket - 1] + buckets[bucket - 2]) / 3.0 + ) + bucket += 1 + + return rates class CreateTestParameters(injection.CreateTestParameters): diff --git a/monitoring/monitorlib/schema_validation.py b/monitoring/monitorlib/schema_validation.py index e96a851dd6..e19d72c8fb 100644 --- a/monitoring/monitorlib/schema_validation.py +++ b/monitoring/monitorlib/schema_validation.py @@ -1,6 +1,6 @@ import os.path from dataclasses import dataclass -from enum import Enum +from enum import StrEnum from pathlib import Path import jsonschema.validators @@ -10,7 +10,7 @@ from implicitdict.jsonschema import SchemaVars, make_json_schema -class F3411_19(str, Enum): +class F3411_19(StrEnum): OpenAPIPath = "interfaces/rid/v1/remoteid/augmented.yaml" GetFlightsResponse = "components.schemas.GetFlightsResponse" GetFlightDetailsResponse = "components.schemas.GetFlightDetailsResponse" @@ -28,7 +28,7 @@ class F3411_19(str, Enum): ) -class F3411_22a(str, Enum): +class F3411_22a(StrEnum): OpenAPIPath = "interfaces/rid/v2/remoteid/updated.yaml" GetFlightsResponse = "components.schemas.GetFlightsResponse" GetFlightDetailsResponse = "components.schemas.GetFlightDetailsResponse" @@ -46,7 +46,7 @@ class F3411_22a(str, Enum): ) -class F3548_21(str, Enum): +class F3548_21(StrEnum): OpenAPIPath = "interfaces/astm-utm/Protocol/utm.yaml" ErrorResponse = "components.schemas.ErrorResponse" GetOperationalIntentDetailsResponse = ( diff --git a/monitoring/monitorlib/scripts/in_container/run_unit_tests.sh b/monitoring/monitorlib/scripts/in_container/run_unit_tests.sh new file mode 100755 index 0000000000..b7d378511e --- /dev/null +++ b/monitoring/monitorlib/scripts/in_container/run_unit_tests.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# This script is intended to be called from within a Docker container running +# mock_uss via the interuss/monitoring image. In that context, this script is +# the entrypoint into the test definition validation tool. + +# Ensure uss_qualifier is the working directory +OS=$(uname) +if [[ $OS == "Darwin" ]]; then + # OSX uses BSD readlink + BASEDIR="$(dirname "$0")" +else + BASEDIR=$(readlink -e "$(dirname "$0")") +fi +cd "${BASEDIR}/../.." || exit 1 + +uv run pytest diff --git a/monitoring/monitorlib/scripts/run_unit_tests.sh b/monitoring/monitorlib/scripts/run_unit_tests.sh new file mode 100755 index 0000000000..c0086eda84 --- /dev/null +++ b/monitoring/monitorlib/scripts/run_unit_tests.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -eo pipefail +set -o xtrace + +# Find and change to repo root directory +OS=$(uname) +if [[ "$OS" == "Darwin" ]]; then + # OSX uses BSD readlink + BASEDIR="$(dirname "$0")" +else + BASEDIR=$(readlink -e "$(dirname "$0")") +fi +cd "${BASEDIR}/../../.." || exit 1 + +( +cd monitoring || exit 1 +make image-dev +) + +# shellcheck disable=SC2086 +docker run --name monitorlib_unit_test \ + --rm \ + -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ + -v "$(pwd):/app" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + interuss/monitoring-dev \ + monitorlib/scripts/in_container/run_unit_tests.sh diff --git a/monitoring/monitorlib/subscription_params.py b/monitoring/monitorlib/subscription_params.py index 5be3ffbece..3310a176e1 100644 --- a/monitoring/monitorlib/subscription_params.py +++ b/monitoring/monitorlib/subscription_params.py @@ -3,7 +3,7 @@ import datetime import s2sphere -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.astm.f3548.v21.api import PutSubscriptionParameters from monitoring.monitorlib.geo import LatLngPoint @@ -21,13 +21,13 @@ class SubscriptionParams(ImplicitDict): area_vertices: list[LatLngPoint] """List of vertices of a polygon defining the area of interest""" - min_alt_m: float | None + min_alt_m: Optional[float] """Minimum altitude in meters""" - max_alt_m: float | None + max_alt_m: Optional[float] """Maximum altitude in meters""" - start_time: datetime.datetime | None = None + start_time: Optional[datetime.datetime] = None """Start time of subscription""" end_time: datetime.datetime diff --git a/monitoring/monitorlib/temporal.py b/monitoring/monitorlib/temporal.py index db87e5a581..d9d71293be 100644 --- a/monitoring/monitorlib/temporal.py +++ b/monitoring/monitorlib/temporal.py @@ -1,10 +1,15 @@ from __future__ import annotations from datetime import datetime, timedelta -from enum import Enum +from enum import StrEnum import arrow -from implicitdict import ImplicitDict, StringBasedDateTime, StringBasedTimeDelta +from implicitdict import ( + ImplicitDict, + Optional, + StringBasedDateTime, + StringBasedTimeDelta, +) from pvlib.solarposition import get_solarposition from uas_standards.astm.f3548.v21 import api as f3548v21 @@ -30,7 +35,7 @@ class NextSunPosition(ImplicitDict): """Elevation of the center of the sun above horizontal, in degrees.""" -class DayOfTheWeek(str, Enum): +class DayOfTheWeek(StrEnum): Mo = "Mo" """Monday""" Tu = "Tu" @@ -58,63 +63,121 @@ class NextDay(ImplicitDict): * "-08:00" (ISO time zone) * "US/Pacific" (IANA time zone)""" - days_of_the_week: list[DayOfTheWeek] | None = None + days_of_the_week: Optional[list[DayOfTheWeek]] = None """Acceptable days of the week. Omit to indicate that any day of the week is acceptable.""" -class TimeDuringTest(str, Enum): - StartOfTestRun = "StartOfTestRun" +class TimeDuringTest(str): + """A particular time during the test, as identified/named by this value. + + Names listed below are provided by the framework when appropriate: + * StartOfTestRun: The time at which the test run started. + * StartOfScenario: The time at which the current scenario started. + * TimeOfEvaluation: The time at which a TestTime was resolved to an absolute time; generally close to 'now'. + """ + + StartOfTestRun: TimeDuringTest """The time at which the test run started.""" - StartOfScenario = "StartOfScenario" + StartOfScenario: TimeDuringTest """The time at which the current scenario started.""" - TimeOfEvaluation = "TimeOfEvaluation" + TimeOfEvaluation: TimeDuringTest """The time at which a TestTime was resolved to an absolute time; generally close to 'now'.""" + ProvidedByFramework: list[TimeDuringTest] + """The TimeDuringTests provided by the framework when appropriate""" + + +TimeDuringTest.StartOfTestRun = TimeDuringTest("StartOfTestRun") +TimeDuringTest.StartOfScenario = TimeDuringTest("StartOfScenario") +TimeDuringTest.TimeOfEvaluation = TimeDuringTest("TimeOfEvaluation") +TimeDuringTest.ProvidedByFramework = [ + TimeDuringTest.StartOfTestRun, + TimeDuringTest.StartOfScenario, + TimeDuringTest.TimeOfEvaluation, +] + + +class Time(StringBasedDateTime): + def offset(self, dt: timedelta) -> Time: + return Time(self.datetime + dt) + + def to_f3548v21(self) -> f3548v21.Time: + return f3548v21.Time(value=self) + + +class TestTimeContext(dict[TimeDuringTest, Time]): + """Context in which TestTimes are evaluated. + + Stores definitions for TimeDuringTests.""" + + def evaluate_now(self) -> TestTimeContext: + """Set TimeOfEvaluation in this context to the current time. + + This should be performed once before resolving a TestTime.""" + self[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) + return self + + @staticmethod + def all_times_are(t: Time) -> TestTimeContext: + """Returns a TestTimeContext where all framework-provided times are the provided time. + + For the purpose of testing/validation.""" + return TestTimeContext( + { + TimeDuringTest.StartOfTestRun: t, + TimeDuringTest.StartOfScenario: t, + TimeDuringTest.TimeOfEvaluation: t, + } + ) + class TestTime(ImplicitDict): """Exactly one of the time option fields of this object must be specified.""" - absolute_time: StringBasedDateTime | None = None + absolute_time: Optional[StringBasedDateTime] = None """Time option field to use a precise timestamp which does not change with test conditions. The value of absolute_time is limited given that the specific time a test will be started is unknown, and the jurisdictions usually impose a limit on how far in the future an operation can be planned. """ - time_during_test: TimeDuringTest | None = None + time_during_test: Optional[TimeDuringTest] = None """Time option field to, if specified, use a timestamp relating to the current test run.""" - next_day: NextDay | None = None + name: Optional[TimeDuringTest] + """If specified, update the TestTimeContext with the time computed for this TestTime as this name, which may then later be referenced by a different TestTime via time_during_test.""" + + next_day: Optional[NextDay] = None """Time option field to use a timestamp equal to midnight beginning the next occurrence of any matching day following the specified reference timestamp.""" - next_sun_position: NextSunPosition | None = None + next_sun_position: Optional[NextSunPosition] = None """Time option field to use a timestamp equal to the next time after the specified reference timestamp at which the sun will be at the specified angle above the horizon.""" - offset_from: OffsetTime | None = None + offset_from: Optional[OffsetTime] = None """Time option field to use a timestamp that is offset by the specified amount from the specified time.""" - use_timezone: str | None = None + use_timezone: Optional[str] = None """If specified, report the timestamp in the specified time zone. Examples: * "local" (local time of machine running this code) * "Z" (Zulu time) * "-08:00" (ISO time zone) * "US/Pacific" (IANA time zone)""" - def resolve(self, times: dict[TimeDuringTest, Time]) -> Time: + def resolve(self, context: TestTimeContext) -> Time: """Resolve TestTime into specific Time.""" result = None if self.absolute_time is not None: result = self.absolute_time.datetime elif self.time_during_test is not None: - if self.time_during_test not in times: + if self.time_during_test not in context: raise ValueError( - f"Specified {self.time_during_test} time during test was not provided when resolving TestTime" + f"Specified '{self.time_during_test}' time during test was not provided when resolving TestTime" ) - result = times[self.time_during_test].datetime + result = context[self.time_during_test].datetime elif self.next_day is not None: t0 = ( - arrow.get(self.next_day.starting_from.resolve(times).datetime) + arrow.get(self.next_day.starting_from.resolve(context).datetime) .to(self.next_day.time_zone) .datetime ) @@ -130,11 +193,11 @@ def resolve(self, times: dict[TimeDuringTest, Time]) -> Time: result = t elif self.offset_from is not None: result = ( - self.offset_from.starting_from.resolve(times).datetime + self.offset_from.starting_from.resolve(context).datetime + self.offset_from.offset.timedelta ) elif self.next_sun_position is not None: - t0 = self.next_sun_position.starting_from.resolve(times).datetime + t0 = self.next_sun_position.starting_from.resolve(context).datetime dt = timedelta(minutes=5) lat = self.next_sun_position.observed_from.lat @@ -186,7 +249,12 @@ def resolve(self, times: dict[TimeDuringTest, Time]) -> Time: if self.use_timezone: result = arrow.get(result).to(self.use_timezone).datetime - return Time(result) + t_result = Time(result) + + if "name" in self and self.name: + context[self.name] = t_result + + return t_result _weekdays = [ @@ -212,11 +280,3 @@ def _sun_elevation(t: datetime, lat_deg: float, lng_deg: float) -> float: Returns: Degrees above the horizon of the center of the sun. """ return get_solarposition(t, lat_deg, lng_deg).elevation.values[0] # pyright:ignore[reportAttributeAccessIssue] - - -class Time(StringBasedDateTime): - def offset(self, dt: timedelta) -> Time: - return Time(self.datetime + dt) - - def to_f3548v21(self) -> f3548v21.Time: - return f3548v21.Time(value=self) diff --git a/monitoring/monitorlib/transformations.py b/monitoring/monitorlib/transformations.py index 0867d87ede..7d4e67fde4 100644 --- a/monitoring/monitorlib/transformations.py +++ b/monitoring/monitorlib/transformations.py @@ -1,22 +1,22 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional class RelativeTranslation(ImplicitDict): """Offset a geo feature by a particular amount.""" - meters_east: float | None + meters_east: Optional[float] """Number of meters east to translate.""" - meters_north: float | None + meters_north: Optional[float] """Number of meters north to translate.""" - meters_up: float | None + meters_up: Optional[float] """Number of meters upward to translate.""" - degrees_east: float | None + degrees_east: Optional[float] """Number of degrees of longitude east to translate.""" - degrees_north: float | None + degrees_north: Optional[float] """Number of degrees of latitude north to translate.""" @@ -33,6 +33,6 @@ class AbsoluteTranslation(ImplicitDict): class Transformation(ImplicitDict): """A transformation to apply to a geotemporal feature. Exactly one field must be specified.""" - relative_translation: RelativeTranslation | None + relative_translation: Optional[RelativeTranslation] - absolute_translation: AbsoluteTranslation | None + absolute_translation: Optional[AbsoluteTranslation] diff --git a/monitoring/monitorlib/versioning.py b/monitoring/monitorlib/versioning.py index 5499c0db99..e15c7ebc82 100644 --- a/monitoring/monitorlib/versioning.py +++ b/monitoring/monitorlib/versioning.py @@ -58,13 +58,8 @@ def _retrieve_commit_hash() -> str: return env_hash # We must be running outside a monitoring-image container; use git to determine the commit hash. - process = subprocess.Popen( - ["git", "rev-parse", "HEAD"], - stdout=subprocess.PIPE, - universal_newlines=True, - ) - commit, _ = process.communicate() - if process.returncode != 0: + commit = _get_process_result(["git", "rev-parse", "HEAD"]) + if commit is None: return "unknown" commit = commit.strip() if "not a git repository" in commit: @@ -95,14 +90,27 @@ def _retrieve_code_version() -> str: if len(commit) > 7: commit = commit[0:7] - process = subprocess.Popen( - ["git", "status", "-s"], stdout=subprocess.PIPE, universal_newlines=True - ) - status, _ = process.communicate() - if process.returncode != 0: + status = _get_process_result(["git", "status", "-s"]) + if status is None: # git status returned an error so we don't know the working status of the repo return commit + "-unknown" - elif status: + if status: # git status indicated differences from the latest commit, so the working copy is dirty return commit + "-dirty" + return commit + + +def _get_process_result(cmd: list[str]) -> str | None: + try: + with subprocess.Popen( + cmd, stdout=subprocess.PIPE, universal_newlines=True + ) as process: + result, _ = process.communicate() + if process.returncode != 0: + return None + return result + except FileNotFoundError: + return None + except subprocess.SubprocessError: + return None diff --git a/monitoring/prober/conftest.py b/monitoring/prober/conftest.py index 8b4b52c23c..7c35f96213 100644 --- a/monitoring/prober/conftest.py +++ b/monitoring/prober/conftest.py @@ -6,7 +6,11 @@ import uas_standards.astm.f3548.v21.constants as v21_constants from monitoring.monitorlib import auth, scd -from monitoring.monitorlib.infrastructure import AsyncUTMTestSession, UTMClientSession +from monitoring.monitorlib.infrastructure import ( + AsyncUTMTestSession, + UTMClientSession, + utm_client_session_factory, +) from monitoring.prober.infrastructure import ( IDFactory, ResourceType, @@ -120,7 +124,9 @@ def make_session( pytest.skip(f"{auth_option} option not set") auth_adapter = auth.make_auth_adapter(auth_spec) - s = UTMClientSession(dss_endpoint + endpoint_suffix, auth_adapter) + s = utm_client_session_factory.get_session( + dss_endpoint + endpoint_suffix, auth_adapter + ) return s diff --git a/monitoring/prober/run_locally.sh b/monitoring/prober/run_locally.sh index f756e8e1d9..bc47d88613 100755 --- a/monitoring/prober/run_locally.sh +++ b/monitoring/prober/run_locally.sh @@ -18,8 +18,8 @@ cd monitoring || exit 1 make image ) -CORE_SERVICE_CONTAINER="local_infra-dss-1" -OAUTH_CONTAINER="local_infra-oauth-1" +CORE_SERVICE_CONTAINER="local_infra_1-1-dss-1" +OAUTH_CONTAINER="local_infra_1-1-oauth-1" declare -a localhost_containers=("$CORE_SERVICE_CONTAINER" "$OAUTH_CONTAINER") for container_name in "${localhost_containers[@]}"; do @@ -44,7 +44,7 @@ if ! docker run \ "${1:-.}" \ -rsx \ --junitxml="/app/$OUTPUT_DIR/e2e_test_result" \ - --dss-endpoint http://dss.uss1.localutm \ + --dss-endpoint http://dss1.uss1.localutm \ --rid-auth "DummyOAuth(http://oauth.authority.localutm:8085/token,sub=fake_uss)" \ --rid-v2-auth "DummyOAuth(http://oauth.authority.localutm:8085/token,sub=fake_uss)" \ --scd-auth1 "DummyOAuth(http://oauth.authority.localutm:8085/token,sub=fake_uss)" \ diff --git a/monitoring/prober/scd/test_uss_availability.py b/monitoring/prober/scd/test_uss_availability.py index 53adc8aa25..ef3a69214e 100644 --- a/monitoring/prober/scd/test_uss_availability.py +++ b/monitoring/prober/scd/test_uss_availability.py @@ -3,35 +3,68 @@ from monitoring.prober.infrastructure import depends_on +def _get_uss_availability(uss_id, scd_session2): + resp = scd_session2.get(f"/uss_availability/{uss_id}", scope=SCOPE_AA) + assert resp.status_code == 200, resp.content + data = resp.json() + return data["status"]["uss"], data["status"]["availability"], data["version"] + + @default_scope(SCOPE_AA) def test_set_uss_availability(ids, scd_session2): - resp = scd_session2.put( - "/uss_availability/uss1", scope=SCOPE_AA, json={"availability": "normal"} - ) + # There is no interface to clear a uss availability status. + # Therefore we always need to check if a status already exists. + _, _, version = _get_uss_availability("uss1", scd_session2) + + payload = {"availability": "normal"} + if version: + payload["old_version"] = version + + resp = scd_session2.put("/uss_availability/uss1", json=payload) + assert resp.status_code == 200, resp.content data = resp.json() assert data["status"]["uss"] == "uss1" assert data["status"]["availability"] == "Normal" assert data["version"] - resp = scd_session2.put( - "/uss_availability/uss1", scope=SCOPE_AA, json={"availability": "pUrPlE"} - ) - assert resp.status_code == 400, resp.content + uss, availability, version = _get_uss_availability("uss1", scd_session2) + assert uss == "uss1" + assert availability == "Normal" + assert version == data["version"] @default_scope(SCOPE_AA) @depends_on(test_set_uss_availability) -def test_get_uss_availability(ids, scd_session2): - resp = scd_session2.get("/uss_availability/unknown_uss2", scope=SCOPE_AA) - assert resp.status_code == 200, resp.content - data = resp.json() - assert data["status"]["availability"] == "Unknown" - assert data["version"] == "" +def test_mutate_uss_availability(ids, scd_session2): + _, _, version = _get_uss_availability("uss1", scd_session2) + + resp = scd_session2.put( + "/uss_availability/uss1", + json={"availability": "down", "old_version": version}, + ) - resp = scd_session2.get("/uss_availability/uss1", scope=SCOPE_AA) assert resp.status_code == 200, resp.content data = resp.json() assert data["status"]["uss"] == "uss1" - assert data["status"]["availability"] == "Normal" + assert data["status"]["availability"] == "Down" assert data["version"] + + uss, availability, version = _get_uss_availability("uss1", scd_session2) + assert uss == "uss1" + assert availability == "Down" + assert version == data["version"] + + +@default_scope(SCOPE_AA) +def test_set_invalid_uss_availability(ids, scd_session2): + resp = scd_session2.put("/uss_availability/uss1", json={"availability": "pUrPlE"}) + assert resp.status_code == 400, resp.content + + +@default_scope(SCOPE_AA) +def test_get_unknown_uss_availability(ids, scd_session2): + uss, availability, version = _get_uss_availability("unknown_uss2", scd_session2) + assert uss == "unknown_uss2" + assert availability == "Unknown" + assert version == "" diff --git a/monitoring/uss_qualifier/action_generators/documentation/definitions.py b/monitoring/uss_qualifier/action_generators/documentation/definitions.py index 76cb03d772..d170d6a867 100644 --- a/monitoring/uss_qualifier/action_generators/documentation/definitions.py +++ b/monitoring/uss_qualifier/action_generators/documentation/definitions.py @@ -1,9 +1,8 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier.action_generators.definitions import GeneratorTypeName from monitoring.uss_qualifier.scenarios.definitions import TestScenarioTypeName from monitoring.uss_qualifier.suites.definitions import ( - ActionType, TestSuiteDefinition, TestSuiteTypeName, ) @@ -15,10 +14,10 @@ class PotentialTestScenarioAction(ImplicitDict): class PotentialTestSuiteAction(ImplicitDict): - suite_type: TestSuiteTypeName | None + suite_type: Optional[TestSuiteTypeName] """Type/location of test suite. Usually expressed as the file name of the suite definition (without extension) qualified relative to the `uss_qualifier` folder""" - suite_definition: TestSuiteDefinition | None + suite_definition: Optional[TestSuiteDefinition] """Definition of test suite internal to the configuration -- specified instead of `suite_type`.""" @@ -31,12 +30,6 @@ class PotentialActionGeneratorAction(ImplicitDict): class PotentialGeneratedAction(ImplicitDict): - test_scenario: PotentialTestScenarioAction | None - test_suite: PotentialTestSuiteAction | None - action_generator: PotentialActionGeneratorAction | None - - def get_action_type(self) -> ActionType: - matches = [v for v in ActionType if v in self and self[v]] - if len(matches) != 1: - raise ActionType.build_invalid_action_declaration() - return ActionType(matches[0]) + test_scenario: Optional[PotentialTestScenarioAction] + test_suite: Optional[PotentialTestSuiteAction] + action_generator: Optional[PotentialActionGeneratorAction] diff --git a/monitoring/uss_qualifier/action_generators/documentation/documentation.py b/monitoring/uss_qualifier/action_generators/documentation/documentation.py index e168427407..2af7a6f09b 100644 --- a/monitoring/uss_qualifier/action_generators/documentation/documentation.py +++ b/monitoring/uss_qualifier/action_generators/documentation/documentation.py @@ -14,7 +14,6 @@ PotentialTestSuiteAction, ) from monitoring.uss_qualifier.suites.definitions import ( - ActionType, TestSuiteActionDeclaration, ) @@ -36,8 +35,7 @@ def list_potential_actions_for_action_generator_definition( def list_potential_actions_for_action_declaration( declaration: TestSuiteActionDeclaration, ) -> list[PotentialGeneratedAction]: - action_type = declaration.get_action_type() - if action_type == ActionType.TestScenario: + if "test_scenario" in declaration and declaration.test_scenario: return [ PotentialGeneratedAction( test_scenario=PotentialTestScenarioAction( @@ -45,7 +43,7 @@ def list_potential_actions_for_action_declaration( ) ) ] - elif action_type == ActionType.TestSuite: + elif "test_suite" in declaration and declaration.test_suite: if "suite_type" in declaration.test_suite and declaration.test_suite.suite_type: return [ PotentialGeneratedAction( @@ -65,7 +63,7 @@ def list_potential_actions_for_action_declaration( ) ) ] - elif action_type == ActionType.ActionGenerator: + elif "action_generator" in declaration and declaration.action_generator: return [ PotentialGeneratedAction( action_generator=PotentialActionGeneratorAction( @@ -74,4 +72,4 @@ def list_potential_actions_for_action_declaration( ) ] else: - raise NotImplementedError(f"Action type {action_type} is not supported") + raise declaration.invalid_type_error diff --git a/monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py b/monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py index 2ec548db50..cf171c8872 100644 --- a/monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py +++ b/monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py @@ -1,6 +1,6 @@ from collections.abc import Iterator -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.inspection import fullname from monitoring.uss_qualifier.action_generators.documentation.definitions import ( @@ -30,7 +30,7 @@ class FlightPlannerCombinationsSpecification(ImplicitDict): flight_planners_source: ResourceID """ID of the resource providing all available flight planners""" - combination_selector_source: ResourceID | None = None + combination_selector_source: Optional[ResourceID] = None """If specified and contained in the provided resources, the resource containing a FlightPlannerCombinationSelectorResource to select only a subset of combinations""" roles: list[ResourceID] diff --git a/monitoring/uss_qualifier/common_data_definitions.py b/monitoring/uss_qualifier/common_data_definitions.py index d8f80cd252..aecf112e28 100644 --- a/monitoring/uss_qualifier/common_data_definitions.py +++ b/monitoring/uss_qualifier/common_data_definitions.py @@ -1,9 +1,9 @@ from __future__ import annotations -from enum import Enum +from enum import StrEnum -class Severity(str, Enum): +class Severity(StrEnum): Critical = "Critical" """The system under test has a critical problem that justifies the discontinuation of testing. @@ -30,10 +30,9 @@ class Severity(str, Enum): """ Low = "Low" - """The system meets requirements but could be improved. + """The system does not fail to meet a requirement, but could be improved. - Further test steps can be executed without impact. A test run with only - Low-Severity issues will be considered successful. + Further test steps can be executed without impact. """ def __eq__(self, other): diff --git a/monitoring/uss_qualifier/configurations/config_design.md b/monitoring/uss_qualifier/configurations/config_design.md new file mode 100644 index 0000000000..426f5516f5 --- /dev/null +++ b/monitoring/uss_qualifier/configurations/config_design.md @@ -0,0 +1,185 @@ +# Configuration design guidelines + +## Purpose + +This document suggests approaches a test designer may adopt when designing a uss_qualifier automated [test configuration](./README.md) to accomplish various maintainability and scalability goals as described below. + +## Strategies + +### Define all test configurations using Jsonnet + +While test configurations may be defined in any combination of any supported dictionary-like formats such as JSON, YAML, and Jsonnet, [Jsonnet](https://jsonnet.org/) provides a wide array of templating and programmatic tools upon which many other strategies described on this page depend. By Jsonnet convention, each .jsonnet file is intended to map to one concrete test configuration. Any number of .libsonnet content fragments can be used by that .jsonnet file (and other .libsonnet files) to construct the concrete test configuration. + +Due to its power and flexibility, it can sometimes be difficult to determine the actual test configuration being produced by a Jsonnet configuration. To address this, each .jsonnet file can be "rendered" into a single flat YAML file which is also version-controlled along with the .jsonnnet and .libsonnet sources. Changes in the single flat YAML file allow the test designer and any reviewers to more easily understand the ultimate impact of changes to the programmatic Jsonnet configuration while the original Jsonnet retains the full power and flexibility of the strategies described below. Flat rendering can be accomplished by invoking uss_qualifier with the `--config-output` flag, and potentially the `--exit-before-execution` flag. [An example Makefile](./dev/utm_implementation_us/environments/Makefile) demonstrates a tool that automatically renders all top-level test configurations (*.jsonnet) in all environments (organized into folder per environment) to flat YAML files. + +### Define test baseline and test environment separately + +Any time multiple concrete test configurations may/will reuse the same configuration element, that element should be defined in isolation (in a .libsonnet file) and used in each .jsonnet test configuration by `import`ing the shared element from its .libsonnet. The most common configuration element likely to be reused between different test configurations is the definition of the test baseline. The reuse approach suggested here when designing a test baseline is to fully define the entire [USSQualifierConfiguration](../../../schemas/monitoring/uss_qualifier/configurations/configuration/USSQualifierConfiguration.json) in a .libsonnet file named according to the identity of the test baseline (e.g., `baseline_foo.libsonnet` for the "foo" test baseline), but do so using a parameterized function that accepts a definition of the test environment as its parameter. See [baseline_a.libsonnet](./dev/utm_implementation_us/definitions/baseline_a.libsonnet) for an example. + +The structure of the object defining the test environment passed as a parameter to the Jsonnet function (defined in the baseline .libsonnet) is flexible and can be defined in whichever way the test designer finds most clear. However, any test baseline .libsonnet file a test designer produces should be accompanied by a corresponding .libsonnet file capable of generating a concrete instance of this environment definition object. The recommended convention is to name this .libsonnet file the same as the baseline, but with an `env_template` prefix replacing the `baseline` prefix -- so, for instance, `baseline_foo.libsonnet` would correspond to `env_template_foo.libsonnet`. To allow for many of the strategies below, the approach suggested here for describing this environment definition object is to define a function at the top level of the environment template .libsonnet accepting any parameters needed to customize the test environment definition differently between different environments (though there may be no such parameters initially). See [env_template_a.libsonnet](./dev/utm_implementation_us/definitions/env_template_a.libsonnet) for an example. + +### Define all participant-specific information in one .libsonnet per participant + +To potentially ease (self-)service of participant environmental information adjustments, the test configuration structuring recommendation here is to avoid specifying any environmental information specific to a particular participant or environment directly in the environment template. Instead, accept information about participants and/or the specific environment as parameters to the top-level environment template function (for instance, the `active_participants` parameter in [env_template_a.libsonnet](./dev/utm_implementation_us/definitions/env_template_a.libsonnet)). This allows the environment template to be reused to generate test configurations for many different combinations of test participants and environments. + +Information about a specific participant should be defined solely in a single .libsonnet file named for that participant (see, e.g., [uss1.libsonnet](./dev/utm_implementation_us/participants/uss1.libsonnet) and [uss2.libsonnet](./dev/utm_implementation_us/participants/uss2.libsonnet)), containing a single participant definition object. Environment-independent information about the participant (such as participant ID) can be specified as top-level fields in the participant definition object (as in [utm_implementation_us participants](./dev/utm_implementation_us/participants)), or grouped into a single top-level key like `env_independent`. Each test environment in which the participant has systems deployed should have a top-level key. For instance, [uss1](./dev/utm_implementation_us/participants/uss1.libsonnet) has systems deployed in a local environment with the identifier `local_env` (this identifier is chosen by the test designer and must be shared across all participants), therefore its participant definition object in uss1.libsonnet contains a `local_env` top-level key. + +The structure of each value corresponding to an environment key in the participant definition object can be defined in whichever way the test designer finds most clear/convenient/appropriate. However, this structure must capture all information about a participant's systems and test involvement in the corresponding environment. This includes, for instance, information about all test-relevant systems a participant may have deployed in the environment (URLs, etc). + +For maximum flexibility, the environment templates of all test baselines should accept the same single format of participant information even if not all of that information is used. For instance, if one test baseline verifies ASTM F3411 NetRID functionality and another test baseline verifies ASTM F3548 strategic coordination, both baselines should nonetheless accept the same participant definitions. This means that the same key name in a participant environment block may not be used to refer to different information in different test configurations. For example, if both baselines needed participants to provide an "auth verifier", but an F3411 auth verifier was an entirely different thing from an F3548 auth verifier, it would be important to name the keys someting like `auth_verifier_f3411` and `auth_verifier_f3548` rather than attempting to name them both `auth_verifier`. This practice applies across time as well -- if a newer test baseline requires a new resource from participants, participants should be able to add that new resource to their environment blocks without breaking their participation in tests using the old test baseline. If a newer test baseline uses a uss_qualifier version that changes the format of a resource provided by participants and the test designer needs to maintain test configurations using the old and new test baselines simultaneously, a new key should be introduced to accept the new-format resource specification while retaining the old key using the old-format resource specification. + +### Select participants for a given test configuration via computed test membership + +Rather than select participants to be involved in a particular test configuration by listing them manually in that test configuration, programmatically extract a set of participants according to whether each of the participants has opted into the test configuration (via specification in that participant's .libsonnet from the previous section). In each environment block, have participants specify a key such as `participating_in_tests` containing a list of test names the participant wishes to participate in. For example, in the utm_implementation_us CI configuration, uss1 has [indicated](./dev/utm_implementation_us/participants/uss1.libsonnet) they wish to participate in `test_1` and `test_2` in the `local_env` environment, and uss2 has [indicated](./dev/utm_implementation_us/participants/uss2.libsonnet) they wish to participate in `test_1` in the `local_env` environment. Then, define a [test_membership.libsonnet](./dev/utm_implementation_us/participants/test_membership.libsonnet) helper to produce a list of participants for a specified test name in a specified environment (`active_participants` in the CI example linked above), and/or any other list of participants needed for tests. In the CI configuration, for example, a list of all participants participating in any test in a given environment (`participants_in_env_to_clear`) is also determined. + +These lists of participants can be used as parameters to the environment template paired with the baseline used by a given test configuration, as can be seen in the [utm_implementation_us `test_1` CI test configuration top-level definition](./dev/utm_implementation_us/environments/local/test_1.jsonnet). + +## Visualization + +The graph below attempts to illustrate an example of configuration information flow using all the strategies above. + +Three participants (USS1, USS2, and USS3) define their deployments and test participation per environment for one or two different environments. Each test configuration (e.g., "Newcomers") first collects the set of participants (dashed Participants intermediate artifact) by using the test_membership tool which processes all participants' definitions, and specifies the environment name and test name to obtain the appropriate set of participants. The environment template corresponding to the test baseline used by the test configuration accepts the appropriate set of participants, the name of the environment, and whatever other information may be specific to the test configuration, and constructs the concrete environment object (dashed Environment intermediate artifact). This concrete environment object is provided to the test baseline to produce the full test configuration ready to use in uss_qualifier. + +```mermaid +flowchart LR + BaselineA[\"Baseline A"/] + BaselineB[\"Baseline B"/] + EnvTemplateA[\"Env template A"/] + EnvTemplateB[\"Env template B"/] + + subgraph QualEnv[Qualification environment] + subgraph NewcomersConfig[Newcomers test configuration] + NewcomersEnvCode["Env code `qual`"] + NewcomersTestName["Test name `newcomers`"] + + NewcomersParticipants["Participants"]:::IntermediateArtifact + NewcomersEnv["Environment"]:::IntermediateArtifact + + NewcomersFullConfig{{"Full test configuration"}}:::RenderedArtifact + end + subgraph ProdReleaseConfig[Production release test configuration] + ProdReleaseEnvCode["Env code `qual`"] + ProdReleaseTestName["Test name `prodrelease`"] + + ProdReleaseParticipants["Participants"]:::IntermediateArtifact + ProdReleaseEnv["Environment"]:::IntermediateArtifact + + ProdReleaseFullConfig{{"Full test configuration"}}:::RenderedArtifact + end + subgraph ExploratoryConfig[Exploratory test configuration] + ExploratoryEnvCode["Env code `qual`"] + ExploratoryTestName["Test name `exploratory`"] + + ExploratoryParticipants["Participants"]:::IntermediateArtifact + ExploratoryEnv["Environment"]:::IntermediateArtifact + + ExploratoryFullConfig{{"Full test configuration"}}:::RenderedArtifact + end + end + + subgraph PrequalEnv[Pre-qualification environment] + subgraph EarlyGateConfig[Early-gate test configuration] + EarlyGateEnvCode["Env code `prequal`"] + EarlyGateTestName["Test name `earlygate`"] + + EarlyGateParticipants["Participants"]:::IntermediateArtifact + EarlyGateEnv["Environment"]:::IntermediateArtifact + + EarlyGateFullConfig{{"Full test configuration"}}:::RenderedArtifact + end + end + + subgraph ParticipantDefs[Participant definitions] + subgraph USS1 + subgraph USS1qual[qual] + USS1qualSys1["System 1 qual URL"] + USS1qualSys2["System 2 qual URL"] + USS1qualTests["Participating in newcomers,prodrelease,exploratory"] + end + subgraph USS1prequal[prequal] + USS1prequalSys1["System 1 pre-qual URL"] + USS1prequalSys2["System 2 pre-qual URL"] + USS1prequalTests["Participating in earlygate"] + end + end + subgraph USS2 + subgraph USS2qual[qual] + USS2qualSys1["System 1 qual URL"] + USS2qualTests["Participating in newcomers,prodrelease,exploratory"] + end + subgraph USS2prequal[prequal] + USS2prequalSys1["System 1 pre-qual URL"] + USS2prequalTests["Participating in earlygate"] + end + end + subgraph USS3 + subgraph USS3qual[qual] + USS3qualSys1["System 1 qual URL"] + USS3qualSys2["System 2 qual URL"] + USS3qualTests["Participating in newcomers"] + end + end + TestMembership[\Test membership/] + end + + NewcomersEnvCode --> NewcomersParticipants + NewcomersTestName --> NewcomersParticipants + ParticipantDefs --> NewcomersParticipants + NewcomersEnvCode --> NewcomersEnv + NewcomersParticipants --> NewcomersEnv + EnvTemplateA --> NewcomersEnv + NewcomersEnv --> NewcomersFullConfig + BaselineA --> NewcomersFullConfig + + linkStyle 0,1,2,3,4,5,6,7 stroke:red + + ProdReleaseEnvCode --> ProdReleaseParticipants + ProdReleaseTestName --> ProdReleaseParticipants + ParticipantDefs --> ProdReleaseParticipants + ProdReleaseEnvCode --> ProdReleaseEnv + ProdReleaseParticipants --> ProdReleaseEnv + EnvTemplateA --> ProdReleaseEnv + ProdReleaseEnv --> ProdReleaseFullConfig + BaselineA --> ProdReleaseFullConfig + + linkStyle 8,9,10,11,12,13,14,15 stroke:blue + + ExploratoryEnvCode --> ExploratoryParticipants + ExploratoryTestName --> ExploratoryParticipants + ParticipantDefs --> ExploratoryParticipants + ExploratoryEnvCode --> ExploratoryEnv + ExploratoryParticipants --> ExploratoryEnv + EnvTemplateB --> ExploratoryEnv + ExploratoryEnv --> ExploratoryFullConfig + BaselineB --> ExploratoryFullConfig + + linkStyle 16,17,18,19,20,21,22,23 stroke:green + + EarlyGateEnvCode --> EarlyGateParticipants + EarlyGateTestName --> EarlyGateParticipants + ParticipantDefs --> EarlyGateParticipants + EarlyGateEnvCode --> EarlyGateEnv + EarlyGateParticipants --> EarlyGateEnv + EnvTemplateA --> EarlyGateEnv + EarlyGateEnv --> EarlyGateFullConfig + BaselineA --> EarlyGateFullConfig + + linkStyle 24,25,26,27,28,29,30,31 stroke:orange + + EnvTemplateA --- BaselineA + EnvTemplateB --- BaselineB + + USS2 ~~~ EnvTemplateA + + +classDef IntermediateArtifact stroke-dasharray: 5 5 +classDef Qual fill:#ADD8E6; +class QualEnv,USS1qual,USS2qual,USS3qual Qual; +classDef Prequal fill:#D8ADE6; +class PrequalEnv,USS1prequal,USS2prequal Prequal; +classDef TestConfig fill:#ADFFD8; +class NewcomersConfig,ProdReleaseConfig,ExploratoryConfig,EarlyGateConfig TestConfig; +classDef USS fill:#BBBBBB; +class USS1,USS2,USS3 USS; +classDef RenderedArtifact stroke-width: 5 +``` diff --git a/monitoring/uss_qualifier/configurations/configuration.py b/monitoring/uss_qualifier/configurations/configuration.py index 3c88d6a37c..5b80109c65 100644 --- a/monitoring/uss_qualifier/configurations/configuration.py +++ b/monitoring/uss_qualifier/configurations/configuration.py @@ -1,6 +1,8 @@ from __future__ import annotations -from implicitdict import ImplicitDict +from collections.abc import Iterable + +from implicitdict import ImplicitDict, Optional, StringBasedTimeDelta from monitoring.monitorlib.dicts import JSONAddress from monitoring.uss_qualifier.action_generators.definitions import GeneratorTypeName @@ -20,13 +22,13 @@ class InstanceIndexRange(ImplicitDict): - lo: int | None + lo: Optional[int] """If specified, no indices lower than this value will be included in the range.""" - i: int | None + i: Optional[int] """If specified, no index other than this one will be included in the range.""" - hi: int | None + hi: Optional[int] """If specified, no indices higher than this value will be included in the range.""" def includes(self, i: int) -> bool: @@ -42,21 +44,21 @@ def includes(self, i: int) -> bool: class ActionGeneratorSelectionCondition(ImplicitDict): """By default, select all action generators. When specified, limit selection to specified conditions.""" - types: list[GeneratorTypeName] | None + types: Optional[list[GeneratorTypeName]] """Only select action generators of the specified types.""" class TestSuiteSelectionCondition(ImplicitDict): """By default, select all test suites. When specified, limit selection to specified conditions.""" - types: list[TestSuiteTypeName] | None + types: Optional[list[TestSuiteTypeName]] """Only select test suites of the specified types.""" class TestScenarioSelectionCondition(ImplicitDict): """By default, select all test scenarios. When specified, limit selection to specified conditions.""" - types: list[TestScenarioTypeName] | None + types: Optional[list[TestScenarioTypeName]] """Only select test scenarios of the specified types.""" @@ -73,7 +75,7 @@ class NthInstanceCondition(ImplicitDict): class AncestorSelectionCondition(ImplicitDict): """Select ancestor actions meeting all the specified conditions.""" - of_generation: int | None + of_generation: Optional[int] """The ancestor is exactly this many generations removed (1 = parent, 2 = grandparent, etc). If not specified, an ancestor of any generation meeting the `which` conditions will be selected.""" @@ -88,19 +90,19 @@ class TestSuiteActionSelectionCondition(ImplicitDict): If more than one subcondition is specified, satisfaction of ALL subconditions are necessary to select the action. """ - is_action_generator: ActionGeneratorSelectionCondition | None + is_action_generator: Optional[ActionGeneratorSelectionCondition] """Select these action generator actions.""" - is_test_suite: TestSuiteSelectionCondition | None + is_test_suite: Optional[TestSuiteSelectionCondition] """Select these test suite actions.""" - is_test_scenario: TestScenarioSelectionCondition | None + is_test_scenario: Optional[TestScenarioSelectionCondition] """Select these test scenario actions.""" - regex_matches_name: str | None + regex_matches_name: Optional[str] """Select actions where this regular expression has a match in the action's name.""" - defined_at: list[JSONAddress] | None + defined_at: Optional[list[JSONAddress]] """Select actions defined at one of the specified addresses. The top-level action in a test run is 'test_scenario', 'test_suite', or 'action_generator'. Children use the @@ -109,66 +111,93 @@ class TestSuiteActionSelectionCondition(ImplicitDict): 'action_generator.actions[1].test_suite.actions[2].test_scenario'. An address that starts or ends with 'actions[i]' is invalid and will never match.""" - nth_instance: NthInstanceCondition | None + nth_instance: Optional[NthInstanceCondition] """Select only certain instances of matching actions.""" - has_ancestor: AncestorSelectionCondition | None + has_ancestor: Optional[AncestorSelectionCondition] """Select only actions with a matching ancestor.""" - except_when: list[TestSuiteActionSelectionCondition] | None + except_when: Optional[list[TestSuiteActionSelectionCondition]] """Do not select actions selected by any of these conditions, even when they are selected by one or more conditions above.""" class ExecutionConfiguration(ImplicitDict): - include_action_when: list[TestSuiteActionSelectionCondition] | None = None + include_action_when: Optional[list[TestSuiteActionSelectionCondition]] = None """If specified, only execute test actions if they are selected by ANY of these conditions (and not selected by any of the `skip_when` conditions).""" - skip_action_when: list[TestSuiteActionSelectionCondition] | None = None + skip_action_when: Optional[list[TestSuiteActionSelectionCondition]] = None """If specified, do not execute test actions if they are selected by ANY of these conditions.""" - stop_fast: bool | None = False + stop_fast: Optional[bool] = False """If true, escalate the Severity of any failed check to Critical in order to end the test run early.""" - stop_when_resource_not_created: bool | None = False + do_not_stop_fast_for_acceptable_findings: Optional[bool] + """If true, make an exception for stop_fast above when the failed check is identified as an acceptable_finding in one of the tested_requirements artifact descriptions.""" + + stop_when_resource_not_created: Optional[bool] = False """If true, stop test execution if one of the resources cannot be created. Otherwise, resources that cannot be created due to missing prerequisites are simply treated as omitted.""" + scenarios_filter: str | None + """Filter test scenarios by scenario type using a regex. If the filter regex does not match within the scenario type, the scenario is skipped. When empty, all scenarios are executed. Useful for targeted debugging. Overridden by --filter""" + + stop_after: Optional[StringBasedTimeDelta] + """If specified, stop the test run at the next earliest convenience (generally just after completion of the current test scenario) if it has been running at least this long.""" + class TestConfiguration(ImplicitDict): action: TestSuiteActionDeclaration """The action this test configuration wants to run (usually a test suite)""" - non_baseline_inputs: list[JSONAddress] | None = None + non_baseline_inputs: Optional[list[JSONAddress]] = None """List of portions of the configuration that should not be considered when computing the test baseline signature (e.g., environmental definitions).""" resources: ResourceCollection """Declarations for resources used by the test suite""" - execution: ExecutionConfiguration | None + execution: Optional[ExecutionConfiguration] """Specification for how to execute the test run.""" TestedRequirementsCollectionIdentifier = str -"""Identifier for a requirements collection, local to a TestedRequirementsConfiguration artifact configuration.""" +"""Identifier for a requirements collection, local to a TestedRequirementsConfiguration artifact configuration. + +This value will be displayed as RC- in the artifact. To avoid confusion, no spaces are recommended in values of +this type; e.g., 'SCD_with_DSS' rather than 'SCD with DSS' or 'ServerProviderAndDisplayProvider' versus 'Service Provider +and Display Provider'. Succinct names are recommended over lengthy ones.""" + + +class FullyQualifiedCheck(ImplicitDict): + scenario_type: TestScenarioTypeName + """Scenario in which the check occurs.""" + + test_case_name: str + """Test case in which the check occurs, omitting the ' test case' suffix. Must be an exact match to documentation; sensitive to case and spacing.""" + + test_step_name: str + """Test step in which the check occurs, omitting the ' test step' suffix. Must be an exact match to documentation; sensitive to case and spacing.""" + + check_name: str + """Name of the check, omitting the ' check' suffix. Must be an exact match to documentation; sensitive to case and spacing.""" class TestedRequirementsConfiguration(ImplicitDict): report_name: str """Name of subfolder in output path to contain the rendered templated report""" - requirement_collections: ( - dict[TestedRequirementsCollectionIdentifier, RequirementCollection] | None - ) + requirement_collections: Optional[ + dict[TestedRequirementsCollectionIdentifier, RequirementCollection] + ] """Definition of requirement collections specific to production of this artifact.""" - aggregate_participants: dict[ParticipantID, list[ParticipantID]] | None + aggregate_participants: Optional[dict[ParticipantID, list[ParticipantID]]] """If specified, a list of 'aggregate participants', each of which is composed of multiple test participants. If specified, these aggregate participants are the preferred subject for `participant_requirements`. """ - participant_requirements: ( - dict[ParticipantID, TestedRequirementsCollectionIdentifier | None] | None - ) + participant_requirements: Optional[ + dict[ParticipantID, TestedRequirementsCollectionIdentifier | None] + ] """If a requirement collection is specified for a participant, only the requirements in the specified collection will be listed on that participant's report. If a requirement collection is specified as None/null for a participant, all potentially-testable requirements will be included. @@ -176,6 +205,9 @@ class TestedRequirementsConfiguration(ImplicitDict): If a participant is not listed, no report will be generated for them. """ + acceptable_findings: Optional[list[FullyQualifiedCheck]] + """If any check identified in this field fails, ignore the failure when determining Tested Requirements outcomes.""" + class SequenceViewConfiguration(ImplicitDict): redact_access_tokens: bool = True @@ -201,7 +233,7 @@ class TemplatedReportConfiguration(ImplicitDict): report_name: str """Name of HTML file (without extension) to contain the rendered templated report""" - configuration: TemplatedReportInjectedConfiguration | None = None + configuration: Optional[TemplatedReportInjectedConfiguration] = None """Configuration to be injected in the templated report""" @@ -209,7 +241,7 @@ class RawReportConfiguration(ImplicitDict): redact_access_tokens: bool = True """When True, look for instances of "Authorization" keys in the report with values starting "Bearer " and redact the signature from those access tokens""" - indent: int | None = None + indent: Optional[int] = None """To pretty-print JSON content, specify an indent level (generally 2), or omit or set to None to write compactly.""" @@ -218,37 +250,57 @@ class GloballyExpandedReportConfiguration(ImplicitDict): """When True, look for instances of "Authorization" keys in the report with values starting "Bearer " and redact the signature from those access tokens""" +class TimingReportConfiguration(ImplicitDict): + percentage_of_time_to_break_down: float = 100.0 + """Percentage of test time to break down in the timing report (smaller contributions are not reported)""" + + class ArtifactsConfiguration(ImplicitDict): - raw_report: RawReportConfiguration | None = None + raw_report: Optional[RawReportConfiguration] = None """Configuration for raw report generation""" - report_html: ReportHTMLConfiguration | None = None + report_html: Optional[ReportHTMLConfiguration] = None """If specified, configuration describing how an HTML version of the raw report should be generated""" - templated_reports: list[TemplatedReportConfiguration] | None = None + templated_reports: Optional[list[TemplatedReportConfiguration]] = None """List of report templates to be rendered""" - tested_requirements: list[TestedRequirementsConfiguration] | None = None + tested_requirements: Optional[list[TestedRequirementsConfiguration]] = None """If specified, list of configurations describing desired reports summarizing tested requirements for each participant""" - sequence_view: SequenceViewConfiguration | None = None + sequence_view: Optional[SequenceViewConfiguration] = None """If specified, configuration describing a desired report describing the sequence of events that occurred during the test""" - globally_expanded_report: GloballyExpandedReportConfiguration | None = None + globally_expanded_report: Optional[GloballyExpandedReportConfiguration] = None """If specified, configuration describing a desired report mimicking what might be seen had the test run been conducted manually.""" + timing_report: Optional[TimingReportConfiguration] = None + """If specified, configuration describing a desired report describing where and how time was spent during the test.""" + + @property + def acceptable_findings(self) -> Iterable[FullyQualifiedCheck]: + """Iterates through checks where findings are acceptable in at least one tested_requirements artifact.""" + if "tested_requirements" not in self or not self.tested_requirements: + return + for tested_requirements in self.tested_requirements: + if ( + "acceptable_findings" in tested_requirements + and tested_requirements.acceptable_findings + ): + yield from tested_requirements.acceptable_findings + class USSQualifierConfigurationV1(ImplicitDict): - test_run: TestConfiguration | None = None + test_run: Optional[TestConfiguration] = None """If specified, configuration describing how to perform a test run""" - artifacts: ArtifactsConfiguration | None = None + artifacts: Optional[ArtifactsConfiguration] = None """If specified, configuration describing the artifacts related to the test run""" - validation: ValidationConfiguration | None = None + validation: Optional[ValidationConfiguration] = None """If specified, configuration describing how to validate the output report (and return an error code if validation fails)""" class USSQualifierConfiguration(ImplicitDict): - v1: USSQualifierConfigurationV1 | None + v1: Optional[USSQualifierConfigurationV1] """Configuration in version 1 format""" diff --git a/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml b/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml index f68961faf3..8bf309e2fb 100644 --- a/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml +++ b/monitoring/uss_qualifier/configurations/dev/dss_probing.yaml @@ -3,7 +3,9 @@ v1: test_run: resources: resource_declarations: + kentland_service_area_volume: { $ref: 'library/resources.yaml#/kentland_service_area_volume' } kentland_service_area: { $ref: 'library/resources.yaml#/kentland_service_area' } + kentland_planning_area_volume: { $ref: 'library/resources.yaml#/kentland_planning_area_volume' } kentland_planning_area: { $ref: 'library/resources.yaml#/kentland_planning_area' } kentland_problematically_big_area: { $ref: 'library/resources.yaml#/kentland_problematically_big_area' } @@ -55,6 +57,7 @@ v1: participant_requirements: uss1: null uss2: all_astm_dss_requirements + timing_report: { } validation: criteria: - $ref: ./library/validation.yaml#/execution_error_none diff --git a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml index 39c0652d71..4881c6c6fb 100644 --- a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml +++ b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml @@ -30,7 +30,6 @@ v1: second_utm_auth: second_utm_auth planning_area: planning_area problematically_big_area: problematically_big_area - test_exclusions: test_exclusions # When a test run is executed, a "baseline signature" is computed uniquely identifying the "baseline" of the test, # usually excluding exactly what systems are participating in the test (the "environment"). This is a list of @@ -135,7 +134,7 @@ v1: specification: # A USS that hosts a DSS instance is also a participant in the test, even if they don't fulfill any other roles participant_id: uss1_dss - base_url: http://dss.uss1.localutm + base_url: http://dss1.uss1.localutm supports_ovn_request: true dss_instances: @@ -148,10 +147,10 @@ v1: user_participant_ids: # Participants using a DSS instance they do not provide should be listed as users of that DSS (so that they can take credit for USS requirements enforced by the DSS) - mock_uss # mock_uss uses this DSS instance; it does not provide its own instance - base_url: http://dss.uss1.localutm + base_url: http://dss1.uss1.localutm supports_ovn_request: true - participant_id: uss2_dss - base_url: http://dss.uss2.localutm + base_url: http://dss1.uss2.localutm supports_ovn_request: true # Mock USS that can be used in tests for flight planning, modifying data sharing behavior and recording interactions @@ -167,15 +166,6 @@ v1: # ========== Environment ========== # ================================= - # Controls tests behavior - test_exclusions: - $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json - resource_type: resources.dev.TestExclusionsResource - specification: - # Tests should allow private addresses that are not publicly addressable since this configuration runs locally - allow_private_addresses: true - allow_cleartext_queries: true - # Means by which uss_qualifier can discover which subscription ('sub' claim of its tokes) it is described by utm_client_identity: resource_type: resources.communications.ClientIdentityResource @@ -194,12 +184,11 @@ v1: client_identity: utm_client_identity # Area that will be used for queries and resource creation that are geo-located - planning_area: + planning_area_volume: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json - resource_type: resources.PlanningAreaResource + resource_type: resources.VolumeResource specification: - base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/f3548_self_contained/planning_area - volume: + template: outline_polygon: vertices: - lat: 37.1853 @@ -219,6 +208,14 @@ v1: reference: W84 units: M + planning_area: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.PlanningAreaResource + dependencies: + volume: planning_area_volume + specification: + base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/f3548_self_contained/planning_area + # An area designed to be soo big as to be refused by systems queries with it. problematically_big_area: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -280,10 +277,13 @@ v1: # How to execute a test run using this configuration execution: - # Since we expect no failed checks and want to stop execution immediately if there are any failed checks, we set - # this parameter to true. + # Since we want to stop execution immediately if there are any unexpected failed checks, we set this parameter to + # true. stop_fast: true + # But, don't stop_fast for findings that are acceptable according to any of the tested_requirements. + do_not_stop_fast_for_acceptable_findings: true + # This block defines artifacts related to the test run. Note that all paths are # relative to where uss_qualifier is executed from, and are located inside the # Docker container executing uss_qualifier. @@ -370,6 +370,7 @@ v1: - astm.f3548.v21.DSS0210,A2-7-2,7 - astm.f3548.v21.DSS0215 - astm.f3548.v21.DSS0300 + - interuss.automated_testing.execution.RunToCompletion - interuss.automated_testing.flight_planning.ClearArea - interuss.automated_testing.flight_planning.DeleteFlightSuccess - interuss.automated_testing.flight_planning.ExpectedBehavior @@ -404,6 +405,7 @@ v1: - astm.f3548.v21.USS0105,1 - astm.f3548.v21.USS0105,3 - astm.f3548.v21.USS0105,4 + - interuss.automated_testing.execution.RunToCompletion - interuss.automated_testing.flight_planning.ClearArea - interuss.automated_testing.flight_planning.DeleteFlightSuccess - interuss.automated_testing.flight_planning.ExpectedBehavior @@ -415,12 +417,24 @@ v1: uss1: scd_and_dss uss2: scd_no_dss + # It is acceptable for the checks listed below to fail (in terms of determining the status of tested requirements in this artifact) + acceptable_findings: + - scenario_type: scenarios.astm.utm.dss.dss_interoperability.DSSInteroperability + test_case_name: "Prerequisites" + test_step_name: "Test environment requirements" + check_name: "DSS instance is publicly addressable" + # Rationale: tests should allow private addresses that are not publicly addressable since this configuration runs locally + # Write out a human-readable report showing the sequence of events of the test sequence_view: {} # Write out a long-form report mimicking if the test run was performed manually globally_expanded_report: {} + # Write out a report describing where and how time was spent during the test + timing_report: + percentage_of_time_to_break_down: 90 + # This block defines whether to return an error code from the execution of uss_qualifier, based on the content of the # test run report. All of the criteria must be met to return a successful code. validation: @@ -441,6 +455,7 @@ v1: pass_condition: # When considering all of the applicable elements... elements: - # ...the number of applicable elements should be zero. + # ...the number of applicable elements should be 4... count: - equal_to: 0 + # ...specifically, the 4x "DSS instance is publicly accessible" checks we deem acceptable + equal_to: 4 diff --git a/monitoring/uss_qualifier/configurations/dev/general_flight_auth.jsonnet b/monitoring/uss_qualifier/configurations/dev/general_flight_auth.jsonnet index 6081819d32..8edc729f85 100644 --- a/monitoring/uss_qualifier/configurations/dev/general_flight_auth.jsonnet +++ b/monitoring/uss_qualifier/configurations/dev/general_flight_auth.jsonnet @@ -36,6 +36,7 @@ local validation = import './library/validation.yaml'; artifacts: { raw_report: {}, sequence_view: {}, + globally_expanded_report: {}, tested_requirements: [ { report_name: 'requirements', diff --git a/monitoring/uss_qualifier/configurations/dev/geoawareness_cis.yaml b/monitoring/uss_qualifier/configurations/dev/geoawareness_cis.yaml index 25b30da1f3..ac405b7fb7 100644 --- a/monitoring/uss_qualifier/configurations/dev/geoawareness_cis.yaml +++ b/monitoring/uss_qualifier/configurations/dev/geoawareness_cis.yaml @@ -3,15 +3,20 @@ v1: test_run: resources: resource_declarations: - source_document: + source_document_ed269: resource_type: resources.eurocae.ed269.source_document.SourceDocument specification: - url: file://./test_data/che/geoawareness/cis_source_sample.json + url: file://./test_data/che/geoawareness/cis_source_sample_ed269.json + source_document_ed318: + resource_type: resources.eurocae.ed318.source_document.SourceDocument + specification: + url: file://./test_data/che/geoawareness/cis_source_sample_ed318.json action: test_suite: suite_type: suites.uspace.geo_awareness_cis resources: - source_document: source_document + source_document_ed269: source_document_ed269 + source_document_ed318: source_document_ed318 execution: stop_fast: true artifacts: diff --git a/monitoring/uss_qualifier/configurations/dev/geospatial_comprehension.yaml b/monitoring/uss_qualifier/configurations/dev/geospatial_comprehension.yaml index 1abe49a8a3..018e97e5be 100644 --- a/monitoring/uss_qualifier/configurations/dev/geospatial_comprehension.yaml +++ b/monitoring/uss_qualifier/configurations/dev/geospatial_comprehension.yaml @@ -4,19 +4,39 @@ v1: resources: resource_declarations: example_feature_check_table: {$ref: 'library/resources.yaml#/example_feature_check_table'} + generated_feature_check_table: {$ref: 'library/resources.yaml#/generated_feature_check_table'} utm_auth: {$ref: 'library/environment.yaml#/utm_auth'} geospatial_info_provider: {$ref: 'library/environment.yaml#/geospatial_info_provider'} action: - test_scenario: - scenario_type: scenarios.interuss.geospatial_map.GeospatialFeatureComprehension + test_suite: resources: geospatial_info_provider: geospatial_info_provider - table: example_feature_check_table + example_feature_check_table: example_feature_check_table + generated_feature_check_table: generated_feature_check_table + suite_definition: + name: Geospatial comprehension CI suite + resources: + geospatial_info_provider: resources.geospatial_info.GeospatialInfoProviderResource + example_feature_check_table: resources.interuss.geospatial_map.FeatureCheckTableResource + generated_feature_check_table: resources.interuss.geospatial_map.FeatureCheckTableResource + actions: + - test_scenario: + scenario_type: scenarios.interuss.geospatial_map.GeospatialFeatureComprehension + resources: + geospatial_info_provider: geospatial_info_provider + table: example_feature_check_table + - test_scenario: + scenario_type: scenarios.interuss.geospatial_map.GeospatialFeatureComprehension + resources: + geospatial_info_provider: geospatial_info_provider + table: generated_feature_check_table + execution: stop_fast: true artifacts: raw_report: {} sequence_view: {} + globally_expanded_report: {} validation: $ref: ./library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml index 2d4f880007..d2f5b4015e 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml @@ -103,10 +103,10 @@ netrid_dss_instances_v19: dss_instances: - participant_id: uss1 rid_version: F3411-19 - base_url: http://dss.uss1.localutm + base_url: http://dss1.uss1.localutm - participant_id: uss2 rid_version: F3411-19 - base_url: http://dss.uss2.localutm + base_url: http://dss1.uss2.localutm netrid_dss_instances_v22a: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -117,10 +117,10 @@ netrid_dss_instances_v22a: dss_instances: - participant_id: uss1 rid_version: F3411-22a - base_url: http://dss.uss1.localutm/rid/v2 + base_url: http://dss1.uss1.localutm/rid/v2 - participant_id: uss2 rid_version: F3411-22a - base_url: http://dss.uss2.localutm/rid/v2 + base_url: http://dss1.uss2.localutm/rid/v2 netrid_dss_instance_v19: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -130,7 +130,7 @@ netrid_dss_instance_v19: specification: participant_id: uss1 rid_version: F3411-19 - base_url: http://dss.uss1.localutm + base_url: http://dss1.uss1.localutm netrid_dss_instance_v22a: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -140,7 +140,7 @@ netrid_dss_instance_v22a: specification: participant_id: uss1 rid_version: F3411-22a - base_url: http://dss.uss1.localutm/rid/v2 + base_url: http://dss1.uss1.localutm/rid/v2 # ===== Flight planning ===== @@ -186,7 +186,7 @@ scd_dss: auth_adapter: utm_auth specification: participant_id: uss1 - base_url: http://dss.uss1.localutm + base_url: http://dss1.uss1.localutm supports_ovn_request: true scd_dss_instances: @@ -199,10 +199,10 @@ scd_dss_instances: - participant_id: uss1 user_participant_ids: - mock_uss - base_url: http://dss.uss1.localutm + base_url: http://dss1.uss1.localutm supports_ovn_request: true - participant_id: uss2 - base_url: http://dss.uss2.localutm + base_url: http://dss1.uss2.localutm supports_ovn_request: true # ===== DSS CockroachDB nodes ===== @@ -312,11 +312,11 @@ uss_identification: specification: uss_identifiers: uss1: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*uss1\.localutm.*' uss2: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*uss2\.localutm.*' uss3: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*uss3\.localutm.*' diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml index a7ae5cf422..1820cf11f3 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml @@ -102,10 +102,10 @@ netrid_dss_instances_v19: dss_instances: - participant_id: uss1 rid_version: F3411-19 - base_url: http://localhost:8082 + base_url: http://localhost:8001 - participant_id: uss2 rid_version: F3411-19 - base_url: http://localhost:8082 + base_url: http://localhost:8001 netrid_dss_instances_v22a: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -116,10 +116,10 @@ netrid_dss_instances_v22a: dss_instances: - participant_id: uss1 rid_version: F3411-22a - base_url: http://localhost:8082/rid/v2 + base_url: http://localhost:8001/rid/v2 - participant_id: uss2 rid_version: F3411-22a - base_url: http://localhost:8082/rid/v2 + base_url: http://localhost:8001/rid/v2 netrid_dss_instance_v19: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -129,7 +129,7 @@ netrid_dss_instance_v19: specification: participant_id: uss1 rid_version: F3411-19 - base_url: http://localhost:8082 + base_url: http://localhost:8001 netrid_dss_instance_v22a: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -139,7 +139,7 @@ netrid_dss_instance_v22a: specification: participant_id: uss1 rid_version: F3411-22a - base_url: http://localhost:8082/rid/v2 + base_url: http://localhost:8001/rid/v2 # ===== Flight planning ===== @@ -185,7 +185,7 @@ scd_dss: auth_adapter: utm_auth specification: participant_id: uss1 - base_url: http://localhost:8082 + base_url: http://localhost:8001 supports_ovn_request: true scd_dss_instances: @@ -198,10 +198,10 @@ scd_dss_instances: - participant_id: uss1 user_participant_ids: - mock_uss - base_url: http://localhost:8082 + base_url: http://localhost:8001 supports_ovn_request: true - participant_id: uss2 - base_url: http://localhost:8082 + base_url: http://localhost:8001 supports_ovn_request: true # ===== DSS CockroachDB nodes ===== @@ -213,7 +213,7 @@ dss_datastore_cluster: nodes: - participant_id: uss1 host: localhost - port: 26257 + port: 26201 # ===== mock_uss instances ===== @@ -279,8 +279,8 @@ uss_identification: specification: uss_identifiers: uss1: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*localhost:8074.*' uss2: - astm_url_regexes: + server_url_regexes: - 'http://[^/]*localhost:8094.*' diff --git a/monitoring/uss_qualifier/configurations/dev/library/resources.yaml b/monitoring/uss_qualifier/configurations/dev/library/resources.yaml index 6441bf46aa..06e42eec7a 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/resources.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/resources.yaml @@ -21,32 +21,53 @@ id_generator: client_identity: utm_client_identity specification: { } +kentland_service_area_volume: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.VolumeResource + specification: + template: + outline_polygon: + vertices: + - lat: 37.1853 + lng: -80.6140 + - lat: 37.2148 + lng: -80.6140 + - lat: 37.2148 + lng: -80.5440 + - lat: 37.1853 + lng: -80.5440 + altitude_lower: + value: 0 + reference: W84 + units: M + altitude_upper: + value: 3048 + reference: W84 + units: M + start_time: + offset_from: + starting_from: + time_during_test: TimeOfEvaluation + offset: 1s + end_time: + offset_from: + starting_from: + time_during_test: TimeOfEvaluation + offset: 1h0m1s + kentland_service_area: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json resource_type: resources.netrid.ServiceAreaResource + dependencies: + volume: kentland_service_area_volume specification: base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/kentland_service_area - footprint: - - lat: 37.1853 - lng: -80.6140 - - lat: 37.2148 - lng: -80.6140 - - lat: 37.2148 - lng: -80.5440 - - lat: 37.1853 - lng: -80.5440 - altitude_min: 0 - altitude_max: 3048 - reference_time: '2023-01-10T00:00:00.123456+00:00' - time_start: '2023-01-10T00:00:01.123456+00:00' - time_end: '2023-01-10T01:00:01.123456+00:00' -kentland_planning_area: +kentland_planning_area_volume: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json - resource_type: resources.PlanningAreaResource + resource_type: resources.VolumeResource specification: - base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/kentland_planning_area - volume: + template: outline_polygon: vertices: - lat: 37.1853 @@ -66,6 +87,14 @@ kentland_planning_area: reference: W84 units: M +kentland_planning_area: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.PlanningAreaResource + dependencies: + volume: kentland_planning_area_volume + specification: + base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/kentland_planning_area + au_problematically_big_area: # A huge (as in "too big") area for checks around area sizes $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json resource_type: resources.VolumeResource @@ -97,38 +126,59 @@ kentland_problematically_big_area: - lat: 38 lng: -80 +zurich_service_area_volume: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.VolumeResource + specification: + template: + outline_polygon: + vertices: + - lat: 47.45237618874023 + lng: 8.337401667996478 + - lat: 47.38882810764797 + lng: 8.343217430398902 + - lat: 47.3643466256706 + lng: 8.389932768382156 + - lat: 47.36323038495268 + lng: 8.536070011827881 + - lat: 47.36909451406398 + lng: 8.580678976356925 + - lat: 47.4007874493574 + lng: 8.57764881798931 + - lat: 47.451319730511365 + lng: 8.46442103543102 + altitude_lower: + value: 0 + reference: W84 + units: M + altitude_upper: + value: 1000 + reference: W84 + units: M + start_time: + offset_from: + starting_from: + time_during_test: TimeOfEvaluation + offset: 1s + end_time: + offset_from: + starting_from: + time_during_test: TimeOfEvaluation + offset: 1h0m1s + zurich_service_area: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json resource_type: resources.netrid.ServiceAreaResource + dependencies: + volume: zurich_service_area_volume specification: base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/zurich_service_area - footprint: - - lat: 47.45237618874023 - lng: 8.337401667996478 - - lat: 47.38882810764797 - lng: 8.343217430398902 - - lat: 47.3643466256706 - lng: 8.389932768382156 - - lat: 47.36323038495268 - lng: 8.536070011827881 - - lat: 47.36909451406398 - lng: 8.580678976356925 - - lat: 47.4007874493574 - lng: 8.57764881798931 - - lat: 47.451319730511365 - lng: 8.46442103543102 - altitude_min: 0 - altitude_max: 1000 - reference_time: '2025-10-15T00:00:00.123456+00:00' - time_start: '2025-10-15T00:00:01.123456+00:00' - time_end: '2025-10-15T01:00:01.123456+00:00' -zurich_planning_area: +zurich_planning_area_volume: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json - resource_type: resources.PlanningAreaResource + resource_type: resources.VolumeResource specification: - base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/zurich_planning_area - volume: + template: outline_polygon: vertices: - lat: 47.45237618874023 @@ -154,6 +204,14 @@ zurich_planning_area: reference: W84 units: M +zurich_planning_area: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.PlanningAreaResource + dependencies: + volume: zurich_planning_area_volume + specification: + base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/zurich_planning_area + zurich_problematically_big_area: # A huge (as in "too big") area for checks around area sizes $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json resource_type: resources.VolumeResource @@ -219,13 +277,11 @@ kml_storage_config: flight_record_collection_path: "./output/generate_rid_test_data/flight_data/test_data.usa.netrid.dcdemo_flights.json" # ===== Flight planning intents ===== - -che_planning_area: +che_planning_area_volume: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json - resource_type: resources.PlanningAreaResource + resource_type: resources.VolumeResource specification: - base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/che_planning_area - volume: + template: outline_polygon: vertices: - lat: 45.1853 @@ -245,6 +301,14 @@ che_planning_area: reference: W84 units: M +che_planning_area: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.PlanningAreaResource + dependencies: + volume: che_planning_area_volume + specification: + base_url: https://testdummy.interuss.org/interuss/monitoring/uss_qualifier/configurations/dev/library/resources/che_planning_area + che_problematically_big_area: resource_type: resources.VolumeResource specification: @@ -421,12 +485,12 @@ example_feature_check_table: use_timezone: +01:00 duration: 5m expected_result: Advise - - geospatial_check_id: TEST_002 + - geospatial_check_id: TEST_003 requirement_ids: - REQ_001 - REQ_003 - REQ_004 - description: The second test step defined by the test designer + description: The third test step defined by the test designer operation_rule_set: Rules1 restriction_source: ThisRegulator volumes: @@ -458,6 +522,17 @@ example_feature_check_table: duration: 5m expected_result: Advise +generated_feature_check_table: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.interuss.geospatial_map.FeatureCheckTableResource + specification: + generate_at_runtime: + dict_resources: + geojson_example: + path: test_data.aus.incidents + jsonnet_script: + path: test_data.aus.incident_translator + # ===== mock_uss behavioral control ===== locality_che: diff --git a/monitoring/uss_qualifier/configurations/dev/message_signing.yaml b/monitoring/uss_qualifier/configurations/dev/message_signing.yaml index 61dbe47ae1..79e4d2aa4e 100644 --- a/monitoring/uss_qualifier/configurations/dev/message_signing.yaml +++ b/monitoring/uss_qualifier/configurations/dev/message_signing.yaml @@ -6,6 +6,7 @@ v1: che_invalid_flight_intents: {$ref: 'library/resources.yaml#/che_invalid_flight_intents'} che_non_conflicting_flights: {$ref: 'library/resources.yaml#/che_non_conflicting_flights'} che_problematically_big_area: {$ref: 'library/resources.yaml#/che_problematically_big_area'} + che_planning_area_volume: {$ref: 'library/resources.yaml#/che_planning_area_volume'} che_planning_area: {$ref: 'library/resources.yaml#/che_planning_area'} combination_selector: resource_type: resources.flight_planning.FlightPlannerCombinationSelectorResource diff --git a/monitoring/uss_qualifier/configurations/dev/minimal_probing.yaml b/monitoring/uss_qualifier/configurations/dev/minimal_probing.yaml index 91f727e45d..b2ecd0e40a 100644 --- a/monitoring/uss_qualifier/configurations/dev/minimal_probing.yaml +++ b/monitoring/uss_qualifier/configurations/dev/minimal_probing.yaml @@ -3,6 +3,7 @@ v1: test_run: resources: resource_declarations: + kentland_planning_area_volume: { $ref: 'library/resources.yaml#/kentland_planning_area_volume' } kentland_planning_area: { $ref: 'library/resources.yaml#/kentland_planning_area' } utm_auth: { $ref: 'library/environment.yaml#/utm_auth' } diff --git a/monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml b/monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml index 83889ae017..198b9eb1f2 100644 --- a/monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml +++ b/monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml @@ -7,7 +7,9 @@ v1: netrid_observation_evaluation_configuration: {$ref: 'library/resources.yaml#/netrid_observation_evaluation_configuration'} utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'} id_generator: {$ref: 'library/resources.yaml#/id_generator'} + kentland_service_area_volume: {$ref: 'library/resources.yaml#/kentland_service_area_volume'} kentland_service_area: {$ref: 'library/resources.yaml#/kentland_service_area'} + kentland_planning_area_volume: {$ref: 'library/resources.yaml#/kentland_planning_area_volume'} kentland_planning_area: {$ref: 'library/resources.yaml#/kentland_planning_area'} au_problematically_big_area: {$ref: 'library/resources.yaml#/au_problematically_big_area'} diff --git a/monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml b/monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml index 54e719e49d..b4ce563c71 100644 --- a/monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml +++ b/monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml @@ -7,7 +7,9 @@ v1: netrid_observation_evaluation_configuration: {$ref: 'library/resources.yaml#/netrid_observation_evaluation_configuration'} utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'} id_generator: {$ref: 'library/resources.yaml#/id_generator'} + kentland_service_area_volume: {$ref: 'library/resources.yaml#/kentland_service_area_volume'} kentland_service_area: {$ref: 'library/resources.yaml#/kentland_service_area'} + kentland_planning_area_volume: {$ref: 'library/resources.yaml#/kentland_planning_area_volume'} kentland_planning_area: {$ref: 'library/resources.yaml#/kentland_planning_area'} au_problematically_big_area: {$ref: 'library/resources.yaml#/au_problematically_big_area'} @@ -64,6 +66,7 @@ v1: uss1: sp_dp_dss uss2: sp_dss sequence_view: {} + timing_report: {} validation: criteria: - $ref: ./library/validation.yaml#/execution_error_none @@ -73,4 +76,4 @@ v1: pass_condition: elements: count: - equal_to: 2 # 2 DatastoreAccess scenarios are skipped + equal_to: 3 # 2 DatastoreAccess scenarios are skipped, 1 F3411-22a system versions diff --git a/monitoring/uss_qualifier/configurations/dev/noop.yaml b/monitoring/uss_qualifier/configurations/dev/noop.yaml index 1570d80e2c..c8406817ed 100644 --- a/monitoring/uss_qualifier/configurations/dev/noop.yaml +++ b/monitoring/uss_qualifier/configurations/dev/noop.yaml @@ -28,5 +28,6 @@ v1: indent: 2 sequence_view: {} report_html: {} + timing_report: {} validation: $ref: ./library/validation.yaml#/normal_test diff --git a/monitoring/uss_qualifier/configurations/dev/uspace.yaml b/monitoring/uss_qualifier/configurations/dev/uspace.yaml index 4113d54311..e905a38925 100644 --- a/monitoring/uss_qualifier/configurations/dev/uspace.yaml +++ b/monitoring/uss_qualifier/configurations/dev/uspace.yaml @@ -8,11 +8,14 @@ v1: che_invalid_flight_intents: {$ref: 'library/resources.yaml#/che_invalid_flight_intents'} che_invalid_flight_auth_flights: {$ref: 'library/resources.yaml#/che_invalid_flight_auth_flights'} che_non_conflicting_flights: {$ref: 'library/resources.yaml#/che_non_conflicting_flights'} + che_planning_area_volume: {$ref: 'library/resources.yaml#/che_planning_area_volume'} che_planning_area: {$ref: 'library/resources.yaml#/che_planning_area'} netrid_observation_evaluation_configuration: {$ref: 'library/resources.yaml#/netrid_observation_evaluation_configuration'} utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'} id_generator: {$ref: 'library/resources.yaml#/id_generator'} + zurich_service_area_volume: {$ref: 'library/resources.yaml#/zurich_service_area_volume'} zurich_service_area: {$ref: 'library/resources.yaml#/zurich_service_area'} + zurich_planning_area_volume: {$ref: 'library/resources.yaml#/zurich_planning_area_volume'} zurich_planning_area: {$ref: 'library/resources.yaml#/zurich_planning_area'} zurich_problematically_big_area: {$ref: 'library/resources.yaml#/zurich_problematically_big_area'} zurich_flights_data: {$ref: 'library/resources.yaml#/zurich_flights_data'} @@ -137,6 +140,8 @@ v1: - astm.f3411.v22a.service_provider#UAS ID Serial Number provider - astm.f3411.v22a.service_provider#Height provider - astm.f3411.v22a.service_provider#Operator Position provider + - astm.f3411.v22a.service_provider#Operator Altitude provider + - astm.f3411.v22a.service_provider#Operator Location Type provider - astm.f3411.v22a.service_provider#Operational Status provider - astm.f3411.v22a.display_provider#Mandatory requirements - astm.f3411.v22a.display_provider#UAS ID transmitter @@ -157,6 +162,8 @@ v1: - astm.f3411.v22a.display_provider#Speed transmitter - astm.f3411.v22a.display_provider#Vertical Speed transmitter - astm.f3411.v22a.display_provider#Operator Position transmitter + - astm.f3411.v22a.display_provider#Operator Altitude transmitter + - astm.f3411.v22a.display_provider#Operator Location Type transmitter - astm.f3411.v22a.dss_provider - astm.f3548.v21.scd#Automated verification - astm.f3548.v21.dss_provider @@ -176,6 +183,8 @@ v1: uss1: uspace uss2: uspace sequence_view: {} + timing_report: + percentage_of_time_to_break_down: 90 validation: criteria: - $ref: ./library/validation.yaml#/execution_error_none @@ -185,4 +194,4 @@ v1: pass_condition: elements: count: - equal_to: 4 # 4 DatastoreAccess scenarios are skipped + equal_to: 5 # 4 DatastoreAccess scenarios are skipped, 1 F3411-22a system versions diff --git a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/definitions/baseline_a.libsonnet b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/definitions/baseline_a.libsonnet index fda4b602ba..a05e4e33ab 100644 --- a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/definitions/baseline_a.libsonnet +++ b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/definitions/baseline_a.libsonnet @@ -108,10 +108,10 @@ function(env) { }, // Area that will be used for queries and resource creation that are geo-located - planning_area: { - resource_type: 'resources.PlanningAreaResource', + planning_area_volume: { + resource_type: 'resources.VolumeResource', specification: { - volume: { + template: { outline_polygon: { vertices: [ { @@ -145,6 +145,13 @@ function(env) { }, }, }, + planning_area: { + resource_type: 'resources.PlanningAreaResource', + specification: {}, + dependencies: { + volume: 'planning_area_volume', + }, + }, // An area designed to be soo big as to be refused by systems queries with it. problematically_big_area: { @@ -361,6 +368,11 @@ function(env) { // Write out a human-readable report showing the sequence of events of the test sequence_view: {}, + + // Write out a timing report showing where and how time was spent during the test + timing_report: { + percentage_of_time_to_break_down: 90, + }, }, // artifacts validation: { diff --git a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/environments/local/test_1.jsonnet b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/environments/local/test_1.jsonnet index 69132854a8..32633fe4ad 100644 --- a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/environments/local/test_1.jsonnet +++ b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/environments/local/test_1.jsonnet @@ -11,6 +11,7 @@ local env_template = import '../../definitions/env_template_a.libsonnet'; // This resource provides membership in the tests defined for this environment. local test_membership = import '../../participants/test_membership.libsonnet'; +local limit_to = import '../../participants/limit_to.libsonnet'; // This resource describes the mock_uss instance to be used in the test. local mock_uss = import '../../participants/mock_uss.libsonnet'; @@ -18,10 +19,11 @@ local mock_uss = import '../../participants/mock_uss.libsonnet'; // The concrete environment depends on which environment is being used and which participants are included; those // participants are specified here so the concrete environment definition can be rendered. local env_code = 'local_env'; // Environment code; each participant below must define a block with this name +local allow_list = ['uss1', 'uss2']; // Participants who may participate in this test if they choose to local env = env_template( env_code, 'DummyOAuth(http://oauth.authority.localutm:8085/token,uss_qualifier)', // Means by which to obtain access tokens for the next-higher environment (to retrieve prod versions) - test_membership(env_code).active_participants.test_1, // Active participants + limit_to(test_membership(env_code).active_participants.test_1, allow_list), // Allowed active participants test_membership(env_code).participants_in_env_to_clear, // Participants for which pre-existing flights should be cleared mock_uss, // mock_uss participant ); diff --git a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/limit_to.libsonnet b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/limit_to.libsonnet new file mode 100644 index 0000000000..f45f996ae2 --- /dev/null +++ b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/limit_to.libsonnet @@ -0,0 +1,6 @@ +// Limits a list of `participants` to only those whose `participant_id` appears in the provided `allow_list` +function(participants, allow_list) [ + p + for p in participants + if std.member(allow_list, p.participant_id) +] diff --git a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/test_membership.libsonnet b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/test_membership.libsonnet index a785a0bd29..86a15db4cb 100644 --- a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/test_membership.libsonnet +++ b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/test_membership.libsonnet @@ -22,7 +22,6 @@ function(env_code) local unique_test_names = std.uniq(std.sort(all_test_names)); { - // --- Active Participant Lists per Environment --- // Defines which participants are active for each specific test configuration in the environment. active_participants: { // For each unique test name, build an array of participants involved in that test. diff --git a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss1.libsonnet b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss1.libsonnet index 529343dd4a..be0f070d81 100644 --- a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss1.libsonnet +++ b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss1.libsonnet @@ -46,7 +46,7 @@ // Participants using a DSS instance they do not provide should be listed as users of that DSS (so that they can take credit for USS requirements enforced by the DSS) 'mock_uss', // mock_uss uses this DSS instance; it does not provide its own instance ], - base_url: 'http://dss.uss1.localutm', + base_url: 'http://dss1.uss1.localutm', supports_ovn_request: true }, ] diff --git a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss2.libsonnet b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss2.libsonnet index 257e4cfda9..c795cc309c 100644 --- a/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss2.libsonnet +++ b/monitoring/uss_qualifier/configurations/dev/utm_implementation_us/participants/uss2.libsonnet @@ -42,7 +42,7 @@ dss_instances: [ { participant_id: 'uss2_dss', - base_url: 'http://dss.uss2.localutm', + base_url: 'http://dss1.uss2.localutm', supports_ovn_request: true, }, ] diff --git a/monitoring/uss_qualifier/configurations/interuss/pooled_dss_probing.yaml b/monitoring/uss_qualifier/configurations/interuss/pooled_dss_probing.yaml index de9860e5af..2f78669ed0 100644 --- a/monitoring/uss_qualifier/configurations/interuss/pooled_dss_probing.yaml +++ b/monitoring/uss_qualifier/configurations/interuss/pooled_dss_probing.yaml @@ -3,7 +3,9 @@ v1: test_run: resources: resource_declarations: + kentland_service_area_volume: {$ref: '../dev/library/resources.yaml#/kentland_service_area_volume'} kentland_service_area: { $ref: '../dev/library/resources.yaml#/kentland_service_area' } + kentland_planning_area_volume: { $ref: '../dev/library/resources.yaml#/kentland_planning_area_volume' } kentland_planning_area: { $ref: '../dev/library/resources.yaml#/kentland_planning_area' } kentland_problematically_big_area: { $ref: '../dev/library/resources.yaml#/kentland_problematically_big_area' } @@ -55,6 +57,12 @@ v1: - astm.f3411.v22a.dss_provider - astm.f3411.v19.dss_provider - astm.f3548.v21.dss_provider + exclude: + requirements: + # the following 3 requirements are about the DSS not retaining precise geographical extents, which cannot be tested automatically + - astm.f3411.v19.DSS0040 + - astm.f3411.v22a.DSS0040 + - astm.f3548.v21.DSS0010 participant_requirements: uss1: all_astm_dss_requirements uss2: all_astm_dss_requirements diff --git a/monitoring/uss_qualifier/fileio.py b/monitoring/uss_qualifier/fileio.py index 1896bf81f0..4dd01b6d4b 100644 --- a/monitoring/uss_qualifier/fileio.py +++ b/monitoring/uss_qualifier/fileio.py @@ -332,7 +332,7 @@ def _replace_refs( cache: dict[str, dict] | None = None, ) -> None: for path in ref_parent_paths: - parent = [m.value for m in bc_jsonpath_ng.parse(path).find(content)] + parent = [m.value for m in bc_jsonpath_ng.parser.parse(path).find(content)] if len(parent) != 1: raise RuntimeError( f'Unexpectedly found {len(parent)} matches for $ref parent JSON Path "{path}"' @@ -344,7 +344,7 @@ def _replace_refs( ref_path, context_file_name, cache ) else: - ref_json_path = bc_jsonpath_ng.parse( + ref_json_path = bc_jsonpath_ng.parser.parse( ref_path.replace("#", "$").replace("/", ".") ) ref_content = [m.value for m in ref_json_path.find(content)] @@ -362,7 +362,9 @@ def _replace_refs( if allof_parent_path + ".allOf" in allof_paths: allof_parent_content = [ m.value - for m in bc_jsonpath_ng.parse(allof_parent_path).find(content) + for m in bc_jsonpath_ng.parser.parse(allof_parent_path).find( + content + ) ] if len(allof_parent_content) != 1: raise RuntimeError( diff --git a/monitoring/uss_qualifier/main.py b/monitoring/uss_qualifier/main.py index c387e7b1a6..48fce5891f 100644 --- a/monitoring/uss_qualifier/main.py +++ b/monitoring/uss_qualifier/main.py @@ -6,12 +6,14 @@ import sys import yaml -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from loguru import logger from monitoring.monitorlib.dicts import get_element_or_default, remove_elements from monitoring.monitorlib.versioning import get_code_version, get_commit_hash from monitoring.uss_qualifier.configurations.configuration import ( + ExecutionConfiguration, + TestConfiguration, USSQualifierConfiguration, USSQualifierConfigurationV1, ) @@ -79,6 +81,12 @@ def parseArgs() -> argparse.Namespace: help="When true, do not run a test configuration which would produce unredacted sensitive information in its artifacts", ) + parser.add_argument( + "--filter", + default=None, + help="Filter test scenarios by class name using a regex. When empty, all scenarios are executed. Useful for targeted debugging.", + ) + return parser.parse_args() @@ -87,8 +95,8 @@ class TestDefinitionDescription(ImplicitDict): codebase_version: str commit_hash: str - baseline_signature: str | None = None - environment_signature: str | None = None + baseline_signature: Optional[str] = None + environment_signature: Optional[str] = None def sign(self, whole_config: USSQualifierConfiguration) -> None: logger.debug("Computing signatures of inputs") @@ -101,18 +109,21 @@ def sign(self, whole_config: USSQualifierConfiguration) -> None: baseline = whole_config environment = [] self.baseline_signature = compute_baseline_signature( - self.codebase_version, - self.commit_hash, - compute_signature(baseline), + self.codebase_version, self.commit_hash, compute_signature(baseline) ) self.environment_signature = compute_signature(environment) def execute_test_run( - whole_config: USSQualifierConfiguration, description: TestDefinitionDescription + whole_config: USSQualifierConfiguration, + description: TestDefinitionDescription, ): + assert whole_config.v1 config = whole_config.v1.test_run + if not config: + raise ValueError("v1.test_run not defined in configuration") + logger.info("Instantiating resources") stop_when_not_created = ( "execution" in config @@ -127,14 +138,17 @@ def execute_test_run( ) logger.info("Instantiating top-level test suite action") - context = ExecutionContext(config.execution if "execution" in config else None) + if "artifacts" in whole_config.v1 and whole_config.v1.artifacts: + acceptable_findings = list(whole_config.v1.artifacts.acceptable_findings) + else: + acceptable_findings = [] + context = ExecutionContext( + config.execution if "execution" in config else None, acceptable_findings + ) action = TestSuiteAction(config.action, resources) logger.info("Running top-level test suite action") report = action.run(context) - if report.successful(): - logger.info("Final result: SUCCESS") - else: - logger.warning("Final result: FAILURE") + logger.info("Top-level test suite action complete") return TestRunReport( codebase_version=description.codebase_version, @@ -172,6 +186,7 @@ def run_config( output_path: str | None, runtime_metadata: dict | None, disallow_unredacted: bool, + scenarios_filter: str | None, ): config_src = load_dict_with_references(config_name) @@ -187,6 +202,21 @@ def run_config( whole_config = ImplicitDict.parse(config_src, USSQualifierConfiguration) + if scenarios_filter: + # We set the scenario filter in the test run execution's object + # As parameters are optional, we ensure they do exist first + + if "v1" not in whole_config or not whole_config.v1: + whole_config.v1 = USSQualifierConfigurationV1() + if "test_run" not in whole_config.v1 or not whole_config.v1.test_run: + whole_config.v1.test_run = TestConfiguration() + if ( + "execution" not in whole_config.v1.test_run + or not whole_config.v1.test_run.execution + ): + whole_config.v1.test_run.execution = ExecutionConfiguration() + whole_config.v1.test_run.execution.scenarios_filter = scenarios_filter + if config_output: logger.info("Writing flattened configuration to {}", config_output) if config_output.lower().endswith(".json"): @@ -257,6 +287,7 @@ def main() -> int: raise ValueError("--runtime-metadata must specify a JSON dictionary") disallow_unredacted = args.disallow_unredacted + scenarios_filter = args.filter config_names = str(args.config).split(",") @@ -285,6 +316,7 @@ def main() -> int: output_path, runtime_metadata, disallow_unredacted, + scenarios_filter, ) if exit_code != os.EX_OK: return exit_code diff --git a/monitoring/uss_qualifier/make_artifacts.sh b/monitoring/uss_qualifier/make_artifacts.sh index 1e8adcff16..8acac43388 100755 --- a/monitoring/uss_qualifier/make_artifacts.sh +++ b/monitoring/uss_qualifier/make_artifacts.sh @@ -3,11 +3,14 @@ set -eo pipefail if [[ $# -lt 1 ]]; then - echo "Usage: $0 [ []]" + echo "Usage: $0 [ []]" echo "Generates artifacts according to the specified configuration(s) using the specified report(s)" - echo ": Location of the report file (or multiple locations separated by commas). Relative paths are relative to this folder. Use file:// prefix to explicitly specify file-based location." - echo ": Location of the configuration file (or multiple locations separated by commas)." - echo ": Location to which artifacts should be written (defaults to output/)" + echo " : Location (on the host machine) of the report file. Relative paths are RELATIVE TO THE REPO ROOT." + echo " : Location of the configuration file describing what artifacts to make. Must be built into the monitoring image (in configurations/personal, for instance). If not specified, use artifacts configuration from report." + echo " : Location (on the host machine) to which artifacts should be written. Defaults to folder containing the report." + echo "Examples:" + echo " ./monitoring/uss_qualifier/make_artifacts.sh ~/Downloads/be0bbe7c-4670-43f5-906a-594be69087f4/report.json configurations.dev.f3548_self_contained" + echo " ./make_artifacts.sh monitoring/uss_qualifier/output/f3548_self_contained/report.json configurations.personal.custom_artifacts" exit 1 fi @@ -26,23 +29,31 @@ cd monitoring || exit 1 make image ) -REPORT_NAME="${1}" -echo "Reading report(s) from: ${REPORT_NAME}" -MAKE_ARTIFACTS_OPTIONS="--report $REPORT_NAME" +REPORT_LOCATION="${1}" + +# TODO: Retrieve local copy of report if location starts with "http" + +REPORT_PATH=$(realpath "${REPORT_LOCATION}") +echo "Reading report from (host machine): ${REPORT_PATH}" +REPORT_FILENAME=$(basename "${REPORT_PATH}") +MAKE_ARTIFACTS_OPTIONS="--report file:///input/${REPORT_FILENAME}" if [ "$#" -gt 1 ]; then CONFIG_NAME="${2}" - echo "Generating artifacts from configuration(s): ${CONFIG_NAME}" + echo "Generating artifacts from configuration (in image): ${CONFIG_NAME}" MAKE_ARTIFACTS_OPTIONS="$MAKE_ARTIFACTS_OPTIONS --config $CONFIG_NAME" fi if [ "$#" -gt 2 ]; then - OUTPUT_PATH="${3}" - MAKE_ARTIFACTS_OPTIONS="$MAKE_ARTIFACTS_OPTIONS --output-path $OUTPUT_PATH" + OUTPUT_PATH=$(realpath "${3}") +else + OUTPUT_PATH=$(dirname "${REPORT_PATH}") fi +OUTPUT_FOLDERNAME=$(basename "${OUTPUT_PATH}") +echo "Writing artifacts to (host machine): ${OUTPUT_PATH}" +MAKE_ARTIFACTS_OPTIONS="$MAKE_ARTIFACTS_OPTIONS --output-path output/${OUTPUT_FOLDERNAME}" -OUTPUT_DIR="monitoring/uss_qualifier/output" -mkdir -p "$OUTPUT_DIR" +mkdir -p "$OUTPUT_PATH" CACHE_DIR="monitoring/uss_qualifier/.templates_cache" mkdir -p "$CACHE_DIR" @@ -53,7 +64,8 @@ docker run --name uss_qualifier \ -u "$(id -u):$(id -g)" \ -e PYTHONBUFFERED=1 \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ - -v "$(pwd)/$OUTPUT_DIR:/app/$OUTPUT_DIR" \ + -v "${REPORT_PATH}:/input/${REPORT_FILENAME}" \ + -v "${OUTPUT_PATH}:/app/monitoring/uss_qualifier/output/${OUTPUT_FOLDERNAME}" \ -v "$(pwd)/$CACHE_DIR:/app/$CACHE_DIR" \ -w /app/monitoring/uss_qualifier \ interuss/monitoring \ diff --git a/monitoring/uss_qualifier/pytest.ini b/monitoring/uss_qualifier/pytest.ini index 1b519f83fb..929c76da6d 100644 --- a/monitoring/uss_qualifier/pytest.ini +++ b/monitoring/uss_qualifier/pytest.ini @@ -1,3 +1,7 @@ [pytest] addopts = -s -v python_files = *_test.py + +# Remove me when https://github.com/testcontainers/testcontainers-python/issues/874 is fixed +filterwarnings = + ignore:^The @wait_container_is_ready decorator:DeprecationWarning diff --git a/monitoring/uss_qualifier/reports/artifacts.py b/monitoring/uss_qualifier/reports/artifacts.py index 4b376b29d8..487ef583d1 100644 --- a/monitoring/uss_qualifier/reports/artifacts.py +++ b/monitoring/uss_qualifier/reports/artifacts.py @@ -1,5 +1,7 @@ import json import os +import time +from multiprocessing import Process from implicitdict import ImplicitDict from loguru import logger @@ -17,6 +19,7 @@ from monitoring.uss_qualifier.reports.tested_requirements.generate import ( generate_tested_requirements, ) +from monitoring.uss_qualifier.reports.timing.generate import generate_timing_report def default_output_path(config_name: str) -> str: @@ -36,7 +39,10 @@ def generate_artifacts( disallow_unredacted: bool, ): logger.debug(f"Writing artifacts to {os.path.abspath(output_path)}") - os.makedirs(output_path, exist_ok=True) + try: + os.makedirs(output_path, exist_ok=True) + except PermissionError: + pass # This may be ok if writing directly to a single specific output folder provided to a container def _should_redact(cfg) -> bool: result = "redact_access_tokens" in cfg and cfg.redact_access_tokens @@ -50,10 +56,12 @@ def _should_redact(cfg) -> bool: redacted_report = ImplicitDict.parse(json.loads(json.dumps(report)), TestRunReport) redact_access_tokens(redacted_report) - if artifacts.raw_report: - # Raw report + def make_raw_report() -> None: + if not artifacts.raw_report: + return path = os.path.join(output_path, "report.json") logger.info(f"Writing raw report to {path}") + t0 = time.monotonic() raw_report = artifacts.raw_report report_to_write = redacted_report if _should_redact(raw_report) else report with open(path, "w") as f: @@ -61,45 +69,60 @@ def _should_redact(cfg) -> bool: json.dump(report_to_write, f, indent=raw_report.indent) else: json.dump(report_to_write, f) + logger.info(f"Wrote raw report in {time.monotonic() - t0:.1f}s") - if artifacts.report_html: - # HTML rendering of raw report + def make_html_report() -> None: + if not artifacts.report_html: + return path = os.path.join(output_path, "report.html") logger.info(f"Writing HTML report to {path}") + t0 = time.monotonic() report_to_write = ( redacted_report if _should_redact(artifacts.report_html) else report ) with open(path, "w") as f: f.write(make_report_html(report_to_write)) + logger.info(f"Wrote HTML report in {time.monotonic() - t0:.1f}s") - if artifacts.templated_reports: - # Templated reports + def make_templated_reports() -> None: + if not artifacts.templated_reports: + return render_templates( output_path, artifacts.templated_reports, redacted_report, ) - if artifacts.tested_requirements: - # Tested requirements view + def make_tested_requirements() -> None: + if not artifacts.tested_requirements: + return for tested_reqs_config in artifacts.tested_requirements: path = os.path.join(output_path, tested_reqs_config.report_name) logger.info(f"Writing tested requirements view to {path}") + t0 = time.monotonic() generate_tested_requirements(redacted_report, tested_reqs_config, path) + logger.info( + f"Wrote tested requirements view in {time.monotonic() - t0:.1f}s" + ) - if artifacts.sequence_view: - # Sequence view + def make_sequence_view() -> None: + if not artifacts.sequence_view: + return path = os.path.join(output_path, "sequence") logger.info(f"Writing sequence view to {path}") + t0 = time.monotonic() report_to_write = ( redacted_report if _should_redact(artifacts.sequence_view) else report ) generate_sequence_view(report_to_write, artifacts.sequence_view, path) + logger.info(f"Wrote sequence view in {time.monotonic() - t0:.1f}s") - if artifacts.globally_expanded_report: - # Globally-expanded report + def make_globally_expanded_report() -> None: + if artifacts.globally_expanded_report is None: + return path = os.path.join(output_path, "globally_expanded") logger.info(f"Writing globally-expanded report to {path}") + t0 = time.monotonic() report_to_write = ( redacted_report if _should_redact(artifacts.globally_expanded_report) @@ -108,3 +131,32 @@ def _should_redact(cfg) -> bool: generate_globally_expanded_report( report_to_write, artifacts.globally_expanded_report, path ) + logger.info(f"Wrote globally-expanded report in {time.monotonic() - t0:.1f}s") + + def make_timing_report() -> None: + if artifacts.timing_report is None: + return + path = os.path.join(output_path, "timing") + logger.info(f"Writing timing report to {path}") + t0 = time.monotonic() + generate_timing_report(redacted_report, artifacts.timing_report, path) + logger.info(f"Wrote timing report in {time.monotonic() - t0:.1f}s") + + artifact_generators = [ + make_raw_report, + make_html_report, + make_templated_reports, + make_tested_requirements, + make_sequence_view, + make_globally_expanded_report, + make_timing_report, + ] + generators = [Process(target=g, daemon=True) for g in artifact_generators] + for p in generators: + p.start() + for p in generators: + p.join() + + failed = [p for p in generators if p.exitcode != 0] + if failed: + raise RuntimeError(f"{len(failed)} generator(s) failed. Check exception above.") diff --git a/monitoring/uss_qualifier/reports/capabilities.py b/monitoring/uss_qualifier/reports/capabilities.py index 02066ddae7..8f395a6a89 100644 --- a/monitoring/uss_qualifier/reports/capabilities.py +++ b/monitoring/uss_qualifier/reports/capabilities.py @@ -1,7 +1,7 @@ from collections.abc import Callable from typing import Any, TypeVar -import bc_jsonpath_ng.ext +from bc_jsonpath_ng.ext.parser import parse as bc_jsonpath_ng_ext_parse from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.reports.capability_definitions import ( @@ -35,11 +35,11 @@ [SpecificConditionType, ParticipantID, TestSuiteReport], ParticipantCapabilityConditionEvaluationReport, ] -_capability_condition_evaluators: dict[SpecificConditionType, ConditionEvaluator] = {} +_capability_condition_evaluators: dict[type[SpecificCondition], ConditionEvaluator] = {} def capability_condition_evaluator[SpecificConditionType: SpecificCondition]( - condition_type: SpecificConditionType, + condition_type: type[SpecificConditionType], ): """Decorator to label a function that evaluates a specific condition for verifying a capability. @@ -196,28 +196,36 @@ def evaluate_requirements_checked_conditions( ) -def _jsonpath_of(descendant: Any, ancestor: Any) -> str | None: +def _jsonpath_of(descendant: Any, ancestor: Any) -> str: """Construct a relative JSONPath to descendant from ancestor by exhaustive reference equality search. One would think this functionality would be part of the jsonpath_ng package when producing matches, but one would apparently be wrong. This approach is monstrously inefficient, but easy to write and easy to understand. """ - if ancestor is descendant: - return "" - elif isinstance(ancestor, dict): - for k, v in ancestor.items(): - subpath = _jsonpath_of(descendant, v) - if subpath is not None: - return f".{k}{subpath}" - return None - elif isinstance(ancestor, list): - for i, v in enumerate(ancestor): - subpath = _jsonpath_of(descendant, v) - if subpath is not None: - return f"[{i}]{subpath}" - else: + + def __jsonpath_of(descendant: Any, ancestor: Any) -> str | None: + if ancestor is descendant: + return "" + elif isinstance(ancestor, dict): + for k, v in ancestor.items(): + subpath = __jsonpath_of(descendant, v) + if subpath is not None: + return f".{k}{subpath}" + elif isinstance(ancestor, list): + for i, v in enumerate(ancestor): + subpath = __jsonpath_of(descendant, v) + if subpath is not None: + return f"[{i}]{subpath}" + return None + result = __jsonpath_of(descendant, ancestor) + + if result is None: + raise ValueError(f"No path to {descendant} from {ancestor}") + + return result + @capability_condition_evaluator(CapabilityVerifiedCondition) def evaluate_capability_verified_condition( @@ -225,8 +233,13 @@ def evaluate_capability_verified_condition( participant_id: ParticipantID, report: TestSuiteReport, ) -> ParticipantCapabilityConditionEvaluationReport: - path = condition.capability_location if "capability_location" in condition else "$" - matching_reports = bc_jsonpath_ng.ext.parse(path).find(report) + path = ( + condition.capability_location + if "capability_location" in condition + and condition.capability_location is not None + else "$" + ) + matching_reports = bc_jsonpath_ng_ext_parse(path).find(report) checked_capabilities = [] spurious_matches = [] for matching_report in matching_reports: diff --git a/monitoring/uss_qualifier/reports/capability_definitions.py b/monitoring/uss_qualifier/reports/capability_definitions.py index 5fbdafe098..a21e715894 100644 --- a/monitoring/uss_qualifier/reports/capability_definitions.py +++ b/monitoring/uss_qualifier/reports/capability_definitions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier.requirements.definitions import RequirementCollection @@ -60,7 +60,7 @@ class CapabilityVerifiedCondition(SpecificCondition): capability_ids: list[CapabilityID] """List of identifier of capability that must be verified for this condition to be satisfied.""" - capability_location: JSONPathExpression | None + capability_location: Optional[JSONPathExpression] """Location of report to inspect for the verification of the specified capability, relative to the report in which the capability is defined. Implicit default value is "$" (look for verified capability in the report in which the dependant capability is defined). @@ -83,11 +83,11 @@ class CapabilityVerificationCondition(ImplicitDict): Exactly one field must be specified.""" - all_conditions: AllConditions | None - any_conditions: AnyCondition | None - no_failed_checks: NoFailedChecksCondition | None - requirements_checked: RequirementsCheckedCondition | None - capability_verified: CapabilityVerifiedCondition | None + all_conditions: Optional[AllConditions] + any_conditions: Optional[AnyCondition] + no_failed_checks: Optional[NoFailedChecksCondition] + requirements_checked: Optional[RequirementsCheckedCondition] + capability_verified: Optional[CapabilityVerifiedCondition] class ParticipantCapabilityDefinition(ImplicitDict): diff --git a/monitoring/uss_qualifier/reports/globally_expanded/generate.py b/monitoring/uss_qualifier/reports/globally_expanded/generate.py index 79b2036245..9cf20dd9fc 100644 --- a/monitoring/uss_qualifier/reports/globally_expanded/generate.py +++ b/monitoring/uss_qualifier/reports/globally_expanded/generate.py @@ -21,8 +21,6 @@ from monitoring.uss_qualifier.reports.sequence_view.summary_types import ( ActionNode, ActionNodeType, - EpochType, - EventType, Indexer, TestedCase, TestedScenario, @@ -110,6 +108,7 @@ def generate_globally_expanded_report( """ + assert report.configuration.v1 and report.configuration.v1.test_run resource_pool = make_resources_config(report.configuration.v1.test_run) def indented_ul(value) -> list[str]: @@ -184,6 +183,7 @@ def describe_pool_resource(k: str, v: dict) -> str: def _generate_sections(node: ActionNode) -> Iterator[_Section]: if node.node_type == ActionNodeType.Scenario: + assert node.scenario yield _generate_scenario_section(node.scenario) elif node.node_type == ActionNodeType.SkippedAction: yield _generate_skipped_scenario_section(node) @@ -195,7 +195,7 @@ def _generate_sections(node: ActionNode) -> Iterator[_Section]: def _generate_skipped_scenario_section(node: ActionNode) -> _Section: return _Section( title=f"[skipped] {node.name}", - body=f"This instance of this test scenario was skipped in this test run because: {node.skipped_action.reason}", + body=f"This instance of this test scenario was skipped in this test run because: {node.skipped_action.reason if node.skipped_action else '?'}", ) @@ -237,15 +237,15 @@ def _indent_headings(elements: Sequence[marko.element.Element], levels: int) -> for element in elements: if isinstance(element, marko.block.Heading): element.level = min(element.level + levels, 6) - if hasattr(element, "children") and element.children: - _indent_headings(element.children, levels) + if hasattr(element, "children") and element.children: # pyright: ignore[reportAttributeAccessIssue] + _indent_headings(element.children, levels) # pyright: ignore[reportAttributeAccessIssue] def _inflate_fragments(parent: marko.element.Element, origin_filename: str) -> None: - if hasattr(parent, "children") and parent.children: + if hasattr(parent, "children") and parent.children: # pyright: ignore[reportAttributeAccessIssue] c = 0 - while c < len(parent.children): - child = parent.children[c] + while c < len(parent.children): # pyright: ignore[reportAttributeAccessIssue] + child = parent.children[c] # pyright: ignore[reportAttributeAccessIssue] if ( isinstance(child, marko.block.Heading) and hasattr(child, "children") @@ -260,12 +260,12 @@ def _inflate_fragments(parent: marko.element.Element, origin_filename: str) -> N doc = _get_test_step_fragment(absolute_path, child.level) _update_links(doc, absolute_path) _strip_link(child) - parent.children = ( - parent.children[0 : c + 1] + doc.children + parent.children[c + 1 :] + parent.children = ( # pyright: ignore[reportAttributeAccessIssue] + parent.children[0 : c + 1] + doc.children + parent.children[c + 1 :] # pyright: ignore[reportAttributeAccessIssue] ) c += len(doc.children) elif isinstance(child, marko.element.Element): - _inflate_fragments(parent.children[c], origin_filename) + _inflate_fragments(parent.children[c], origin_filename) # pyright: ignore[reportAttributeAccessIssue] c += 1 else: c += 1 @@ -286,14 +286,14 @@ def add_resource_origin(): note = marko.parse( """∅ _This resource was not applicable to this test run and was therefore not provided._\n\n""" ) - doc.children = doc.children[0:c] + note.children + doc.children[c:] + doc.children = doc.children[0:c] + note.children + doc.children[c:] # pyright: ignore[reportAttributeAccessIssue,reportOperatorIssue] c += len(note.children) return # Insert resource origin information origin = marko.parse( f"\n\n✅ Provided by {scenario.resource_origins[current_resource]}.\n" ) - doc.children = doc.children[0:c] + origin.children + doc.children[c:] + doc.children = doc.children[0:c] + origin.children + doc.children[c:] # pyright: ignore[reportOperatorIssue] c += len(origin.children) while c < len(doc.children): @@ -327,11 +327,11 @@ def add_resource_origin(): def _strip_link(element: marko.element.Element) -> None: - if hasattr(element, "children") and element.children: - for c in range(len(element.children)): - child = element.children[c] + if hasattr(element, "children") and element.children: # pyright: ignore[reportAttributeAccessIssue] + for c in range(len(element.children)): # pyright: ignore[reportAttributeAccessIssue] + child = element.children[c] # pyright: ignore[reportAttributeAccessIssue] if isinstance(child, marko.block.inline.Link): - element.children[c] = child.children[0] + element.children[c] = child.children[0] # pyright: ignore[reportAttributeAccessIssue] elif isinstance(child, marko.element.Element): _strip_link(child) @@ -372,7 +372,7 @@ def add_context_to_case(): """∅ _This test case was not applicable to this test run and is therefore not statused._\n\n""" ) doc.children = ( - doc.children[0 : test_case_i0 + 1] + doc.children[0 : test_case_i0 + 1] # pyright: ignore[reportAttributeAccessIssue,reportOperatorIssue] + note.children + doc.children[test_case_i0 + 1 :] ) @@ -394,7 +394,7 @@ def add_context_to_case(): test_case_i0 = c test_case_level = child.level for epoch in scenario.epochs: - if epoch.type != EpochType.Case: + if epoch.case is None: continue if case_name == epoch.case.name: test_case = epoch.case @@ -407,7 +407,7 @@ def add_context_to_case(): test_case_level = child.level cleanup = True for epoch in scenario.epochs: - if epoch.type != EpochType.Case: + if epoch.case is None: continue if len(epoch.case.steps) == 1 and epoch.case.steps[0].name == "Cleanup": test_case = epoch.case @@ -444,7 +444,7 @@ def add_context_to_step(): ) dc = len(note.children) doc.children = ( - doc.children[0 : test_step_i0 + 1] + doc.children[0 : test_step_i0 + 1] # pyright: ignore[reportOperatorIssue] + note.children + doc.children[test_step_i0 + 1 :] ) @@ -494,6 +494,7 @@ def _add_context_to_step( def add_context_to_check(): nonlocal c, i1, added, test_check_name, test_check_i0, test_check_level if test_check_name is not None: + assert test_check_i0 is not None dc = _add_context_to_check(doc, step, test_check_name, test_check_i0, c) c += dc i1 += dc @@ -533,14 +534,14 @@ def _add_context_to_check( check_text = [""] for event in step.events: if ( - event.type == EventType.PassedCheck + event.passed_check is not None and event.passed_check.name == test_check_name ): check_text.append( f"✅ {', '.join(event.passed_check.participants)} ({event.passed_check.timestamp})" ) elif ( - event.type == EventType.FailedCheck + event.failed_check is not None and event.failed_check.name == test_check_name ): check_text.append( @@ -552,7 +553,7 @@ def _add_context_to_check( additions = marko.parse( """∅ _This check was not applicable to this test run and is therefore not statused._\n\n""" ) - doc.children = doc.children[0:i1] + additions.children + doc.children[i1:] + doc.children = doc.children[0:i1] + additions.children + doc.children[i1:] # pyright: ignore[reportOperatorIssue] return len(additions.children) @@ -571,8 +572,8 @@ def _update_links(element: marko.element.Element, origin_filename: str) -> None: url = url.replace("/github.com/", "/raw.githubusercontent.com/") url = url.replace("/blob/", "/") element.dest = url - if hasattr(element, "children") and element.children: - for child in element.children: + if hasattr(element, "children") and element.children: # pyright: ignore[reportAttributeAccessIssue] + for child in element.children: # pyright: ignore[reportAttributeAccessIssue] if isinstance(child, marko.element.Element): _update_links(child, origin_filename) @@ -580,7 +581,7 @@ def _update_links(element: marko.element.Element, origin_filename: str) -> None: def _add_section_numbers(elements: Sequence[marko.element.Element]) -> None: heading_level = 2 levels = [0] - headings = [None] + headings: list[str | None] = [None] prev_heading = None for i, element in enumerate(elements): if isinstance(element, marko.block.Heading): @@ -599,7 +600,7 @@ def _add_section_numbers(elements: Sequence[marko.element.Element]) -> None: heading_level += 1 else: headings.append(text_of(element)) - heading_trace = " -> ".join(headings) + heading_trace = " -> ".join([str(heading) for heading in headings]) raise ValueError( f"Encountered a level {element.level} heading ({text_of(element)}) at element {i} following a level {heading_level} heading ({prev_heading}); expected heading levels to increase by 1 level at a time. Trace: {heading_trace}" ) @@ -612,4 +613,4 @@ def _add_section_numbers(elements: Sequence[marko.element.Element]) -> None: else: element.children = [ marko.block.inline.RawText(section_number) - ] + element.children + ] + element.children # pyright: ignore[reportOperatorIssue] diff --git a/monitoring/uss_qualifier/reports/report.py b/monitoring/uss_qualifier/reports/report.py index 815a649247..6e0ef2d56f 100644 --- a/monitoring/uss_qualifier/reports/report.py +++ b/monitoring/uss_qualifier/reports/report.py @@ -4,7 +4,13 @@ from datetime import UTC, datetime from typing import Any -from implicitdict import ImplicitDict, StringBasedDateTime +import deprecation +from implicitdict import ( + ImplicitDict, + Optional, + StringBasedDateTime, + StringBasedTimeDelta, +) from monitoring.monitorlib import fetch, inspection from monitoring.monitorlib.errors import stacktrace_string @@ -50,10 +56,10 @@ class FailedCheck(ImplicitDict): participants: list[ParticipantID] """Participants that may not meet the relevant requirements due to this failed check""" - query_report_timestamps: list[str] | None + query_report_timestamps: Optional[list[str]] """List of the `report` timestamp field for queries relevant to this failed check""" - additional_data: dict | None + additional_data: Optional[dict] """Additional data, structured according to the checks' needs, that may be relevant for understanding this failed check""" @@ -71,6 +77,34 @@ class PassedCheck(ImplicitDict): """Participants that may not have met the relevant requirements if this check had failed""" +class IntentionalDelay(ImplicitDict): + start_time: StringBasedDateTime + """When the delay started""" + + duration: StringBasedTimeDelta + """Duration of the delay""" + + reason: str + """Reason given for this delay""" + + +class _TimestampAccumulator: + result: datetime | None + _f_accum: Callable[[datetime, datetime], datetime] + + def __init__(self, f_accum: Callable[[datetime, datetime], datetime]): + self.result = None + self._f_accum = f_accum + + def accumulate(self, new_timestamp: datetime | None) -> None: + if new_timestamp is None: + return + if self.result is None: + self.result = new_timestamp + else: + self.result = self._f_accum(self.result, new_timestamp) + + class TestStepReport(ImplicitDict): name: str """Name of this test step""" @@ -81,7 +115,7 @@ class TestStepReport(ImplicitDict): start_time: StringBasedDateTime """Time at which the test step started""" - queries: list[fetch.Query] | None + queries: Optional[list[fetch.Query]] """Description of HTTP requests relevant to this issue""" failed_checks: list[FailedCheck] @@ -90,12 +124,16 @@ class TestStepReport(ImplicitDict): passed_checks: list[PassedCheck] """The checks which successfully passed in this test step""" - end_time: StringBasedDateTime | None + delays: Optional[list[IntentionalDelay]] + """Delays intentionally introduced during this test step""" + + end_time: Optional[StringBasedDateTime] """Time at which the test step completed or encountered an error""" def has_critical_problem(self) -> bool: return any(fc.severity == Severity.Critical for fc in self.failed_checks) + @deprecation.deprecated() def successful(self) -> bool: return False if self.failed_checks else True @@ -125,7 +163,7 @@ def query_passed_checks( def query_failed_checks( self, participant_id: str | None = None - ) -> Iterator[tuple[JSONPathExpression, PassedCheck]]: + ) -> Iterator[tuple[JSONPathExpression, FailedCheck]]: for i, fc in enumerate(self.failed_checks): if participant_id is None or participant_id in fc.participants: yield f"failed_checks[{i}]", fc @@ -138,6 +176,25 @@ def participant_ids(self) -> set[ParticipantID]: ids.update(fc.participants) return ids + @property + def latest_timestamp(self) -> datetime | None: + timestamp = _TimestampAccumulator(max) + if "end_time" in self and self.end_time: + timestamp.accumulate(self.end_time.datetime) + if "queries" in self and self.queries: + for query in self.queries: + timestamp.accumulate(query.response.reported.datetime) + if "delays" in self and self.delays: + for delay in self.delays: + timestamp.accumulate( + delay.start_time.datetime + delay.duration.timedelta + ) + for check in self.failed_checks: + timestamp.accumulate(check.timestamp.datetime) + for check in self.passed_checks: + timestamp.accumulate(check.timestamp.datetime) + return timestamp.result + class TestCaseReport(ImplicitDict): name: str @@ -149,7 +206,7 @@ class TestCaseReport(ImplicitDict): start_time: StringBasedDateTime """Time at which the test case started""" - end_time: StringBasedDateTime | None + end_time: Optional[StringBasedDateTime] """Time at which the test case completed or encountered an error""" steps: list[TestStepReport] @@ -173,7 +230,7 @@ def query_passed_checks( def query_failed_checks( self, participant_id: str | None = None - ) -> Iterator[tuple[JSONPathExpression, PassedCheck]]: + ) -> Iterator[tuple[JSONPathExpression, FailedCheck]]: for i, step in enumerate(self.steps): for path, fc in step.query_failed_checks(participant_id): yield f"steps[{i}].{path}", fc @@ -184,6 +241,16 @@ def participant_ids(self) -> set[ParticipantID]: ids.update(step.participant_ids()) return ids + @property + def latest_timestamp(self) -> datetime | None: + timestamp = _TimestampAccumulator(max) + if "end_time" in self and self.end_time: + timestamp.accumulate(self.end_time.datetime) + if "steps" in self and self.steps: + for step in self.steps: + timestamp.accumulate(step.latest_timestamp) + return timestamp.result + class ErrorReport(ImplicitDict): type: str @@ -223,28 +290,31 @@ class TestScenarioReport(ImplicitDict): documentation_url: str """URL at which this test scenario is described""" - resource_origins: dict[ResourceID, str] | None + resource_origins: Optional[dict[ResourceID, str]] """For each resource used by this test scenario, the place that resource originated.""" - notes: dict[str, Note] | None + notes: Optional[dict[str, Note]] """Additional information about this scenario that may be useful""" + delays: Optional[list[IntentionalDelay]] + """Delays intentionally introduced during this test scenario, but not during any test step""" + start_time: StringBasedDateTime """Time at which the test scenario started""" - end_time: StringBasedDateTime | None + end_time: Optional[StringBasedDateTime] """Time at which the test scenario completed or encountered an error""" successful: bool = False - """True iff test scenario completed normally with no failed checks""" + """DEPRECATED. True iff test scenario completed normally with no failed checks.""" cases: list[TestCaseReport] """Reports for each of the test cases in this test scenario, in chronological order.""" - cleanup: TestStepReport | None + cleanup: Optional[TestStepReport] """If this test scenario performed cleanup, this report captures the relevant information.""" - execution_error: ErrorReport | None + execution_error: Optional[ErrorReport] """If there was an error while executing this test scenario, this field describes the error""" def has_critical_problem(self) -> bool: @@ -288,11 +358,14 @@ def queries(self) -> list[fetch.Query]: queries = list() for case in self.cases: for step in case.steps: - if step.has_field_with_value("queries"): + if "queries" in step and step.queries: queries.extend(step.queries) - if self.has_field_with_value("cleanup") and self.cleanup.has_field_with_value( - "queries" + if ( + "cleanup" in self + and self.cleanup + and "queries" in self.cleanup + and self.cleanup.queries ): queries.extend(self.cleanup.queries) @@ -306,6 +379,28 @@ def participant_ids(self) -> set[ParticipantID]: ids.update(self.cleanup.participant_ids()) return ids + @property + def latest_timestamp(self) -> datetime | None: + timestamp = _TimestampAccumulator(max) + if "end_time" in self and self.end_time: + timestamp.accumulate(self.end_time.datetime) + if "notes" in self and self.notes: + for note in self.notes.values(): + timestamp.accumulate(note.timestamp.datetime) + if "delays" in self and self.delays: + for delay in self.delays: + timestamp.accumulate( + delay.start_time.datetime + delay.duration.timedelta + ) + if "cases" in self and self.cases: + for case in self.cases: + timestamp.accumulate(case.latest_timestamp) + if "cleanup" in self and self.cleanup: + timestamp.accumulate(self.cleanup.latest_timestamp) + if "execution_error" in self and self.execution_error: + timestamp.accumulate(self.execution_error.timestamp.datetime) + return timestamp.result + class ActionGeneratorReport(ImplicitDict): generator_type: GeneratorTypeName @@ -317,11 +412,11 @@ class ActionGeneratorReport(ImplicitDict): actions: list[TestSuiteActionReport] """Reports from the actions generated by the action generator, in order of execution.""" - end_time: StringBasedDateTime | None + end_time: Optional[StringBasedDateTime] """Time at which the action generator completed or encountered an error""" successful: bool = False - """True iff all actions completed normally with no failed checks""" + """DEPRECATED. True iff all actions completed normally with no failed checks""" def has_critical_problem(self) -> bool: return any(a.has_critical_problem() for a in self.actions) @@ -341,7 +436,7 @@ def query_passed_checks( def query_failed_checks( self, participant_id: str | None = None - ) -> Iterator[tuple[JSONPathExpression, PassedCheck]]: + ) -> Iterator[tuple[JSONPathExpression, FailedCheck]]: for i, action in enumerate(self.actions): for path, fc in action.query_failed_checks(participant_id): yield f"actions[{i}].{path}", fc @@ -358,78 +453,63 @@ def participant_ids(self) -> set[ParticipantID]: ids.update(action.participant_ids()) return ids + @property + def latest_timestamp(self) -> datetime | None: + timestamp = _TimestampAccumulator(max) + if "end_time" in self and self.end_time: + timestamp.accumulate(self.end_time.datetime) + for action in self.actions: + timestamp.accumulate(action.latest_timestamp) + return timestamp.result + class TestSuiteActionReport(ImplicitDict): - test_suite: TestSuiteReport | None + test_suite: Optional[TestSuiteReport] """If this action was a test suite, this field will hold its report""" - test_scenario: TestScenarioReport | None + test_scenario: Optional[TestScenarioReport] """If this action was a test scenario, this field will hold its report""" - action_generator: ActionGeneratorReport | None + action_generator: Optional[ActionGeneratorReport] """If this action was an action generator, this field will hold its report""" - skipped_action: SkippedActionReport | None + skipped_action: Optional[SkippedActionReport] """If this action was skipped, this field will hold its report""" - def get_applicable_report(self) -> tuple[bool, bool, bool]: - """Determine which type of report is applicable for this action. - - Note that skipped_action is applicable if none of the other return values are true. - - Returns: - * Whether test_suite is applicable - * Whether test_scenario is applicable - * Whether action_generator is applicable - """ - test_suite = "test_suite" in self and self.test_suite is not None - test_scenario = "test_scenario" in self and self.test_scenario is not None - action_generator = ( - "action_generator" in self and self.action_generator is not None + @property + def invalid_type_error(self): + return ValueError( + "Invalid TestSuiteActionReport: test_scenario, test_suite, action_generator or skipped_action must be specified" ) - skipped_action = "skipped_action" in self and self.skipped_action is not None - if ( - sum( - 1 if case else 0 - for case in [ - test_suite, - test_scenario, - action_generator, - skipped_action, - ] - ) - != 1 - ): - raise ValueError( - "Exactly one of `test_suite`, `test_scenario`, `action_generator`, or `skipped_action` must be populated" - ) - return test_suite, test_scenario, action_generator def _conditional( self, - test_suite_func: Callable[[TestSuiteReport], Any] | Callable[[Any], Any], + test_suite_func: Callable[[Any], Any], test_scenario_func: Callable[[TestScenarioReport], Any] | None = None, action_generator_func: Callable[[ActionGeneratorReport], Any] | None = None, skipped_action_func: Callable[[SkippedActionReport], Any] | None = None, ) -> Any: - test_suite, test_scenario, action_generator = self.get_applicable_report() - if test_suite: + if "test_suite" in self and self.test_suite: return test_suite_func(self.test_suite) - if test_scenario: + elif "test_scenario" in self and self.test_scenario: if test_scenario_func is not None: return test_scenario_func(self.test_scenario) else: return test_suite_func(self.test_scenario) - if action_generator: + elif "action_generator" in self and self.action_generator: if action_generator_func is not None: return action_generator_func(self.action_generator) else: return test_suite_func(self.action_generator) - if skipped_action_func is not None: - return skipped_action_func(self.skipped_action) + elif "skipped_action" in self and self.skipped_action: + if skipped_action_func is not None: + return skipped_action_func(self.skipped_action) + else: + return test_suite_func(self.skipped_action) else: - return test_suite_func(self.skipped_action) + raise self.invalid_type_error + @deprecation.deprecated() def successful(self) -> bool: return self._conditional(lambda report: report.successful) @@ -442,14 +522,13 @@ def all_participants(self) -> set[ParticipantID]: def query_passed_checks( self, participant_id: str | None = None ) -> Iterator[tuple[JSONPathExpression, PassedCheck]]: - test_suite, test_scenario, action_generator = self.get_applicable_report() - if test_suite: + if "test_suite" in self and self.test_suite: report = self.test_suite prefix = "test_suite" - elif test_scenario: + elif "test_scenario" in self and self.test_scenario: report = self.test_scenario prefix = "test_scenario" - elif action_generator: + elif "action_generator" in self and self.action_generator: report = self.action_generator prefix = "action_generator" else: @@ -461,14 +540,13 @@ def query_passed_checks( def query_failed_checks( self, participant_id: str | None = None ) -> Iterator[tuple[JSONPathExpression, FailedCheck]]: - test_suite, test_scenario, action_generator = self.get_applicable_report() - if test_suite: + if "test_suite" in self and self.test_suite: report = self.test_suite prefix = "test_suite" - elif test_scenario: + elif "test_scenario" in self and self.test_scenario: report = self.test_scenario prefix = "test_scenario" - elif action_generator: + elif "action_generator" in self and self.action_generator: report = self.action_generator prefix = "action_generator" else: @@ -493,6 +571,22 @@ def end_time(self) -> StringBasedDateTime | None: lambda report: report.end_time if "end_time" in report else None ) + @property + def latest_timestamp(self) -> datetime | None: + return self._conditional(lambda report: report.latest_time) + + def get_action_type_name(self): + if "test_suite" in self and self.test_suite: + return "test_suite" + elif "test_scenario" in self and self.test_scenario: + return "test_scenario" + elif "action_generator" in self and self.action_generator: + return "action_generator" + elif "skipped_action" in self and self.skipped_action: + return "skipped_action" + else: + return "unknown" + class AllConditionsEvaluationReport(ImplicitDict): """Result of an evaluation of AllConditions determined by whether all the subconditions are satisfied.""" @@ -594,19 +688,19 @@ class ParticipantCapabilityConditionEvaluationReport(ImplicitDict): condition_satisfied: bool """Whether the condition was satisfied for the relevant participant.""" - all_conditions: AllConditionsEvaluationReport | None + all_conditions: Optional[AllConditionsEvaluationReport] """When specified, the condition evaluated was AllConditions.""" - any_conditions: AnyConditionEvaluationReport | None + any_conditions: Optional[AnyConditionEvaluationReport] """When specified, the condition evaluated was AnyCondition.""" - no_failed_checks: NoFailedChecksConditionEvaluationReport | None + no_failed_checks: Optional[NoFailedChecksConditionEvaluationReport] """When specified, the condition evaluated was NoFailedChecksCondition.""" - requirements_checked: RequirementsCheckedConditionEvaluationReport | None + requirements_checked: Optional[RequirementsCheckedConditionEvaluationReport] """When specified, the condition evaluated was RequirementsCheckedCondition.""" - capability_verified: CapabilityVerifiedConditionEvaluationReport | None + capability_verified: Optional[CapabilityVerifiedConditionEvaluationReport] """When specified, the condition evaluated was CapabilityVerifiedCondition.""" @@ -635,6 +729,7 @@ class SkippedActionReport(ImplicitDict): """Full declaration of the action that was skipped.""" @property + @deprecation.deprecated() def successful(self) -> bool: return True @@ -675,11 +770,11 @@ class TestSuiteReport(ImplicitDict): actions: list[TestSuiteActionReport] """Reports from test scenarios and test suites comprising the test suite for this report, in order of execution.""" - end_time: StringBasedDateTime | None + end_time: Optional[StringBasedDateTime] """Time at which the test suite completed""" successful: bool = False - """True iff test suite completed normally with no failed checks""" + """DEPRECATED. True iff test suite completed normally with no failed checks""" capability_evaluations: list[ParticipantCapabilityEvaluationReport] """List of capabilities defined in this test suite, evaluated for each participant.""" @@ -743,7 +838,7 @@ class TestRunReport(ImplicitDict): report: TestSuiteActionReport """Report produced by configured test action""" - runtime_metadata: dict | None + runtime_metadata: Optional[dict] """Metadata for the test run specified at runtime.""" diff --git a/monitoring/uss_qualifier/reports/sequence_view/README.md b/monitoring/uss_qualifier/reports/sequence_view/README.md index 4fda89e71a..99b7bed0a8 100644 --- a/monitoring/uss_qualifier/reports/sequence_view/README.md +++ b/monitoring/uss_qualifier/reports/sequence_view/README.md @@ -1,11 +1,74 @@ # Sequence view artifact +## Purpose + A sequence view artifact is intended to show a chronological view of what happened during a test run. It is mostly useful to developers trying to understand the context of a test failure or examine the sequence of events for a test run. +## Overview + The generated index file contains a list of scenarios and what caused them to be run (e.g., test suite, action generator, etc). Each scenario's page details the events (queries, checks, notes) that occurred during the execution of that scenario. + +## Index page + +The index page contains a brief summary of test run metadata at the top in a table, followed by collapsed configuration details, followed by a "Scenarios executed" table. + +### Resources configuration + +The "Resources configuration" section presents the resources components of the test configuration arranged by "Baseline" and "Environment". This is intended to be useful when trying to understand what the test baseline consists of, and what the test environment consists of. + +### Full configuration + +The "Full configuration" section presents the full, literal test configuration in a mostly raw form. It can be somewhat harder to read/understand than the "Resources configuration" view if looking only for resources, but it more directly corresponds to the actual instructions provided to uss_qualifier and contains all configuration elements (not just resources). + +### Scenarios executed + +The "Scenarios executed" table shows how test scenarios came to be run (e.g., test suite A included action generator B which ran test suite C which included test scenario D), and then the list of actual test scenarios run in the "Scenario" column. Test scenarios are the core unit of test execution, so each test scenario run is numbered in the order it was run. The shorthand to refer to a particular scenario is "s" followed by the test scenario run index. So, for instance, the first scenario run is "s1". Clicking on the name of a test scenario will navigate to a page detailing the execution of that scenario, named s[N].html. + +The columns following the "Scenario" column summarize which participants were active in each scenario by showing the outcomes of checks attributed to those participants in the test scenario. For instance, a green checkmark under USS1 indicates that uss_qualifier verified USS1's compliance to at least one requirement and did not detect any non-compliance from USS1. + +The final column indicates how long each test scenario took to run in minutes:seconds. + +#### Skipped elements + +When a test element is skipped, the cell that would have contained that element is populated with the name of the skipped element and the reason it was skipped in italics. A skipped element does not necessarily indicate a problem. The most common reason for a test element to be skipped is that a resource of a certain type is necessary to perform that element, but that resource was not provided in the test configuration. This generally occurs when the test designer chooses not to include one or more available test features in their test configuration. For instance, a jurisdiction with only one ASTM F3548-21 priority level would likely omit a resource containing flights at different priority levels, and therefore the test scenarios targeting requirements regarding differing priority levels would be skipped. + +#### Execution errors + +If the cell of a test scenario is colored red, that indicates an execution error occurred during that test scenario. Execution errors always indicate a bug in uss_qualifier as all normal uss_qualifier operation should not raise uncaught exceptions. Therefore, any execution errors should be reported to InterUSS unless the most recent version of uss_qualifier is not being used and the bug has already been fixed in a newer version of uss_qualifier. + +## Scenario page + +Each test scenario executed during the test run has its own page linked from the index page. The heading names the test scenario and links to the documentation explaining what actions are conducted during test scenario execution. This documentation [is programmatically linked](../../scenarios/README.md#documentation) to the actual execution of the test scenario. Below the human-readable name of the scenario is the package-based identifier of the test scenario; the base `scenarios` package is the [scenarios folder](../../scenarios) of uss_qualifier. + +### Resources + +The "Resources" section helps identify the origin of each resource used in this particular execution of the test scenario. The test scenario itself may call for, e.g., a control USS and a test USS. This section identifies which resource was used for the control USS and which resource was used for the test USS. + +### Events + +The events table shows the reportable events that happened during this test scenario execution. Test scenarios are segmented into test cases and test cases are segmented into test steps. The content of the test case and test step columns name the test case and test step in which events occur, and provide links to the parts of the test scenario documentation defining those elements. During a test step, the following events can occur: + +* uss_qualifier makes a query (🌐 icon) + * The top-level "[METHOD] [server] [response code]" summary of the query can be clicked to expand to see all recorded details of the query +* uss_qualifier performs a check (icon indicating outcome of check) + * Icon and coloring indicate outcome of check + * Main text is the name of the check (visible in documentation) + * If check did not succeed, a summary of the problem is shown in italics with details below without italics + * The participants associated with the check are identified in their corresponding columns to the right of the event description +* uss_qualifier makes a note (📓 icon) + +Notes can also be made when not inside a test step. + +Each event is identified by its index within the scenario execution. The shorthand to refer to a particular event is "e" followed by the event index. So, for instance, the first event in the test scenario run is "e1". Because event indices are specific to a test scenario run, an event in a particular test run can be identified with the scenario index + event index; for instance, "s3e45". Many unsuccessful checks reference the query or queries that are the basis for the failed check. + +## Troubleshooting a failed check + +To determine why a particular check failed for a particular participant, that participant should locate the first instance of that failed check. This can be done by scrolling down the test scenario page until a red/yellow event for the participant is found. The explanation of what the check is checking and the context around why that check corresponds to requirement compliance can be found in the documentation for the check which can be found by clicking the link on the test step name. This test step documentation is found on the documentation page for the test scenario, and it may be necessary to read more of the earlier portions of the test scenario to understand what is happening in the check that failed. When relevant, the failed check will link to a query event upon which the check's failure is based. In this case, find the referenced query and review its content by clicking the event text to expand the query information. + +If the reason for check failure cannot be determined via this means, a [test scenario bug Issue](https://github.com/interuss/monitoring/issues/new?template=bug_test_scenario.md) can be filed with InterUSS. Be sure to attach at least the relevant test scenario sequence view page when possible, ideally the full zip of artifacts produced from the test run. diff --git a/monitoring/uss_qualifier/reports/sequence_view/events.py b/monitoring/uss_qualifier/reports/sequence_view/events.py index 7519e96d78..a7e18ad083 100644 --- a/monitoring/uss_qualifier/reports/sequence_view/events.py +++ b/monitoring/uss_qualifier/reports/sequence_view/events.py @@ -15,7 +15,6 @@ from monitoring.uss_qualifier.reports.sequence_view.summary_types import ( Epoch, Event, - EventType, Indexer, NoteEvent, TestedCase, @@ -67,7 +66,7 @@ def _step_events( scenario_participants: dict[ParticipantID, TestedParticipant], all_events: list[Event], after: datetime | None, -) -> tuple[TestedStep, datetime]: +) -> tuple[TestedStep, datetime | None]: events = [] # Create events for this step's passed checks @@ -84,7 +83,7 @@ def _step_events( ) for pid in participants: p = scenario_participants.get(pid, TestedParticipant()) - p.has_successes = True + p.has_passes = True scenario_participants[pid] = p # Create events for this step's queries @@ -116,7 +115,7 @@ def _step_events( found = False for e in all_events: if ( - e.type == EventType.Query + e.query is not None and e.query.request.initiated_at == query_timestamp ): query_events.append(e) diff --git a/monitoring/uss_qualifier/reports/sequence_view/generate.py b/monitoring/uss_qualifier/reports/sequence_view/generate.py index 720ae82e63..f6d7b29f24 100644 --- a/monitoring/uss_qualifier/reports/sequence_view/generate.py +++ b/monitoring/uss_qualifier/reports/sequence_view/generate.py @@ -31,8 +31,6 @@ from monitoring.uss_qualifier.reports.sequence_view.summary_types import ( ActionNode, ActionNodeType, - EpochType, - EventType, Indexer, OverviewRow, SkippedAction, @@ -44,13 +42,13 @@ from monitoring.uss_qualifier.scenarios.documentation.parsing import ( get_documentation_by_name, ) -from monitoring.uss_qualifier.suites.definitions import ActionType, TestSuiteDefinition +from monitoring.uss_qualifier.suites.definitions import TestSuiteDefinition UNATTRIBUTED_PARTICIPANT = "unattributed" def _skipped_action_of(report: SkippedActionReport) -> ActionNode: - if report.declaration.get_action_type() == ActionType.TestSuite: + if "test_suite" in report.declaration and report.declaration.test_suite: if ( "suite_type" in report.declaration.test_suite and report.declaration.test_suite.suite_type @@ -75,7 +73,7 @@ def _skipped_action_of(report: SkippedActionReport) -> ActionNode: "Cannot process skipped action for test suite that does not define suite_type nor suite_definition" ) name = "All actions in test suite" - elif report.declaration.get_action_type() == ActionType.TestScenario: + elif "test_scenario" in report.declaration and report.declaration.test_scenario: docs = get_documentation_by_name(report.declaration.test_scenario.scenario_type) return ActionNode( name=docs.name, @@ -83,7 +81,9 @@ def _skipped_action_of(report: SkippedActionReport) -> ActionNode: children=[], skipped_action=SkippedAction(reason=report.reason), ) - elif report.declaration.get_action_type() == ActionType.ActionGenerator: + elif ( + "action_generator" in report.declaration and report.declaration.action_generator + ): generator_type = action_generator_type_from_name( report.declaration.action_generator.generator_type ) @@ -94,9 +94,7 @@ def _skipped_action_of(report: SkippedActionReport) -> ActionNode: ) name = "All actions from action generator" else: - raise ValueError( - f"Cannot process skipped action of type '{report.declaration.get_action_type()}'" - ) + raise report.declaration.invalid_type_error parent.children.append( ActionNode( name=name, @@ -117,26 +115,21 @@ def compute_action_node(report: TestSuiteActionReport, indexer: Indexer) -> Acti Returns: Report information summarized to support a sequence view artifact. """ - ( - is_test_suite, - is_test_scenario, - is_action_generator, - ) = report.get_applicable_report() - if is_test_scenario: + if "test_scenario" in report and report.test_scenario: return ActionNode( name=report.test_scenario.name, node_type=ActionNodeType.Scenario, children=[], scenario=compute_tested_scenario(report.test_scenario, indexer), ) - elif is_test_suite: + elif "test_suite" in report and report.test_suite: children = [compute_action_node(a, indexer) for a in report.test_suite.actions] return ActionNode( name=report.test_suite.name, node_type=ActionNodeType.Suite, children=children, ) - elif is_action_generator: + elif "action_generator" in report and report.action_generator: generator_type = action_generator_type_from_name( report.action_generator.generator_type ) @@ -147,8 +140,10 @@ def compute_action_node(report: TestSuiteActionReport, indexer: Indexer) -> Acti compute_action_node(a, indexer) for a in report.action_generator.actions ], ) - else: + elif "skipped_action" in report and report.skipped_action: return _skipped_action_of(report.skipped_action) + else: + raise report.invalid_type_error def _compute_overview_rows(node: ActionNode) -> Iterator[OverviewRow]: @@ -177,9 +172,13 @@ def _align_overview_rows(rows: list[OverviewRow]) -> None: row.filled = True to_fill -= 1 elif len(row.suite_cells) < max_suite_cols: - if row.suite_cells[-1].first_row and all( - c.node_type == ActionNodeType.Scenario - for c in row.suite_cells[-1].node.children + if ( + row.suite_cells[-1].first_row + and row.suite_cells[-1].node is not None + and all( + c.node_type == ActionNodeType.Scenario + for c in row.suite_cells[-1].node.children + ) ): row.suite_cells[-1].colspan += max_suite_cols - len(row.suite_cells) row.filled = True @@ -212,6 +211,7 @@ def _align_overview_rows(rows: list[OverviewRow]) -> None: def _enumerate_all_participants(node: ActionNode) -> list[ParticipantID]: if node.node_type == ActionNodeType.Scenario: + assert node.scenario return list(node.scenario.participants) else: result = set() @@ -225,6 +225,7 @@ def _generate_scenario_pages( node: ActionNode, config: SequenceViewConfiguration, output_path: str ) -> None: if node.node_type == ActionNodeType.Scenario: + assert node.scenario all_participants = list(node.scenario.participants) all_participants.sort() if UNATTRIBUTED_PARTICIPANT in all_participants: @@ -241,8 +242,6 @@ def _generate_scenario_pages( test_scenario=node.scenario, all_participants=all_participants, kml_file=kml_file if config.render_kml else None, - EpochType=EpochType, - EventType=EventType, UNATTRIBUTED_PARTICIPANT=UNATTRIBUTED_PARTICIPANT, len=len, str=str, @@ -308,6 +307,7 @@ def generate_sequence_view( ) -> None: node = compute_action_node(report.report, Indexer()) + assert report.configuration.v1 and report.configuration.v1.test_run resources_config = make_resources_config(report.configuration.v1.test_run) os.makedirs(output_path, exist_ok=True) diff --git a/monitoring/uss_qualifier/reports/sequence_view/kml.py b/monitoring/uss_qualifier/reports/sequence_view/kml.py index f21f2ac714..33b1774625 100644 --- a/monitoring/uss_qualifier/reports/sequence_view/kml.py +++ b/monitoring/uss_qualifier/reports/sequence_view/kml.py @@ -2,10 +2,10 @@ from typing import Protocol, get_type_hints from implicitdict import ImplicitDict -from loguru import logger from lxml import etree from pykml.factory import KML_ElementMaker as kml from pykml.util import format_xml_with_cdata +from uas_standards.astm.f3411.v22a import api as f3411v22a from uas_standards.astm.f3548.v21.api import ( GetOperationalIntentDetailsResponse, QueryOperationalIntentReferenceParameters, @@ -15,6 +15,7 @@ UpsertFlightPlanRequest, UpsertFlightPlanResponse, ) +from uas_standards.interuss.automated_testing.rid.v1 import injection as rid_injection from monitoring.monitorlib.errors import stacktrace_string from monitoring.monitorlib.fetch import Query, QueryType @@ -28,13 +29,14 @@ upsert_flight_plan, ) from monitoring.monitorlib.kml.generation import query_styles +from monitoring.monitorlib.kml.rid import create_test, get_flights_v22a, rid_styles from monitoring.uss_qualifier.reports.sequence_view.summary_types import TestedScenario class QueryKMLRenderer(Protocol): def __call__( self, query: Query, req: ImplicitDict, resp: ImplicitDict - ) -> list[kml.Element]: + ) -> list[kml.Element]: # pyright: ignore[reportInvalidTypeForm, reportReturnType] """Function that renders the provided query information into KML elements. Args: @@ -45,6 +47,8 @@ def __call__( Returns: List of KML elements to include the folder for the query. """ + __name__: str + @dataclass class QueryKMLRenderInfo: @@ -97,7 +101,11 @@ def make_scenario_kml(scenario: TestedScenario) -> str: step_folder = kml.Folder(kml.name(step.name)) case_folder.append(step_folder) for event in step.events: - if not event.query or "query_type" not in event.query: + if ( + not event.query + or "query_type" not in event.query + or not event.query.query_type + ): continue # Only visualize queries of known types if event.query.query_type not in _query_kml_renderers: continue # Only visualize queries with renderers @@ -114,18 +122,20 @@ def make_scenario_kml(scenario: TestedScenario) -> str: ) step_folder.append(query_folder) - kwargs = {} + kwargs: dict[str, ImplicitDict | Query] = {} if render_info.include_query: kwargs["query"] = event.query if render_info.request_type: try: + if not event.query.request.json: + raise ValueError("No JSON in request") + kwargs["req"] = ImplicitDict.parse( event.query.request.json, render_info.request_type, ) except ValueError as e: msg = f"Error parsing request into {render_info.request_type.__name__}" - logger.warning(msg) query_folder.append( kml.Folder( kml.name(msg), @@ -138,13 +148,15 @@ def make_scenario_kml(scenario: TestedScenario) -> str: and render_info.response_type ): try: + if not event.query.response.json: + raise ValueError("No JSON in response") + kwargs["resp"] = ImplicitDict.parse( event.query.response.json, render_info.response_type, ) except ValueError as e: msg = f"Error parsing response into {render_info.response_type.__name__}" - logger.warning(msg) query_folder.append( kml.Folder( kml.name(msg), @@ -153,7 +165,7 @@ def make_scenario_kml(scenario: TestedScenario) -> str: ) continue try: - query_folder.extend(render_info.renderer(**kwargs)) + query_folder.extend(render_info.renderer(**kwargs)) # pyright: ignore[reportArgumentType] except (TypeError, KeyError, ValueError) as e: msg = f"Error rendering {render_info.renderer.__name__}" query_folder.append( @@ -164,13 +176,17 @@ def make_scenario_kml(scenario: TestedScenario) -> str: ) doc = kml.kml( kml.Document( - *query_styles(), *f3548v21_styles(), *flight_planning_styles(), top_folder + *query_styles(), + *f3548v21_styles(), + *flight_planning_styles(), + *rid_styles(), + top_folder, ) ) return etree.tostring(format_xml_with_cdata(doc), pretty_print=True).decode("utf-8") -@query_kml_renderer(QueryType.F3548v21DSSQueryOperationalIntentReferences) +@query_kml_renderer(QueryType.F3548v21DSSQueryOperationalIntentReferences) # pyright: ignore[reportArgumentType] def render_query_op_intent_references( req: QueryOperationalIntentReferenceParameters, resp: QueryOperationalIntentReferenceResponse, @@ -178,13 +194,25 @@ def render_query_op_intent_references( return [op_intent_refs_query(req, resp)] -@query_kml_renderer(QueryType.F3548v21USSGetOperationalIntentDetails) +@query_kml_renderer(QueryType.F3548v21USSGetOperationalIntentDetails) # pyright: ignore[reportArgumentType] def render_get_op_intent_details(resp: GetOperationalIntentDetailsResponse): return [full_op_intent(resp.operational_intent)] -@query_kml_renderer(QueryType.InterUSSFlightPlanningV1UpsertFlightPlan) +@query_kml_renderer(QueryType.InterUSSFlightPlanningV1UpsertFlightPlan) # pyright: ignore[reportArgumentType] def render_flight_planning_upsert_flight_plan( req: UpsertFlightPlanRequest, resp: UpsertFlightPlanResponse ): return [upsert_flight_plan(req, resp)] + + +@query_kml_renderer(QueryType.InterussRIDAutomatedTestingV1CreateTest) # pyright: ignore[reportArgumentType] +def render_rid_injection_create_test( + req: rid_injection.CreateTestParameters, resp: rid_injection.ChangeTestResponse +): + return create_test(req, resp) + + +@query_kml_renderer(QueryType.F3411v22aUSSSearchFlights) # pyright: ignore[reportArgumentType] +def render_f3411_22a_search_flights(query: Query, resp: f3411v22a.GetFlightsResponse): + return get_flights_v22a(query.request.url, resp) diff --git a/monitoring/uss_qualifier/reports/sequence_view/summary_types.py b/monitoring/uss_qualifier/reports/sequence_view/summary_types.py index 8a18cd71a4..0271fa247f 100644 --- a/monitoring/uss_qualifier/reports/sequence_view/summary_types.py +++ b/monitoring/uss_qualifier/reports/sequence_view/summary_types.py @@ -2,9 +2,9 @@ from dataclasses import dataclass from datetime import datetime -from enum import Enum +from enum import StrEnum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.fetch import Query from monitoring.uss_qualifier.configurations.configuration import ParticipantID @@ -23,33 +23,13 @@ class NoteEvent(ImplicitDict): timestamp: datetime -class EventType(str, Enum): - PassedCheck = "PassedCheck" - FailedCheck = "FailedCheck" - Query = "Query" - Note = "Note" - - class Event(ImplicitDict): event_index: int = 0 - passed_check: PassedCheck | None = None - failed_check: FailedCheck | None = None - query_events: list[Event | str] | None = None - query: Query | None = None - note: NoteEvent | None = None - - @property - def type(self) -> EventType: - if self.passed_check: - return EventType.PassedCheck - elif self.failed_check: - return EventType.FailedCheck - elif self.query: - return EventType.Query - elif self.note: - return EventType.Note - else: - raise ValueError("Invalid Event type") + passed_check: Optional[PassedCheck] = None + failed_check: Optional[FailedCheck] = None + query_events: Optional[list[Event | str]] = None + query: Optional[Query] = None + note: Optional[NoteEvent] = None @property def timestamp(self) -> datetime: @@ -66,7 +46,7 @@ def timestamp(self) -> datetime: def get_query_links(self) -> str: links = [] - for e in self.query_events: + for e in self.query_events or []: if isinstance(e, str): links.append(e) else: @@ -94,23 +74,9 @@ def rows(self) -> int: return sum(s.rows for s in self.steps) -class EpochType(str, Enum): - Case = "Case" - Events = "Events" - - class Epoch(ImplicitDict): - case: TestedCase | None = None - events: list[Event] | None = None - - @property - def type(self) -> EpochType: - if self.case: - return EpochType.Case - elif self.events: - return EpochType.Events - else: - raise ValueError("Invalid Epoch did not specify case or events") + case: Optional[TestedCase] = None + events: Optional[list[Event]] = None @property def rows(self) -> int: @@ -126,7 +92,7 @@ def rows(self) -> int: class TestedParticipant: has_failures: bool = False has_infos: bool = False - has_successes: bool = False + has_passes: bool = False has_queries: bool = False @@ -152,7 +118,7 @@ class SkippedAction: reason: str -class ActionNodeType(str, Enum): +class ActionNodeType(StrEnum): Scenario = "Scenario" Suite = "Suite" ActionGenerator = "ActionGenerator" @@ -163,8 +129,8 @@ class ActionNode(ImplicitDict): name: str node_type: ActionNodeType children: list[ActionNode] - scenario: TestedScenario | None = None - skipped_action: SkippedAction | None = None + scenario: Optional[TestedScenario] = None + skipped_action: Optional[SkippedAction] = None @property def rows(self) -> int: diff --git a/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html b/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html index 1ff4f32162..44a7f74f79 100644 --- a/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html +++ b/monitoring/uss_qualifier/reports/templates/sequence_view/overview.html @@ -133,7 +133,7 @@

Scenarios executed

{% elif row.scenario_node.scenario.participants[participant_id].has_infos %} ℹ️ - {% elif row.scenario_node.scenario.participants[participant_id].has_successes %} + {% elif row.scenario_node.scenario.participants[participant_id].has_passes %} {% elif row.scenario_node.scenario.participants[participant_id].has_queries %} 🌐 diff --git a/monitoring/uss_qualifier/reports/templates/sequence_view/scenario.html b/monitoring/uss_qualifier/reports/templates/sequence_view/scenario.html index 79a0d2cd3a..3dba385eca 100644 --- a/monitoring/uss_qualifier/reports/templates/sequence_view/scenario.html +++ b/monitoring/uss_qualifier/reports/templates/sequence_view/scenario.html @@ -52,7 +52,7 @@

{% set first_row = namespace(epoch=True, step=True) %} {% for epoch in test_scenario.epochs %} {% set first_row.epoch = True %} - {% if epoch.type == EpochType.Case %} + {% if epoch.case %} {% for test_step in epoch.case.steps %} {% set first_row.step = True %} {% for event in test_step.events %} @@ -76,7 +76,7 @@

{% endif %} {{ event.event_index }} - {% if event.type == EventType.PassedCheck %} + {% if event.passed_check %} ✅ {{ event.passed_check.name }} @@ -88,7 +88,7 @@

{% endif %} {% endfor %} - {% elif event.type == EventType.FailedCheck %} + {% elif event.failed_check %} {{ severity_symbol(event.failed_check.severity) }} {% if event.failed_check.documentation_url %} @@ -117,7 +117,7 @@

{% endif %} {% endfor %} - {% elif event.type == EventType.Query %} + {% elif event.query %} 🌐 {% set query_dict = {event.query.request.method + " " + event.query.request.url_hostname + " " + str(event.query.response.status_code): event.query} %} @@ -132,20 +132,20 @@

{% endif %} {% endfor %} - {% elif event.type == EventType.Note %} + {% elif event.note %} 📓 {{ event.note.key }}: {{ event.note.message }} {% else %} - ???Render error: unknown EventType '{{ event.type }}' + ???Render error: unknown event type {% endif %} {% set first_row.epoch = False %} {% set first_row.step = False %} {% endfor %} {% endfor %} - {% elif epoch.type == EpochType.Events %} + {% elif epoch.events %} {% for event in epoch.events %} {% if first_row.epoch %} diff --git a/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html b/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html index 498d6964f6..db76d9fde8 100644 --- a/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html +++ b/monitoring/uss_qualifier/reports/templates/tested_requirements/participant_tested_requirements.html @@ -1,79 +1,6 @@ - + {% include "tested_requirements/style.html" %}
@@ -124,28 +51,35 @@

Test run

Environment identifier TE-{{ test_run.environment[0:7] }} + + Requirements identifier + RC-{{ req_set_name }} + Requirement verification status - - {% if overall_status == ParticipantVerificationStatus.Pass %} - Pass - {% elif overall_status == ParticipantVerificationStatus.PassWithFindings %} - Pass (with findings) - {% elif overall_status == ParticipantVerificationStatus.Fail %} - Fail - {% elif overall_status == ParticipantVerificationStatus.Incomplete %} - Not fully verified - {% else %} - ??? - {% endif %} - + {{ overall_status.get_text() }}
Artifact generated by {{ codebase_version }} with {{ config_source }}
+{% if overall_status != ParticipantVerificationStatus.Pass %} +
+

Notable requirements

+
    + {% for outcome_class in ("fail_result", "not_tested", "findings_result") %} + {% for package in breakdown.packages %} + {% for requirement in package.requirements %} + {% if requirement.classname == outcome_class %} +
  • {{ package.id + '.' + requirement.id }}
  • + {% endif %} + {% endfor %} + {% endfor %} + {% endfor %} +
+
+{% endif %}

Tested requirements

-
Requirements: {{ req_set_name }}
@@ -170,7 +104,7 @@

Tested requirements

{% for test_step in test_case.steps %} {% set first_row.step = True %} {% for check in test_step.checks %} - + {% if first_row.package %} {% endif %} @@ -211,8 +145,8 @@

Tested requirements

{% else %} {{ check.name }} {% endif %} - {% if check.successes + check.failures > 1 %} - ({{ check.successes + check.failures }}x) + {% if check.passes + check.failures > 1 %} + ({{ check.passes + check.failures }}x) {% endif %} @@ -227,7 +161,7 @@

Tested requirements

{% endfor %} {% endfor %} {% else %} - + {% if first_row.package %} {% endif %} diff --git a/monitoring/uss_qualifier/reports/templates/tested_requirements/style.html b/monitoring/uss_qualifier/reports/templates/tested_requirements/style.html new file mode 100644 index 0000000000..0b074eeefa --- /dev/null +++ b/monitoring/uss_qualifier/reports/templates/tested_requirements/style.html @@ -0,0 +1,77 @@ + diff --git a/monitoring/uss_qualifier/reports/templates/tested_requirements/test_run_report.html b/monitoring/uss_qualifier/reports/templates/tested_requirements/test_run_report.html index bad896ed36..f53c11a3c0 100644 --- a/monitoring/uss_qualifier/reports/templates/tested_requirements/test_run_report.html +++ b/monitoring/uss_qualifier/reports/templates/tested_requirements/test_run_report.html @@ -1,12 +1,28 @@ + + {% include "tested_requirements/style.html" %} +

Participants

-
    +
Package
{{ package.name }}
{{ package.name }}
+ + + + + {% for participant_id in participant_ids %} -
  • {{ participant_id }}
  • + + + + + {% endfor %} - +
    ParticipantVerification statusSystem version
    {{ participant_id }} + {{ verification_report.participant_verifications[participant_id].status.get_text() }} + {{ verification_report.participant_verifications[participant_id].system_version or "" }}
    +
    +

    Programmatic verification statuses

    status.json
    diff --git a/monitoring/uss_qualifier/reports/templates/timing/cannot_generate.html b/monitoring/uss_qualifier/reports/templates/timing/cannot_generate.html new file mode 100644 index 0000000000..d3e177be03 --- /dev/null +++ b/monitoring/uss_qualifier/reports/templates/timing/cannot_generate.html @@ -0,0 +1,5 @@ + + +Timing report could be not generated because {{ non_generation_reason }}. + + diff --git a/monitoring/uss_qualifier/reports/templates/timing/report.html b/monitoring/uss_qualifier/reports/templates/timing/report.html new file mode 100644 index 0000000000..6021c3d64f --- /dev/null +++ b/monitoring/uss_qualifier/reports/templates/timing/report.html @@ -0,0 +1,212 @@ + + + + + +
    +

    Timing

    +

    Test run

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Test characteristicValue
    Test run identifierTR-{{ test_run.test_run_id[0:7] }}
    Start time{{ test_run.start_time }}
    End time{{ test_run.end_time }}
    Total duration{{ format_time(duration) }}
    Test baseline identifierTB-{{ test_run.baseline[0:7] }}
    Environment identifierTE-{{ test_run.environment[0:7] }}
    Codebase version{{ report.codebase_version }}
    Commit hash{{ report.commit_hash }}
    +
    Artifact generated by {{ codebase_version }}
    +
    +
    +
    +

    Breakdown by scenario type

    + + + + + + + + + {% for row in scenario_breakdown %} + + + + + + + + {% endfor %} +
    ScenarioTotal timeAverage timeQuery time
    (fraction of scenario)
    Delay time
    (fraction of scenario)
    {{ row.scenario }}{{ format_time(row.total_time) }}{{ format_time(row.average_time) }}{{ round(row.query_fraction * 100, 1) }}% + {% if row.query_fraction > 1 %} (concurrent queries){% endif %} + {{ round(row.delay_fraction * 100, 1) }}%
    +
    +
    +

    Breakdown by query

    +
    + Note that differing performance between servers may be due to differing queries sent to the servers (and/or conditions at the times of those queries) in addition to, or instead of, fundamental server performance differences. +
    + + + + + + + + + {% for server in servers %} + + {% endfor %} + + {% for row in query_breakdown %} + + + + + {% set max_seconds = row.max_average_server_time().total_seconds() %} + {% for server in servers %} + {% if server in row.times_per_server %} + {% set server_time = sum(row.times_per_server[server]) / len(row.times_per_server[server]) %} + + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} +
    Query typeTotal timeAverage time
    Overall{{ server }}
    {{ row.query_type }}{{ format_time(row.total_time) }}{{ format_time(row.average_time) }} + {{ format_time(server_time) }}
    n={{ len(row.times_per_server[server]) }} +
    +
    +
    +

    Breakdown by intentional delay

    + + + + + + + + {% for row in delays_breakdown %} + + + + + + + {% endfor %} +
    ScenarioReasonTotal timeAverage time
    {{ row.scenario_type }}{{ row.reason }}{{ format_time(row.total_time) }}{{ format_time(row.average_time) }}
    +
    +
    + + diff --git a/monitoring/uss_qualifier/reports/tested_requirements/README.md b/monitoring/uss_qualifier/reports/tested_requirements/README.md index bf78a9697b..b4cf6fc29a 100644 --- a/monitoring/uss_qualifier/reports/tested_requirements/README.md +++ b/monitoring/uss_qualifier/reports/tested_requirements/README.md @@ -16,25 +16,151 @@ list of relevant requirements included on this page can either be set explicitly in the artifact configuration, or else it defaults to every requirement that the set of scenarios run may have been capable of measuring. -### Summaries +### Requirement summaries -Each requirement is summarized for a participant in the following way: +Each requirement is summarized (see [`TestedRequirement.status`](./data_types.py)) for a participant in the following way: -* If any test check relevant to that requirement failed, then the compliance - summary for that requirement is indicated as "Fail". -* Otherwise, if at least one test check measured compliance with the - requirement did not detect non-compliance, then the compliance summary for - that requirement is indicated as "Pass". -* If no test checks measuring compliance with the requirement were performed - for the participant, then the compliance summary for that requirement is - indicated as "Not tested" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Requirement statusMeaningCriteria for checks associated with the requirement
    PassNot testedFail
    Medium+ severity, not in acceptable_​findingsLow severity, not in acceptable_​findingsIn acceptable_​findings
    FailThe participant is likely to be non-compliant with one or more requirementsAny numberAny numberAt least oneAny numberAny number
    FindingsThe participant was not detected as non-compliant to any requirement, but there are important findings and no fully-successful checksNoneAny numberNoneAt least oneAny number
    Pass (with findings)The participant was not detected as non-compliant to any requirement, and some checks validate compliance to the requirement, but there are important findingsAt least oneAny numberNoneAt least oneAny number
    PassAt least one test check measuring compliance with the requirement did not detect non-compliance, and no non-compliance was detectedAt least oneAny numberNoneNoneAny number
    Not testedNo checks associated with the requirement produced positive or negative resultsNoneAny numberNoneNoneAny number
    + +### Top-level verification status The overall "Requirement verification status" for the participant is -summarized in the following way: - -* If any relevant requirement for the participant indicates "Fail", then the - overall status is indicated as "Fail". -* Otherwise, if any relevant requirement for the participant indicates "Not - tested", then the overall status is indicated as "Not fully verified". -* If all relevant requirements for the participant indicate "Pass", then the - overall status is indicated as "Pass". +summarized in the following way (see [`compute_overall_status`](./summaries.py)): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Verification status (top-level)MeaningCriteria for requirements included in the artifact
    FailFindingsPass (with findings)PassNot tested
    FailThe participant is likely to be non-compliant with one or more relevant requirementsAt least oneAny numberAny numberAny numberAny number
    PassEvery requirement considered relevant for the artifact has at least one test check measuring the participant's compliance with the requirement, and no relevant non-compliance was detected, and there are no relevant Low-severity findingsNoneNoneNoneAt least oneNone
    Pass (with findings)Every requirement considered relevant for the artifact has at least one test check measuring the participant's compliance with the requirement, and no relevant non-compliance was detected, but there are important findings regarding one or more relevant requirementsNoneNoneAt least oneAny numberNone
    Not fully verifiedThe participant was not detected to be non-compliant with any requirement, but tests could not be successfully conducted to verify compliance to all relevant requirementsNoneAny numberAny numberAny numberAt least one
    + +## Troubleshooting "Fail" for a set of tested requirements + +If the "Requirement verification status" for a participant is "Fail" (see above for how this is determined), begin troubleshooting by locating the requirement row in the "Tested requirements" table. Identify the exact checks failed and see if the names alone reveal the issue. If not, click on the check name to navigate to the documentation for that check and see if the reason for failure can be determined from that documentation. If not, note the name of the test scenario in which the failed check appears, then find that test scenario run in the [sequence view artifact](../sequence_view) and follow [the instructions to troubleshoot a failed check from the sequence view artifact](../sequence_view/README.md#troubleshooting-a-failed-check). + +## Troubleshooting "Not fully verified" for a set of tested requirements + +If the "Requirement verification status" for a participant is "Not fully verified" (see above for how this is determined), begin troubleshooting by locating the requirement row for a requirement that was not fully tested in the "Tested requirements" table. The Requirement cell will be gray rather than green for such requirements. + +If that row does not contain any Scenario entries, the test configuration is not capable of verifying compliance to the requirement for any participant and the test designer should be consulted. The test designer may need to request or contribute a new uss_qualifier feature to verify compliance to the requirement. + +If the requirement row contains one or more Scenario entries, these are the test scenarios that are potentially capable of verifying compliance to the requirement. But, for some reason, the participant's compliance to the requirement was not verified by any of these test scenarios. For each test scenario capable of verifying compliance to the requirement, review the documentation (linked via the test scenario name) to determine if verification is expected for the participant (for instance, verification may not be expected if verification requires implementing a particular feature and the participant has chosen not to implement that feature). If verification is expected, consult the index page of the [sequence view artifact](../sequence_view) and find all runs of the test scenario in question. For each run of the test scenario in question, review the test scenario run page of the sequence view artifact (linked from the test scenario name on the index page). If the participant does not appear in any of the roles of any of the test scenario runs, the problem may be that the test designer has not included the participant in the relevant environmental resource (e.g., participant has not been added to the list of USSs implementing feature X). If the participant is involved in one or more runs of the test scenario, trace the events performed to determine why the check that should have verified requirement compliance was not performed for the participant (based on test scenario documentation) -- this is usually because the participant was found to not support an optional capability needed to verify compliance to the requirement. + +After performing the above procedure, if the reason for non-verification still cannot be determined, file [a test scenario bug Issue](https://github.com/interuss/monitoring/issues/new?template=bug_test_scenario.md) with InterUSS. Be sure to attach the entire sequence view artifact and tested requirements artifact when possible, ideally the full zip of artifacts produced from the test run. + +## Purple cells + +If a test designer explicitly indicates one or more checks in `acceptable_findings`, then failure of this type of check should not affect the status indicated for associated requirements and the test overall. When the outcome of such a check is ignored in this way, that behavior will be annotated visually with a purple background. That is, instead of a red background for the failed check, it will be shown with a purple background to indicate that finding has been considered acceptable according to explicit test designer instructions. diff --git a/monitoring/uss_qualifier/reports/tested_requirements/breakdown.py b/monitoring/uss_qualifier/reports/tested_requirements/breakdown.py index 4a7a359405..4ff825afdc 100644 --- a/monitoring/uss_qualifier/reports/tested_requirements/breakdown.py +++ b/monitoring/uss_qualifier/reports/tested_requirements/breakdown.py @@ -10,11 +10,15 @@ list_potential_actions_for_action_generator_definition, ) from monitoring.uss_qualifier.common_data_definitions import Severity -from monitoring.uss_qualifier.configurations.configuration import ParticipantID +from monitoring.uss_qualifier.configurations.configuration import ( + FullyQualifiedCheck, + ParticipantID, +) from monitoring.uss_qualifier.fileio import load_dict_with_references from monitoring.uss_qualifier.reports.report import ( FailedCheck, PassedCheck, + SkippedActionReport, TestCaseReport, TestRunReport, TestScenarioReport, @@ -34,16 +38,25 @@ from monitoring.uss_qualifier.requirements.definitions import RequirementID from monitoring.uss_qualifier.scenarios.definitions import TestScenarioTypeName from monitoring.uss_qualifier.scenarios.documentation.parsing import get_documentation -from monitoring.uss_qualifier.scenarios.scenario import get_scenario_type_by_name +from monitoring.uss_qualifier.scenarios.scenario import ( + are_scenario_types_equal, + fully_qualified_check_in_collection, + get_scenario_type_by_name, +) from monitoring.uss_qualifier.suites.definitions import ( - ActionType, TestSuiteActionDeclaration, TestSuiteDefinition, ) +from monitoring.uss_qualifier.suites.suite import TEST_RUN_TIMEOUT_SKIP_REASON + +REQ_RUN_TO_COMPLETION = RequirementID( + "interuss.automated_testing.execution.RunToCompletion" +) def make_breakdown( report: TestRunReport, + acceptable_findings: list[FullyQualifiedCheck], participant_reqs: set[RequirementID] | None, participant_ids: Iterable[ParticipantID], ) -> TestedBreakdown: @@ -51,6 +64,7 @@ def make_breakdown( Args: report: Report to break down. + acceptable_findings: Checks where failure is acceptable, according to the configuration. participant_reqs: Set of requirements to report for these participants. If None, defaults to everything. participant_ids: IDs of participants for which the breakdown is being computed. @@ -58,13 +72,56 @@ def make_breakdown( """ participant_breakdown = TestedBreakdown(packages=[]) _populate_breakdown_with_action_report( - participant_breakdown, report.report, participant_ids, participant_reqs + participant_breakdown, + report.report, + acceptable_findings, + participant_ids, + participant_reqs, ) + assert report.configuration.v1 + assert report.configuration.v1.test_run _populate_breakdown_with_action_declaration( - participant_breakdown, report.configuration.v1.test_run.action, participant_reqs + participant_breakdown, + report.configuration.v1.test_run.action, + acceptable_findings, + participant_reqs, ) if participant_reqs is not None: _populate_breakdown_with_req_set(participant_breakdown, participant_reqs) + if REQ_RUN_TO_COMPLETION in participant_reqs: + # Add a passing check to REQ_RUN_TO_COMPLETION if nothing caused it to fail + tested_requirement = _tested_requirement_for( + REQ_RUN_TO_COMPLETION, participant_breakdown + ) + if not tested_requirement.scenarios: + tested_requirement.scenarios.append( + TestedScenario( + type="uss_qualifier.execution", + name="N/A", + url="", + cases=[ + TestedCase( + name="N/A", + url="", + steps=[ + TestedStep( + name="N/A", + url="", + checks=[ + TestedCheck( + name="Test run completed normally", + url="", + has_todo=False, + is_finding_acceptable=False, + passes=1, + ) + ], + ) + ], + ) + ], + ) + ) sort_breakdown(participant_breakdown) return participant_breakdown @@ -73,58 +130,115 @@ def _populate_breakdown_with_req_set( breakdown: TestedBreakdown, req_set: set[RequirementID] ) -> None: for req_id in req_set: - package_id = req_id.package() - matches = [p for p in breakdown.packages if p.id == package_id] - if matches: - tested_package = matches[0] - else: - url = repo_url_of(package_id.md_file_path()) - tested_package = TestedPackage( - id=package_id, url=url, name=package_id, requirements=[] - ) - breakdown.packages.append(tested_package) - - short_req_id = req_id.split(".")[-1] - matches = [r for r in tested_package.requirements if r.id == short_req_id] - if matches: - tested_requirement = matches[0] - else: - tested_requirement = TestedRequirement(id=short_req_id, scenarios=[]) - tested_package.requirements.append(tested_requirement) + _tested_requirement_for(req_id, breakdown) def _populate_breakdown_with_action_report( breakdown: TestedBreakdown, action: TestSuiteActionReport, + acceptable_findings: list[FullyQualifiedCheck], participant_ids: Iterable[ParticipantID], req_set: set[RequirementID] | None, ) -> None: - test_suite, test_scenario, action_generator = action.get_applicable_report() - if test_scenario: + if "test_scenario" in action and action.test_scenario: return _populate_breakdown_with_scenario_report( - breakdown, action.test_scenario, participant_ids, req_set + breakdown, + action.test_scenario, + acceptable_findings, + participant_ids, + req_set, ) - elif test_suite: + elif "test_suite" in action and action.test_suite: for subaction in action.test_suite.actions: _populate_breakdown_with_action_report( - breakdown, subaction, participant_ids, req_set + breakdown, subaction, acceptable_findings, participant_ids, req_set ) - elif action_generator: + elif "action_generator" in action and action.action_generator: for subaction in action.action_generator.actions: _populate_breakdown_with_action_report( - breakdown, subaction, participant_ids, req_set + breakdown, subaction, acceptable_findings, participant_ids, req_set + ) + elif "skipped_action" in action and action.skipped_action: + if ( + req_set is not None + and REQ_RUN_TO_COMPLETION in req_set + and action.skipped_action.reason == TEST_RUN_TIMEOUT_SKIP_REASON + ): + _populate_breakdown_with_timeout_skip(breakdown, action.skipped_action) + else: + raise ValueError( + "Unrecognized or unspecified oneof option in TestSuiteActionReport" + ) + + +def _populate_breakdown_with_timeout_skip( + breakdown: TestedBreakdown, skipped_action: SkippedActionReport +) -> None: + declaration = skipped_action.declaration + if "test_scenario" in declaration and declaration.test_scenario: + doc = get_documentation( + get_scenario_type_by_name(declaration.test_scenario.scenario_type) + ) + default_scenario = TestedScenario( + type=declaration.test_scenario.scenario_type, + name=doc.name, + documentation_url=doc.url, + cases=[], + ) + elif "test_suite" in declaration and declaration.test_suite: + type_name = declaration.test_suite.type_name + default_scenario = TestedScenario( + name=f"(Test suite) {type_name}", type=type_name, url="", cases=[] + ) + elif "action_generator" in declaration and declaration.action_generator: + type_name = declaration.action_generator.generator_type + default_scenario = TestedScenario( + name=f"(Action generator) {type_name}", type=type_name, url="", cases=[] + ) + else: + raise ValueError( + "Unrecognized or unspecified oneof option in TestSuiteActionDeclaration" + ) + tested_requirement = _tested_requirement_for(REQ_RUN_TO_COMPLETION, breakdown) + tested_scenario = _tested_scenario_for(default_scenario, tested_requirement) + # Assume each TestedScenario for the TestedRequirement for this requirement should only ever have 1 case with 1 step with 1 check + if not tested_scenario.cases: + tested_scenario.cases.append( + TestedCase( + name="N/A", + url="", + steps=[ + TestedStep( + name="N/A", + url="", + checks=[ + TestedCheck( + name="Test run completed normally", + url="", + has_todo=False, + is_finding_acceptable=False, + failures=1, + ) + ], + ) + ], ) + ) else: - pass # Skipped action + if len(tested_scenario.cases) > 1: + raise ValueError( + f"TestedScenario {tested_scenario.name} ({tested_scenario.type}) for requirement {tested_requirement.id} was expected to only have one N/A case, but instead had {len(tested_scenario.cases)} cases: {', '.join(c.name for c in tested_scenario.cases)}" + ) + tested_scenario.cases[0].steps[0].checks[0].failures += 1 def _populate_breakdown_with_scenario_report( breakdown: TestedBreakdown, scenario_report: TestScenarioReport, + acceptable_findings: list[FullyQualifiedCheck], participant_ids: Iterable[ParticipantID], req_set: set[RequirementID] | None, ) -> None: - scenario_type_name = scenario_report.scenario_type steps: list[tuple[TestCaseReport | None, TestStepReport]] = [] for case in scenario_report.cases: for step in case.steps: @@ -137,131 +251,216 @@ def _populate_breakdown_with_scenario_report( if not any(pid in check.participants for pid in participant_ids): continue for req_id in check.requirements: - if req_set is not None and req_id not in req_set: - continue - package_id = req_id.package() - package_name = "
    .".join(package_id.split(".")) - matches = [p for p in breakdown.packages if p.id == package_id] - if matches: - tested_package = matches[0] - else: - # TODO: Improve name of package by using title of page - url = repo_url_of(package_id.md_file_path()) - tested_package = TestedPackage( - id=package_id, url=url, name=package_name, requirements=[] - ) - breakdown.packages.append(tested_package) - - short_req_id = req_id.split(".")[-1] - matches = [ - r for r in tested_package.requirements if r.id == short_req_id - ] - if matches: - tested_requirement = matches[0] - else: - tested_requirement = TestedRequirement( - id=short_req_id, scenarios=[] - ) - tested_package.requirements.append(tested_requirement) - - matches = [ - s - for s in tested_requirement.scenarios - if s.type == scenario_type_name - ] - if matches: - tested_scenario = matches[0] - else: - tested_scenario = TestedScenario( - type=scenario_type_name, - name=scenario_report.name, - url=scenario_report.documentation_url, - cases=[], + if req_set is None or req_id in req_set: + _add_check_to_breakdown_for_req( + req_id, + scenario_report, + case, + step, + check, + breakdown, + acceptable_findings, ) - tested_requirement.scenarios.append(tested_scenario) - - if case: - case_name = case.name - case_url = case.documentation_url - else: - case_name = "Cleanup" - case_url = step.documentation_url - matches = [c for c in tested_scenario.cases if c.name == case_name] - if matches: - tested_case = matches[0] - else: - tested_case = TestedCase(name=case_name, url=case_url, steps=[]) - tested_scenario.cases.append(tested_case) - - matches = [s for s in tested_case.steps if s.name == step.name] - if matches: - tested_step = matches[0] - else: - tested_step = TestedStep( - name=step.name, url=step.documentation_url, checks=[] - ) - tested_case.steps.append(tested_step) - - matches = [c for c in tested_step.checks if c.name == check.name] - if matches: - tested_check = matches[0] - else: - tested_check = TestedCheck( - name=check.name, url="", has_todo=False - ) # TODO: Consider populating has_todo with documentation instead - if isinstance(check, FailedCheck): - tested_check.url = check.documentation_url - tested_step.checks.append(tested_check) - if isinstance(check, PassedCheck): - tested_check.successes += 1 - elif isinstance(check, FailedCheck): - if check.severity == Severity.Low: - tested_check.findings += 1 - else: - tested_check.failures += 1 - else: - raise ValueError("Check is neither PassedCheck nor FailedCheck") + if ( + req_set is not None + and REQ_RUN_TO_COMPLETION in req_set + and "severity" in check + and check.severity == Severity.Critical + ): + _add_check_to_breakdown_for_req( + REQ_RUN_TO_COMPLETION, + scenario_report, + case, + step, + check, + breakdown, + acceptable_findings, + ) + + +def _tested_requirement_for( + req_id: RequirementID, breakdown: TestedBreakdown +) -> TestedRequirement: + """Retrieves the TestedRequirement for the specified ID in the breakdown, creating an empty one if necessary.""" + package_id = req_id.package() + package_name = "
    .".join(package_id.split(".")) + matches = [p for p in breakdown.packages if p.id == package_id] + if matches: + tested_package = matches[0] + else: + # TODO: Improve name of package by using title of page + url = repo_url_of(package_id.md_file_path()) + tested_package = TestedPackage( + id=package_id, url=url, name=package_name, requirements=[] + ) + breakdown.packages.append(tested_package) + + short_req_id = req_id.split(".")[-1] + matches = [r for r in tested_package.requirements if r.id == short_req_id] + if matches: + tested_requirement = matches[0] + else: + tested_requirement = TestedRequirement(id=short_req_id, scenarios=[]) + tested_package.requirements.append(tested_requirement) + + return tested_requirement + + +class ScenarioInfo(ImplicitDict): + """Limited subset of a full TestScenarioReport that still contains enough information to produce a TestedScenario.""" + + name: str + scenario_type: TestScenarioTypeName + documentation_url: str + + +def _same_tested_scenario_types(s1: TestedScenario, s2: TestedScenario) -> bool: + if s1.type.startswith("scenarios.") and s2.type.startswith("scenarios."): + return are_scenario_types_equal(s1.type, s2.type) + else: + return s1.type == s2.type + + +def _tested_scenario_for( + default_scenario: TestedScenario, tested_requirement: TestedRequirement +) -> TestedScenario: + """Retrieves the TestedScenario for the specified scenario within the specified requirement, creating an empty one if necessary. + + Args: + * default_scenario: The TestedScenario information to use if no pre-existing TestedScenario is found. + * tested_requirement: The requirement breakdown level for which the scenario is being found. + """ + matches = [ + s + for s in tested_requirement.scenarios + if _same_tested_scenario_types(s, default_scenario) + ] + if matches: + tested_scenario = matches[0] + else: + tested_scenario = TestedScenario( + type=default_scenario.type, + name=default_scenario.name, + url=default_scenario.url, + cases=[], + ) + tested_requirement.scenarios.append(tested_scenario) + + return tested_scenario + + +def _add_check_to_breakdown_for_req( + req_id: RequirementID, + scenario_report: TestScenarioReport, + case: TestCaseReport | None, + step: TestStepReport, + check: PassedCheck | FailedCheck, + breakdown: TestedBreakdown, + acceptable_findings: list[FullyQualifiedCheck], +): + tested_requirement = _tested_requirement_for(req_id, breakdown) + tested_scenario = _tested_scenario_for( + TestedScenario.from_scenario_report(scenario_report), tested_requirement + ) + + if case: + case_name = case.name + case_url = case.documentation_url + else: + case_name = "Cleanup" + case_url = step.documentation_url + matches = [c for c in tested_scenario.cases if c.name == case_name] + if matches: + tested_case = matches[0] + else: + tested_case = TestedCase(name=case_name, url=case_url, steps=[]) + tested_scenario.cases.append(tested_case) + + matches = [s for s in tested_case.steps if s.name == step.name] + if matches: + tested_step = matches[0] + else: + tested_step = TestedStep(name=step.name, url=step.documentation_url, checks=[]) + tested_case.steps.append(tested_step) + + matches = [c for c in tested_step.checks if c.name == check.name] + if matches: + tested_check = matches[0] + else: + current_check = FullyQualifiedCheck( + scenario_type=scenario_report.scenario_type, + test_case_name=case_name, + test_step_name=step.name, + check_name=check.name, + ) + tested_check = TestedCheck( + name=check.name, + url="", + has_todo=False, + is_finding_acceptable=fully_qualified_check_in_collection( + current_check, acceptable_findings + ), + ) # TODO: Consider populating has_todo with documentation instead + if isinstance(check, FailedCheck): + tested_check.url = check.documentation_url + tested_step.checks.append(tested_check) + if isinstance(check, PassedCheck): + tested_check.passes += 1 + elif isinstance(check, FailedCheck): + if check.severity == Severity.Low: + tested_check.findings += 1 + else: + tested_check.failures += 1 + else: + raise ValueError("Check is neither PassedCheck nor FailedCheck") def _populate_breakdown_with_action_declaration( breakdown: TestedBreakdown, action: TestSuiteActionDeclaration | PotentialGeneratedAction, + acceptable_findings: list[FullyQualifiedCheck], req_set: set[RequirementID] | None, ) -> None: - action_type = action.get_action_type() - if action_type == ActionType.TestScenario: + if "test_scenario" in action and action.test_scenario: _populate_breakdown_with_scenario( - breakdown, action.test_scenario.scenario_type, req_set + breakdown, action.test_scenario.scenario_type, acceptable_findings, req_set ) - elif action_type == ActionType.TestSuite: + elif "test_suite" in action and action.test_suite: if "suite_type" in action.test_suite and action.test_suite.suite_type: suite_def: TestSuiteDefinition = ImplicitDict.parse( load_dict_with_references(action.test_suite.suite_type), TestSuiteDefinition, ) for a in suite_def.actions: - _populate_breakdown_with_action_declaration(breakdown, a, req_set) + _populate_breakdown_with_action_declaration( + breakdown, a, acceptable_findings, req_set + ) elif ( "suite_definition" in action.test_suite and action.test_suite.suite_definition ): for a in action.test_suite.suite_definition.actions: - _populate_breakdown_with_action_declaration(breakdown, a, req_set) + _populate_breakdown_with_action_declaration( + breakdown, a, acceptable_findings, req_set + ) else: raise ValueError("Test suite action missing suite type or definition") - elif action_type == ActionType.ActionGenerator: + elif "action_generator" in action and action.action_generator: potential_actions = list_potential_actions_for_action_generator_definition( action.action_generator ) for a in potential_actions: - _populate_breakdown_with_action_declaration(breakdown, a, req_set) + _populate_breakdown_with_action_declaration( + breakdown, a, acceptable_findings, req_set + ) else: - raise NotImplementedError(f"Unsupported test suite action type: {action_type}") + raise action.invalid_type_error def _populate_breakdown_with_scenario( breakdown: TestedBreakdown, scenario_type_name: TestScenarioTypeName, + acceptable_findings: list[FullyQualifiedCheck], req_set: set[RequirementID] | None, ) -> None: scenario_type = get_scenario_type_by_name(scenario_type_name) @@ -272,46 +471,16 @@ def _populate_breakdown_with_scenario( for req_id in check.applicable_requirements: if req_set is not None and req_id not in req_set: continue - package_id = req_id.package() - package_name = "
    .".join(package_id.split(".")) - matches = [p for p in breakdown.packages if p.id == package_id] - if matches: - tested_package = matches[0] - else: - # TODO: Improve name of package by using title of page - url = repo_url_of(package_id.md_file_path()) - tested_package = TestedPackage( - id=package_id, url=url, name=package_name, requirements=[] - ) - breakdown.packages.append(tested_package) - - short_req_id = req_id.split(".")[-1] - matches = [ - r for r in tested_package.requirements if r.id == short_req_id - ] - if matches: - tested_requirement = matches[0] - else: - tested_requirement = TestedRequirement( - id=short_req_id, scenarios=[] - ) - tested_package.requirements.append(tested_requirement) - - matches = [ - s - for s in tested_requirement.scenarios - if s.type == scenario_type_name - ] - if matches: - tested_scenario = matches[0] - else: - tested_scenario = TestedScenario( + tested_requirement = _tested_requirement_for(req_id, breakdown) + tested_scenario = _tested_scenario_for( + TestedScenario( type=scenario_type_name, name=scenario_doc.name, url=scenario_doc.url, cases=[], - ) - tested_requirement.scenarios.append(tested_scenario) + ), + tested_requirement, + ) matches = [c for c in tested_scenario.cases if c.name == case.name] if matches: @@ -333,9 +502,20 @@ def _populate_breakdown_with_scenario( if matches: tested_check = matches[0] else: + current_check = FullyQualifiedCheck( + scenario_type=scenario_type_name, + test_case_name=case.name, + test_step_name=step.name, + check_name=check.name, + ) tested_check = TestedCheck( - name=check.name, url=check.url, has_todo=check.has_todo + name=check.name, + url=check.url, + has_todo=check.has_todo, + is_finding_acceptable=fully_qualified_check_in_collection( + current_check, acceptable_findings + ), ) tested_step.checks.append(tested_check) - if not tested_check.url: + if not tested_check.url and check.url: tested_check.url = check.url diff --git a/monitoring/uss_qualifier/reports/tested_requirements/data_types.py b/monitoring/uss_qualifier/reports/tested_requirements/data_types.py index ac0c4a9739..cf214b99f6 100644 --- a/monitoring/uss_qualifier/reports/tested_requirements/data_types.py +++ b/monitoring/uss_qualifier/reports/tested_requirements/data_types.py @@ -1,15 +1,19 @@ -from enum import Enum +from __future__ import annotations -from implicitdict import ImplicitDict +from collections.abc import Iterable +from enum import StrEnum + +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier.configurations.configuration import ParticipantID +from monitoring.uss_qualifier.reports.report import TestScenarioReport from monitoring.uss_qualifier.requirements.definitions import PackageID -from monitoring.uss_qualifier.scenarios.definitions import TestScenarioTypeName PASS_CLASS = "pass_result" FINDINGS_CLASS = "findings_result" NOT_TESTED_CLASS = "not_tested" FAIL_CLASS = "fail_result" +ACCEPTED_FINDINGS_CLASS = "accepted_findings_result" HAS_TODO_CLASS = "has_todo" @@ -17,7 +21,8 @@ class TestedCheck(ImplicitDict): name: str url: str has_todo: bool - successes: int = 0 + is_finding_acceptable: bool + passes: int = 0 findings: int = 0 failures: int = 0 @@ -25,19 +30,19 @@ class TestedCheck(ImplicitDict): def result(self) -> str: if self.failures > 0: return "Fail" - if self.findings > 0 and self.successes == 0: + if self.findings > 0 and self.passes == 0: return "Findings" - if self.not_tested: - return "Not tested" - if self.findings > 0: + if self.findings == 0 and self.passes > 0: + return "Pass" + if self.findings > 0 and self.passes > 0: return "Pass (with findings)" - return "Pass" + return "Not tested" @property def check_classname(self) -> str: if self.failures > 0: - return FAIL_CLASS - if self.successes + self.failures == 0: + return ACCEPTED_FINDINGS_CLASS if self.is_finding_acceptable else FAIL_CLASS + if self.passes + self.failures == 0: if self.has_todo: return HAS_TODO_CLASS else: @@ -47,17 +52,21 @@ def check_classname(self) -> str: @property def result_classname(self) -> str: - if self.failures > 0: - return FAIL_CLASS - if self.successes + self.failures + self.findings == 0: - return NOT_TESTED_CLASS - if self.findings > 0: - return FINDINGS_CLASS - return PASS_CLASS - - @property - def not_tested(self) -> bool: - return self.successes + self.failures == 0 + if self.is_finding_acceptable: + if self.passes > 0: + return PASS_CLASS + elif self.failures > 0 or self.findings > 0: + return ACCEPTED_FINDINGS_CLASS + else: + return NOT_TESTED_CLASS + else: + if self.failures > 0: + return FAIL_CLASS + if self.passes + self.failures + self.findings == 0: + return NOT_TESTED_CLASS + if self.findings > 0: + return FINDINGS_CLASS + return PASS_CLASS class TestedStep(ImplicitDict): @@ -69,18 +78,6 @@ class TestedStep(ImplicitDict): def rows(self) -> int: return len(self.checks) - @property - def no_failures(self) -> bool: - return all(c.failures == 0 for c in self.checks) - - @property - def not_tested(self) -> bool: - return all(c.not_tested for c in self.checks) - - @property - def findings(self) -> bool: - return any(c.findings > 0 for c in self.checks) - class TestedCase(ImplicitDict): name: str @@ -91,21 +88,9 @@ class TestedCase(ImplicitDict): def rows(self) -> int: return sum(s.rows for s in self.steps) - @property - def no_failures(self) -> bool: - return all(s.no_failures for s in self.steps) - - @property - def not_tested(self) -> bool: - return all(s.not_tested for s in self.steps) - - @property - def findings(self) -> bool: - return any(s.findings for s in self.steps) - class TestedScenario(ImplicitDict): - type: TestScenarioTypeName + type: str name: str url: str cases: list[TestedCase] @@ -114,17 +99,22 @@ class TestedScenario(ImplicitDict): def rows(self) -> int: return sum(c.rows for c in self.cases) - @property - def no_failures(self) -> bool: - return all(c.no_failures for c in self.cases) + @staticmethod + def from_scenario_report(report: TestScenarioReport) -> TestedScenario: + return TestedScenario( + type=report.scenario_type, + name=report.name, + url=report.documentation_url, + cases=[], + ) - @property - def not_tested(self) -> bool: - return all(c.not_tested for c in self.cases) - @property - def findings(self) -> bool: - return any(c.findings for c in self.cases) +class TestedRequirementStatus(StrEnum): + Pass = "Pass" + PassWithFindings = "Pass (with findings)" + Findings = "Findings" + Fail = "Fail" + NotTested = "Not tested" class TestedRequirement(ImplicitDict): @@ -138,16 +128,38 @@ def rows(self) -> int: n = 1 return n + @property + def checks(self) -> Iterable[TestedCheck]: + for scenario in self.scenarios: + for case in scenario.cases: + for step in case.steps: + yield from step.checks + + @property + def status(self) -> TestedRequirementStatus: + if any((c.failures > 0 and not c.is_finding_acceptable) for c in self.checks): + return TestedRequirementStatus.Fail + if all(c.passes == 0 for c in self.checks) and any( + c.findings > 0 for c in self.checks + ): + return TestedRequirementStatus.Findings + if any(c.passes > 0 for c in self.checks) and any( + (c.findings > 0 and not c.is_finding_acceptable) for c in self.checks + ): + return TestedRequirementStatus.PassWithFindings + if any(c.passes > 0 for c in self.checks): + return TestedRequirementStatus.Pass + return TestedRequirementStatus.NotTested + @property def classname(self) -> str: - if not all(s.no_failures for s in self.scenarios): - return FAIL_CLASS - elif all(s.not_tested for s in self.scenarios): - return NOT_TESTED_CLASS - elif any(s.findings for s in self.scenarios): - return FINDINGS_CLASS - else: - return PASS_CLASS + return { + TestedRequirementStatus.Fail: FAIL_CLASS, + TestedRequirementStatus.Findings: FINDINGS_CLASS, + TestedRequirementStatus.PassWithFindings: FINDINGS_CLASS, + TestedRequirementStatus.Pass: PASS_CLASS, + TestedRequirementStatus.NotTested: NOT_TESTED_CLASS, + }[self.status] class TestedPackage(ImplicitDict): @@ -167,13 +179,13 @@ class TestedBreakdown(ImplicitDict): class TestRunInformation(ImplicitDict): test_run_id: str - start_time: str | None = None - end_time: str | None = None + start_time: Optional[str] = None + end_time: Optional[str] = None baseline: str environment: str -class ParticipantVerificationStatus(str, Enum): +class ParticipantVerificationStatus(StrEnum): Unknown = "Unknown" """Participant verification status is not known.""" @@ -186,7 +198,7 @@ class ParticipantVerificationStatus(str, Enum): Fail = "Fail" """Participant has failed to comply with one or more requirements.""" - Incomplete = "Incomplete" + NotFullyVerified = "NotFullyVerified" """Participant has not failed to comply with any requirements, but some identified requirements were not verified.""" def get_class(self) -> str: @@ -196,17 +208,29 @@ def get_class(self) -> str: return PASS_CLASS elif self == ParticipantVerificationStatus.Fail: return FAIL_CLASS - elif self == ParticipantVerificationStatus.Incomplete: + elif self == ParticipantVerificationStatus.NotFullyVerified: return NOT_TESTED_CLASS else: return "" + def get_text(self) -> str: + if self == ParticipantVerificationStatus.Pass: + return "Pass" + elif self == ParticipantVerificationStatus.PassWithFindings: + return "Pass (with findings)" + elif self == ParticipantVerificationStatus.Fail: + return "Fail" + elif self == ParticipantVerificationStatus.NotFullyVerified: + return "Not fully verified" + else: + return "???" + class ParticipantVerificationInfo(ImplicitDict): status: ParticipantVerificationStatus """Verification status of participant for the associated requirements set.""" - system_version: str | None = None + system_version: Optional[str] = None """The version of the participant's system that was tested, if this information was acquired during testing.""" @@ -217,6 +241,6 @@ class RequirementsVerificationReport(ImplicitDict): participant_verifications: dict[ParticipantID, ParticipantVerificationInfo] """Information regarding verification of compliance for each participant.""" - artifact_configuration: str | None + artifact_configuration: Optional[str] """Name of the tested requirements artifact configuration from the test run configuration, or "post-hoc" if the artifact configuration generating this verification report is not specified in the test run configuration.""" diff --git a/monitoring/uss_qualifier/reports/tested_requirements/generate.py b/monitoring/uss_qualifier/reports/tested_requirements/generate.py index b1ed75d1f3..d46f9f3ef9 100644 --- a/monitoring/uss_qualifier/reports/tested_requirements/generate.py +++ b/monitoring/uss_qualifier/reports/tested_requirements/generate.py @@ -35,8 +35,13 @@ def generate_tested_requirements( report: TestRunReport, config: TestedRequirementsConfiguration, output_path: str ) -> None: # Determine where the configuration to generate these tested requirements originated + assert report.configuration.v1 is not None artifacts = report.configuration.v1.artifacts - if "tested_requirements" in artifacts and artifacts.tested_requirements: + if ( + artifacts + and "tested_requirements" in artifacts + and artifacts.tested_requirements + ): i = ( artifacts.tested_requirements.index(config) if config in artifacts.tested_requirements @@ -88,11 +93,6 @@ def generate_tested_requirements( index_file = os.path.join(output_path, "index.html") all_participant_ids = list(report.report.participant_ids()) - reported_participant_ids = list(participant_req_collections) - reported_participant_ids.sort() - template = jinja_env.get_template("tested_requirements/test_run_report.html") - with open(index_file, "w") as f: - f.write(template.render(participant_ids=reported_participant_ids)) verification_report = RequirementsVerificationReport( test_run_information=test_run, @@ -111,7 +111,14 @@ def generate_tested_requirements( matching_participants = config.aggregate_participants[participant_id] else: matching_participants = [participant_id] - participant_breakdown = make_breakdown(report, req_set, matching_participants) + participant_breakdown = make_breakdown( + report, + list(config.acceptable_findings) + if "acceptable_findings" in config and config.acceptable_findings + else [], + req_set, + matching_participants, + ) overall_status = compute_overall_status(participant_breakdown) system_version = get_system_version( find_participant_system_versions(report.report, matching_participants) @@ -138,9 +145,25 @@ def generate_tested_requirements( ParticipantVerificationStatus=ParticipantVerificationStatus, codebase_version=get_code_version(), config_source=config_source, + anchor_name_of=_anchor_name_of, ) ) + reported_participant_ids = list(participant_req_collections) + reported_participant_ids.sort() + template = jinja_env.get_template("tested_requirements/test_run_report.html") + with open(index_file, "w") as f: + f.write( + template.render( + participant_ids=reported_participant_ids, + verification_report=verification_report, + ) + ) + status_file = os.path.join(output_path, "status.json") with open(status_file, "w") as f: json.dump(verification_report, f, indent=2) + + +def _anchor_name_of(fully_qualified_req_id: str) -> str: + return "req-" + fully_qualified_req_id.replace(".", "-") diff --git a/monitoring/uss_qualifier/reports/tested_requirements/summaries.py b/monitoring/uss_qualifier/reports/tested_requirements/summaries.py index 368f2aa9c0..f5688a0a96 100644 --- a/monitoring/uss_qualifier/reports/tested_requirements/summaries.py +++ b/monitoring/uss_qualifier/reports/tested_requirements/summaries.py @@ -40,7 +40,7 @@ def compute_overall_status( if req.classname == FAIL_CLASS: return ParticipantVerificationStatus.Fail elif req.classname == NOT_TESTED_CLASS: - overall_status = ParticipantVerificationStatus.Incomplete + overall_status = ParticipantVerificationStatus.NotFullyVerified elif req.classname == FINDINGS_CLASS: if overall_status == ParticipantVerificationStatus.Pass: overall_status = ParticipantVerificationStatus.PassWithFindings @@ -57,15 +57,14 @@ def find_participant_system_versions( ) -> list[str]: if isinstance(participant_ids, ParticipantID): participant_ids = [participant_ids] - test_suite, test_scenario, action_generator = report.get_applicable_report() result = [] - if test_suite: + if "test_suite" in report and report.test_suite: for action in report.test_suite.actions: result.extend(find_participant_system_versions(action, participant_ids)) - elif action_generator: + elif "action_generator" in report and report.action_generator: for action in report.action_generator.actions: result.extend(find_participant_system_versions(action, participant_ids)) - elif test_scenario: + elif "test_scenario" in report and report.test_scenario: if ( report.test_scenario.scenario_type in ( @@ -73,6 +72,7 @@ def find_participant_system_versions( "scenarios.versioning.GetSystemVersions", ) and "notes" in report.test_scenario + and report.test_scenario.notes is not None ): for participant_id in participant_ids: if participant_id in report.test_scenario.notes: diff --git a/monitoring/uss_qualifier/reports/timing/__init__.py b/monitoring/uss_qualifier/reports/timing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/uss_qualifier/reports/timing/generate.py b/monitoring/uss_qualifier/reports/timing/generate.py new file mode 100644 index 0000000000..712419a29c --- /dev/null +++ b/monitoring/uss_qualifier/reports/timing/generate.py @@ -0,0 +1,492 @@ +import os +from colorsys import hsv_to_rgb +from dataclasses import dataclass +from datetime import timedelta + +from monitoring.monitorlib.fetch import QueryType +from monitoring.monitorlib.inspection import import_submodules +from monitoring.monitorlib.versioning import get_code_version +from monitoring.uss_qualifier import action_generators, scenarios, suites +from monitoring.uss_qualifier.configurations.configuration import ( + TimingReportConfiguration, +) +from monitoring.uss_qualifier.reports import jinja_env +from monitoring.uss_qualifier.reports.report import TestRunReport, TestSuiteActionReport +from monitoring.uss_qualifier.reports.tested_requirements.summaries import ( + compute_test_run_information, +) + + +class _GenerationError(RuntimeError): + def __init__(self, msg: str): + super().__init__(msg) + + +def generate_timing_report( + report: TestRunReport, config: TimingReportConfiguration, output_path: str +) -> None: + import_submodules(scenarios) + import_submodules(suites) + import_submodules(action_generators) + + test_run = compute_test_run_information(report) + + os.makedirs(output_path, exist_ok=True) + index_file = os.path.join(output_path, "index.html") + + try: + if report.report.start_time is None: + raise _GenerationError("start_time is missing") + if report.report.end_time: + duration = ( + report.report.end_time.datetime - report.report.start_time.datetime + ) + elif report.report.latest_timestamp: + duration = ( + report.report.latest_timestamp - report.report.start_time.datetime + ) + else: + raise _GenerationError("end_time is missing") + + scenario_summaries, query_summaries, delays_summary = _summarize(report.report) + scenario_breakdown = _make_scenario_breakdown( + report, scenario_summaries, config + ) + query_breakdown = _make_query_breakdown(query_summaries, config) + delays_breakdown = _make_delays_breakdown(delays_summary) + servers = _list_servers(query_summaries) + except _GenerationError as e: + template = jinja_env.get_template("timing/cannot_generate.html") + with open(index_file, "w") as f: + f.write(template.render(non_generation_reason=str(e))) + return + + template = jinja_env.get_template("timing/report.html") + + with open(index_file, "w") as f: + f.write( + template.render( + report=report, + test_run=test_run, + duration=duration, + codebase_version=get_code_version(), + scenario_breakdown=scenario_breakdown, + max_total_seconds_scenario=max( + r.total_time.total_seconds() for r in scenario_breakdown + ), + max_average_seconds_scenario=max( + r.average_time.total_seconds() for r in scenario_breakdown + ), + servers=servers, + query_breakdown=query_breakdown, + max_total_seconds_query=max( + r.total_time.total_seconds() for r in query_breakdown + ) + if query_breakdown + else 0, + max_average_seconds_query=max( + r.average_time.total_seconds() for r in query_breakdown + ) + if query_breakdown + else 0, + delays_breakdown=delays_breakdown, + max_total_seconds_delay=max( + r.total_time.total_seconds() for r in delays_breakdown + ) + if delays_breakdown + else 0, + max_average_seconds_delay=max( + r.average_time.total_seconds() for r in delays_breakdown + ) + if delays_breakdown + else 0, + round=round, + len=len, + sum=_sum, + format_time=_format_time, + color_of=_color_of, + ) + ) + + +def _format_time(dt: timedelta) -> str: + hours = int(dt.seconds / 3600) + minutes = int((dt.seconds % 3600) / 60) + seconds = dt.total_seconds() % 60 + if dt > timedelta(hours=24): + return f"{dt.days}d{hours}h{minutes}m" + elif dt > timedelta(hours=1): + return f"{hours}h{minutes}m{seconds:.0f}s" + elif dt > timedelta(minutes=1): + return f"{minutes}m{seconds:.0f}s" + elif dt > timedelta(seconds=10): + return f"{seconds:.1f}s" + elif dt > timedelta(seconds=1): + return f"{seconds:.2f}s" + else: + return f"{seconds:.3f}s" + + +def _color_of(f: float) -> str: + hue = 0.58 * (1 - min(max(f, 0), 1)) + rgb = hsv_to_rgb(hue, 0.4, 1) + return f"{int(rgb[0] * 255.99):02x}{int(rgb[1] * 255.99):02x}{int(rgb[2] * 255.99):02x}" + + +def _sum(items): + """This sum function works with a list of timedeltas (or any type defining self-addition), unlike the built-in sum.""" + result = None + for v in items: + if result is None: + result = v + else: + result = result + v + return result + + +def _add_dicts(d1: dict, d2: dict, result: dict) -> None: + """Creates a merged dict with unique elements of d1 and d2, and the sum of values for keys shared by d1 and d2.""" + for k, v in d1.items(): + if k in d2: + result[k] = v + d2[k] + else: + result[k] = v + for k, v in d2.items(): + if k not in d1: + result[k] = v + + +@dataclass +class _ScenarioSummary: + instances: int + total_time: timedelta + query_time: timedelta + delay_time: timedelta + + def __add__(self, other): + if isinstance(other, _ScenarioSummary): + return _ScenarioSummary( + instances=self.instances + other.instances, + total_time=self.total_time + other.total_time, + query_time=self.query_time + other.query_time, + delay_time=self.delay_time + other.delay_time, + ) + else: + raise TypeError(f"Cannot add _ScenarioSummary to {type(other)}") + + +class _ScenarioSummaryCollection(dict[str, _ScenarioSummary]): + def __add__(self, other): + if isinstance(other, _ScenarioSummaryCollection): + result = _ScenarioSummaryCollection() + _add_dicts(self, other, result) + return result + else: + raise TypeError(f"Cannot add _ScenarioSummaryCollection to {type(other)}") + + +@dataclass +class _QuerySummary: + times_per_server: dict[str, list[timedelta]] + + def __add__(self, other): + if isinstance(other, _QuerySummary): + times_per_server = {} + _add_dicts(self.times_per_server, other.times_per_server, times_per_server) + return _QuerySummary( + times_per_server=times_per_server, + ) + else: + raise TypeError(f"Cannot add _QuerySummary to {type(other)}") + + +class _QuerySummaryCollection(dict[QueryType, _QuerySummary]): + def __add__(self, other): + if isinstance(other, _QuerySummaryCollection): + result = _QuerySummaryCollection() + _add_dicts(self, other, result) + return result + else: + raise TypeError(f"Cannot add _QuerySummaryCollection to {type(other)}") + + +DelayReason = tuple[str, str] +"""(scenario type, reason)""" + + +@dataclass +class _DelaysSummary: + times_per_reason: dict[DelayReason, list[timedelta]] + + def __add__(self, other): + if isinstance(other, _DelaysSummary): + times_per_reason = {} + _add_dicts(self.times_per_reason, other.times_per_reason, times_per_reason) + result = _DelaysSummary(times_per_reason=times_per_reason) + return result + else: + raise TypeError(f"Cannot add _DelaysSummary to {type(other)}") + + +def _summarize( + report: TestSuiteActionReport, +) -> tuple[_ScenarioSummaryCollection, _QuerySummaryCollection, _DelaysSummary]: + if "test_scenario" in report and report.test_scenario: + scenario = report.test_scenario + if "start_time" not in scenario or not scenario.start_time: + raise _GenerationError( + f"test scenario {scenario.scenario_type} is missing start_time" + ) + if "end_time" in scenario and scenario.end_time: + duration = scenario.end_time.datetime - scenario.start_time.datetime + elif scenario.latest_timestamp: + duration = scenario.latest_timestamp - scenario.start_time.datetime + else: + raise _GenerationError( + f"test scenario {scenario.scenario_type} started at {scenario.start_time} is missing end_time" + ) + query_time = timedelta(seconds=0) + delay_time = timedelta(seconds=0) + if "delays" in scenario and scenario.delays: + for delay in scenario.delays: + delay_time += delay.duration.timedelta + query_summaries = _QuerySummaryCollection() + delays_summary = _DelaysSummary(times_per_reason={}) + if "delays" in scenario and scenario.delays: + for delay in scenario.delays: + reason = (scenario.scenario_type, delay.reason) + if reason not in delays_summary.times_per_reason: + delays_summary.times_per_reason[reason] = [] + delays_summary.times_per_reason[reason].append(delay.duration.timedelta) + if "cases" in scenario and scenario.cases: + for case in scenario.cases: + if "steps" in case and case.steps: + for step in case.steps: + if "queries" in step and step.queries: + for query in step.queries: + dt = ( + query.response.reported.datetime + - query.request.timestamp + ) + if dt == 0: + dt = timedelta(seconds=0) + query_time += dt + query_type = query.query_type or QueryType.Unknown + if query_type not in query_summaries: + query_summaries[query_type] = _QuerySummary( + times_per_server={} + ) + server = query.request.url_hostname + if ( + server + not in query_summaries[query_type].times_per_server + ): + query_summaries[query_type].times_per_server[ + server + ] = [] + query_summaries[query_type].times_per_server[ + server + ].append(dt) + if "delays" in step and step.delays: + for delay in step.delays: + delay_time += delay.duration.timedelta + reason = (scenario.scenario_type, delay.reason) + if reason not in delays_summary.times_per_reason: + delays_summary.times_per_reason[reason] = [] + delays_summary.times_per_reason[reason].append( + delay.duration.timedelta + ) + scenario_summaries = _ScenarioSummaryCollection( + { + scenario.scenario_type: _ScenarioSummary( + instances=1, + total_time=duration, + query_time=query_time, + delay_time=delay_time, + ) + } + ) + return scenario_summaries, query_summaries, delays_summary + + elif "test_suite" in report and report.test_suite: + actions = report.test_suite.actions + elif "action_generator" in report and report.action_generator: + actions = report.action_generator.actions + elif "skipped_action" in report and report.skipped_action: + return ( + _ScenarioSummaryCollection(), + _QuerySummaryCollection(), + _DelaysSummary(times_per_reason={}), + ) + else: + raise _GenerationError( + f"test action started at {report.start_time} does not have action content" + ) + + summaries = _ScenarioSummaryCollection() + queries = _QuerySummaryCollection() + delays = _DelaysSummary(times_per_reason={}) + for action in actions: + ds, dq, dd = _summarize(action) + summaries = summaries + ds + queries = queries + dq + delays = delays + dd + return summaries, queries, delays + + +@dataclass +class _ScenarioBreakdownRow: + scenario: str + total_time: timedelta + average_time: timedelta + query_fraction: float + delay_fraction: float + + +def _truncate(items, value_of, fraction): + result = [] + items_list = list(items) + if not items_list: + return result + + total_value = value_of(items_list[0]) + for item in items_list[1:]: + total_value += value_of(item) + threshold_value = fraction * total_value + + result.append(items_list[0]) + running_value = value_of(items_list[0]) + if running_value < threshold_value: + for item in items_list[1:]: + running_value = running_value + value_of(item) + result.append(item) + if running_value >= threshold_value: + break + return result + + +def _make_scenario_breakdown( + report: TestRunReport, + summaries: _ScenarioSummaryCollection, + config: TimingReportConfiguration, +) -> list[_ScenarioBreakdownRow]: + rows = [] + for scenario_type, summary in summaries.items(): + rows.append( + _ScenarioBreakdownRow( + scenario=scenario_type, + total_time=summary.total_time, + average_time=summary.total_time / summary.instances, + query_fraction=summary.query_time.total_seconds() + / summary.total_time.total_seconds(), + delay_fraction=summary.delay_time.total_seconds() + / summary.total_time.total_seconds(), + ) + ) + if report.report.end_time and report.report.start_time: + scenario_time = _sum( + summary.total_time for summary in summaries.values() + ) or timedelta(seconds=0) + overhead = ( + report.report.end_time.datetime - report.report.start_time.datetime + ) - scenario_time + rows.append( + _ScenarioBreakdownRow( + scenario="Non-scenario overhead", + total_time=overhead, + average_time=overhead, + query_fraction=0, + delay_fraction=0, + ) + ) + rows.sort(key=lambda row: row.total_time, reverse=True) + rows = _truncate( + rows, lambda row: row.total_time, config.percentage_of_time_to_break_down / 100 + ) + return rows + + +@dataclass +class _QueryBreakdownRow: + query_type: QueryType + times_per_server: dict[str, list[timedelta]] + + @property + def total_time(self) -> timedelta: + total = timedelta(seconds=0) + for dts in self.times_per_server.values(): + server_dts = _sum(dts) or timedelta(seconds=0) + total += server_dts + return total + + @property + def average_time(self) -> timedelta: + total = timedelta(seconds=0) + n = 0 + for dts in self.times_per_server.values(): + server_dts = _sum(dts) or timedelta(seconds=0) + total += server_dts + n += len(dts) + return total / n + + def max_average_server_time(self) -> timedelta | None: + if not self.times_per_server: + return None + return max( + (_sum(values) or timedelta(seconds=0)) / len(values) + for values in self.times_per_server.values() + ) + + +def _make_query_breakdown( + summaries: _QuerySummaryCollection, config: TimingReportConfiguration +) -> list[_QueryBreakdownRow]: + rows = [] + for query_type, summary in summaries.items(): + rows.append( + _QueryBreakdownRow( + query_type=query_type, times_per_server=summary.times_per_server + ) + ) + rows.sort(key=lambda row: row.total_time, reverse=True) + rows = _truncate( + rows, lambda row: row.total_time, config.percentage_of_time_to_break_down / 100 + ) + return rows + + +def _list_servers(summaries: _QuerySummaryCollection) -> list[str]: + server_set = set() + for summary in summaries.values(): + for server in summary.times_per_server.keys(): + server_set.add(server) + servers = list(server_set) + servers.sort() + return servers + + +@dataclass +class _DelaysBreakdownRow: + scenario_type: str + reason: str + times: list[timedelta] + + @property + def total_time(self) -> timedelta: + result = _sum(self.times) + assert result + return result + + @property + def average_time(self) -> timedelta: + return self.total_time / len(self.times) + + +def _make_delays_breakdown(summary: _DelaysSummary) -> list[_DelaysBreakdownRow]: + rows = [] + for reason, times in summary.times_per_reason.items(): + rows.append( + _DelaysBreakdownRow(scenario_type=reason[0], reason=reason[1], times=times) + ) + rows.sort(key=lambda row: row.total_time, reverse=True) + return rows diff --git a/monitoring/uss_qualifier/reports/validation/definitions.py b/monitoring/uss_qualifier/reports/validation/definitions.py index b3d0e05ae7..8040bbde3f 100644 --- a/monitoring/uss_qualifier/reports/validation/definitions.py +++ b/monitoring/uss_qualifier/reports/validation/definitions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.dicts import JSONAddress from monitoring.uss_qualifier.common_data_definitions import Severity @@ -11,21 +11,21 @@ class SeverityComparison(ImplicitDict): """Exactly one field must be specified.""" - equal_to: Severity | None - at_least: Severity | None - higher_than: Severity | None - no_higher_than: Severity | None - lower_than: Severity | None + equal_to: Optional[Severity] + at_least: Optional[Severity] + higher_than: Optional[Severity] + no_higher_than: Optional[Severity] + lower_than: Optional[Severity] class NumericComparison(ImplicitDict): """Exactly one field must be specified.""" - equal_to: float | None - at_least: float | None - more_than: float | None - no_more_than: float | None - less_than: float | None + equal_to: Optional[float] + at_least: Optional[float] + more_than: Optional[float] + no_more_than: Optional[float] + less_than: Optional[float] # ===== Applicability ===== @@ -40,7 +40,7 @@ class TestScenarioApplicability(ImplicitDict): class FailedCheckApplicability(ImplicitDict): """FailedCheck test report elements are applicable according to this specification.""" - has_severity: SeverityComparison | None + has_severity: Optional[SeverityComparison] """If specified, only FailedChecks with specified severity are applicable.""" @@ -69,25 +69,25 @@ class ValidationCriterionApplicability(ImplicitDict): Exactly one field must be specified.""" - test_scenarios: TestScenarioApplicability | None + test_scenarios: Optional[TestScenarioApplicability] """Only this kind of TestScenarioReport elements are applicable.""" - failed_checks: FailedCheckApplicability | None + failed_checks: Optional[FailedCheckApplicability] """Only this kind of FailedCheck elements are applicable.""" - skipped_actions: SkippedCheckApplicability | None + skipped_actions: Optional[SkippedCheckApplicability] """Only this kind of SkippedCheckReport elements are applicable.""" - address_is: JSONAddress | None + address_is: Optional[JSONAddress] """Only the element at this JSONAddress in the test report is applicable.""" - does_not_satisfy: ValidationCriterionApplicability | None + does_not_satisfy: Optional[ValidationCriterionApplicability] """Only elements that do not satisfy this criterion are applicable.""" - satisfies_all: AllCriteriaApplicability | None + satisfies_all: Optional[AllCriteriaApplicability] """Only elements which satisfy all these criteria are applicable.""" - satisfies_any: AnyCriteriaApplicability | None + satisfies_any: Optional[AnyCriteriaApplicability] """Elements which satisfy any of these criteria are applicable.""" @@ -97,17 +97,17 @@ class ValidationCriterionApplicability(ImplicitDict): class EachElementCondition(ImplicitDict): """A single applicable element must meet this condition. Exactly one field must be specified.""" - has_severity: SeverityComparison | None + has_severity: Optional[SeverityComparison] """The element must be a FailedCheck that has this specified kind of severity.""" - has_execution_error: bool | None + has_execution_error: Optional[bool] """The element must be a TestScenarioReport that either must have or must not have an execution error.""" class ElementGroupCondition(ImplicitDict): """A group of applicable elements must meet this condition. Exactly one field must be specified.""" - count: NumericComparison | None + count: Optional[NumericComparison] """The number of applicable elements must have this specified kind of count.""" @@ -128,19 +128,19 @@ class AnyPassCondition(ImplicitDict): class PassCondition(ImplicitDict): """Condition for passing validation. Exactly one field must be specified.""" - each_element: EachElementCondition | None + each_element: Optional[EachElementCondition] """Condition applies to each applicable element.""" - elements: ElementGroupCondition | None + elements: Optional[ElementGroupCondition] """Condition applies to the group of applicable elements.""" - does_not_pass: PassCondition | None + does_not_pass: Optional[PassCondition] """Overall condition is met only if this specified condition is not met.""" - all_of: AllPassConditions | None + all_of: Optional[AllPassConditions] """Overall condition is met only if all of these specified conditions are met.""" - any_of: AnyPassCondition | None + any_of: Optional[AnyPassCondition] """Overall condition is met if any of these specified conditions are met.""" diff --git a/monitoring/uss_qualifier/reports/validation/report_validation.py b/monitoring/uss_qualifier/reports/validation/report_validation.py index a25d59817a..b3311a7886 100644 --- a/monitoring/uss_qualifier/reports/validation/report_validation.py +++ b/monitoring/uss_qualifier/reports/validation/report_validation.py @@ -176,29 +176,30 @@ def _get_applicable_elements_from_action( report: TestSuiteActionReport, location: JSONAddress, ) -> Iterator[TestReportElement]: - test_suite, test_scenario, action_generator = report.get_applicable_report() - if test_scenario: + if "test_scenario" in report and report.test_scenario: return _get_applicable_elements_from_test_scenario( applicability, report.test_scenario, JSONAddress(location + ".test_scenario"), ) - elif test_suite: + elif "test_suite" in report and report.test_suite: return _get_applicable_elements_from_test_suite( applicability, report.test_suite, JSONAddress(location + ".test_suite") ) - elif action_generator: + elif "action_generator" in report and report.action_generator: return _get_applicable_elements_from_action_generator( applicability, report.action_generator, JSONAddress(location + ".action_generator"), ) - else: + elif "skipped_action" in report and report.skipped_action: return _get_applicable_elements_from_skipped_action( applicability, report.skipped_action, JSONAddress(location + ".skipped_action"), ) + else: + raise report.invalid_type_error # ===== Evaluation of conditions ===== diff --git a/monitoring/uss_qualifier/requirements/astm/f3411/v22a/service_provider.md b/monitoring/uss_qualifier/requirements/astm/f3411/v22a/service_provider.md index 61ae34a55a..f68a798346 100644 --- a/monitoring/uss_qualifier/requirements/astm/f3411/v22a/service_provider.md +++ b/monitoring/uss_qualifier/requirements/astm/f3411/v22a/service_provider.md @@ -101,6 +101,14 @@ All Service Provider Role requirements can be verified by automation. * **astm.f3411.v22a.NET0260,Table1,23** * **astm.f3411.v22a.NET0260,Table1,24** +#### Operator Altitude provider + +* **astm.f3411.v22a.NET0260,Table1,25** + +#### Operator Location Type provider + +* **astm.f3411.v22a.NET0260,Table1,26** + #### Operational Status provider * **astm.f3411.v22a.NET0260,Table1,7** diff --git a/monitoring/uss_qualifier/requirements/definitions.py b/monitoring/uss_qualifier/requirements/definitions.py index 3d320de30b..3fc0b41efc 100644 --- a/monitoring/uss_qualifier/requirements/definitions.py +++ b/monitoring/uss_qualifier/requirements/definitions.py @@ -2,7 +2,7 @@ import os -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional class RequirementID(str): @@ -120,14 +120,14 @@ def anchor(self) -> str: class RequirementCollection(ImplicitDict): - requirements: list[RequirementID] | None + requirements: Optional[list[RequirementID]] """This collection includes all of these requirements.""" - requirement_sets: list[RequirementSetID] | None + requirement_sets: Optional[list[RequirementSetID]] """This collection includes all requirements in all of these requirement sets.""" - requirement_collections: list[RequirementCollection] | None + requirement_collections: Optional[list[RequirementCollection]] """This collection includes all of the requirements in all of these requirement collections.""" - exclude: RequirementCollection | None + exclude: Optional[RequirementCollection] """This collection does not include any of these requirements, despite all previous fields.""" diff --git a/monitoring/uss_qualifier/requirements/interuss/automated_testing/execution.md b/monitoring/uss_qualifier/requirements/interuss/automated_testing/execution.md new file mode 100644 index 0000000000..47ea77c0e5 --- /dev/null +++ b/monitoring/uss_qualifier/requirements/interuss/automated_testing/execution.md @@ -0,0 +1,7 @@ +# InterUSS automated test execution requirements + +## Requirements + +### RunToCompletion + +When a test configuration designer specifies that an automated test must run to completion (by including this requirement), all applicable participants in the automated test will fail this requirement if the automated test does not run to completion. Not running to completion may be due to exceeding the maximum allowed test run time in ExecutionConfiguration.stop_after or because of a failed check with Critical severity. diff --git a/monitoring/uss_qualifier/requirements/interuss/dss/hosting.md b/monitoring/uss_qualifier/requirements/interuss/dss/hosting.md new file mode 100644 index 0000000000..4bbd2dee52 --- /dev/null +++ b/monitoring/uss_qualifier/requirements/interuss/dss/hosting.md @@ -0,0 +1,5 @@ +# InterUSS DSS hosting requirements + +While not all competent authorities will require any or all of these requirements, when they are required, these requirements can provide certain benefits like enabling experimental validation of successful pooling when DSS instances are the InterUSS DSS implementation. + +* ExposeAux: The DSS implementation under test must expose the InterUSS-defined `aux` interface implemented by the InterUSS DSS implementation corresponding to the range of versions allowed by the competent authoity and respond to queries correctly according to that interface. diff --git a/monitoring/uss_qualifier/resources/astm/f3411/dss.py b/monitoring/uss_qualifier/resources/astm/f3411/dss.py index ac6e59eb07..37fd428f5c 100644 --- a/monitoring/uss_qualifier/resources/astm/f3411/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3411/dss.py @@ -5,6 +5,7 @@ from implicitdict import ImplicitDict from monitoring.monitorlib import infrastructure +from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.reports.report import ParticipantID from monitoring.uss_qualifier.resources.communications import AuthAdapterResource @@ -40,12 +41,12 @@ def __init__( participant_id: ParticipantID, base_url: str, rid_version: RIDVersion, - auth_adapter: infrastructure.AuthAdapter, + client: UTMClientSession, ): self.participant_id = participant_id self.base_url = base_url self.rid_version = rid_version - self.client = infrastructure.UTMClientSession(base_url, auth_adapter) + self.client = client def is_same_as(self, other: DSSInstance) -> bool: return ( @@ -81,7 +82,9 @@ def __init__( specification.participant_id, specification.base_url, specification.rid_version, - auth_adapter.adapter, + infrastructure.utm_client_session_factory.get_session( + specification.base_url, auth_adapter.adapter + ), ) @classmethod diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py index 6be1b20a54..a8feb9b8c3 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse import s2sphere -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.astm.f3548.v21.api import ( OPERATIONS, AirspaceConflictResponse, @@ -41,10 +41,13 @@ ) from uas_standards.astm.f3548.v21.constants import Scope -from monitoring.monitorlib import infrastructure from monitoring.monitorlib.fetch import Query, QueryError, QueryType, query_and_describe from monitoring.monitorlib.fetch import scd as fetch from monitoring.monitorlib.fetch.scd import FetchedSubscription, FetchedSubscriptions +from monitoring.monitorlib.infrastructure import ( + UTMClientSession, + utm_client_session_factory, +) from monitoring.monitorlib.inspection import calling_function_name, fullname from monitoring.monitorlib.mutate import scd as mutate from monitoring.monitorlib.mutate.scd import MutatedSubscription @@ -56,15 +59,18 @@ class DSSInstanceSpecification(ImplicitDict): participant_id: str """ID of the USS responsible for this DSS instance""" - user_participant_ids: list[str] | None + user_participant_ids: Optional[list[str]] """IDs of any participants using this DSS instance, apart from the USS responsible for this DSS instance.""" base_url: str """Base URL for the DSS instance according to the ASTM F3548-21 API""" - supports_ovn_request: bool | None + supports_ovn_request: Optional[bool] """Whether this DSS instance supports the optional extension not part of the original F3548 standard API allowing a USS to request a specific OVN when creating or updating an operational intent.""" + timeout_seconds: Optional[float] + """If specified, number of seconds to allow before timing out requests to this DSS instance.""" + def __init__(self, *args, **kwargs): super().__init__(**kwargs) try: @@ -77,7 +83,7 @@ class DSSInstance: participant_id: str user_participant_ids: list[str] base_url: str - client: infrastructure.UTMClientSession + client: UTMClientSession _scopes_authorized: set[str] def __init__( @@ -85,13 +91,13 @@ def __init__( participant_id: str, user_participant_ids: list[str], base_url: str, - auth_adapter: infrastructure.AuthAdapter, + client: UTMClientSession, scopes_authorized: list[str], ): self.participant_id = participant_id self.user_participant_ids = user_participant_ids self.base_url = base_url - self.client = infrastructure.UTMClientSession(base_url, auth_adapter) + self.client = client self._scopes_authorized = set( s.value if isinstance(s, Enum) else s for s in scopes_authorized ) @@ -126,7 +132,11 @@ def with_different_auth( participant_id=self.participant_id, user_participant_ids=self.user_participant_ids, base_url=self.base_url, - auth_adapter=auth_adapter.adapter, + client=utm_client_session_factory.get_session( + self.base_url, + auth_adapter=auth_adapter.adapter, + timeout_seconds=self.client.timeout_seconds, + ), scopes_authorized=list(scopes_required), ) @@ -694,6 +704,7 @@ def delete_subscription(self, sub_id: str, sub_version: str) -> MutatedSubscript class DSSInstanceResource(Resource[DSSInstanceSpecification]): _specification: DSSInstanceSpecification _auth_adapter: AuthAdapterResource + _client: UTMClientSession def __init__( self, @@ -704,6 +715,14 @@ def __init__( super().__init__(specification, resource_origin) self._specification = specification self._auth_adapter = auth_adapter + timeout_seconds = ( + specification.timeout_seconds + if "timeout_seconds" in specification + else None + ) + self._client = utm_client_session_factory.get_session( + self._specification.base_url, auth_adapter.adapter, timeout_seconds + ) def can_use_scope(self, scope: str) -> bool: return scope in self._auth_adapter.scopes @@ -777,7 +796,7 @@ def get_instance(self, scopes_required: dict[str, str]) -> DSSInstance: else [] ), self._specification.base_url, - self._auth_adapter.adapter, + self._client, list(scopes_required), ) diff --git a/monitoring/uss_qualifier/resources/communications/auth_adapter.py b/monitoring/uss_qualifier/resources/communications/auth_adapter.py index 615db07ca0..31a8c7cfb8 100644 --- a/monitoring/uss_qualifier/resources/communications/auth_adapter.py +++ b/monitoring/uss_qualifier/resources/communications/auth_adapter.py @@ -1,7 +1,7 @@ import os from enum import Enum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib import infrastructure from monitoring.monitorlib.auth import make_auth_adapter @@ -16,10 +16,10 @@ class AuthAdapterSpecification(ImplicitDict): * environment_variable_containing_auth_spec """ - auth_spec: str | None + auth_spec: Optional[str] """Literal representation of auth spec. WARNING: Specifying this directly may cause sensitive information to be included in reports and unprotected configuration files.""" - environment_variable_containing_auth_spec: str | None + environment_variable_containing_auth_spec: Optional[str] """Name of environment variable containing the auth spec. This is the preferred method of providing the auth spec.""" scopes_authorized: list[str] diff --git a/monitoring/uss_qualifier/resources/communications/client_identity.py b/monitoring/uss_qualifier/resources/communications/client_identity.py index 51975995af..a5c3ad9812 100644 --- a/monitoring/uss_qualifier/resources/communications/client_identity.py +++ b/monitoring/uss_qualifier/resources/communications/client_identity.py @@ -55,7 +55,7 @@ def subject(self) -> str: # we force one using the client identify audience and scopes # Trigger a caching initial token request so that adapter.get_sub() will return something - headers = self._adapter.get_headers( + additional_headers = self._adapter.get_headers( f"https://{self.specification.whoami_audience}", [self.specification.whoami_scope], ) @@ -66,7 +66,7 @@ def subject(self) -> str: raise ValueError( f"subject is None, meaning `sub` claim was not found in payload of token, " f"using {type(self._adapter).__name__} requesting {self.specification.whoami_scope} scope " - f"for {self.specification.whoami_audience} audience: {headers['Authorization'][len('Bearer: ') :]}" + f"for {self.specification.whoami_audience} audience: {additional_headers.headers['Authorization'][len('Bearer: ') :]}" ) return sub diff --git a/monitoring/uss_qualifier/resources/dev/test_exclusions.py b/monitoring/uss_qualifier/resources/dev/test_exclusions.py index 133f2311ae..799b47c6d6 100644 --- a/monitoring/uss_qualifier/resources/dev/test_exclusions.py +++ b/monitoring/uss_qualifier/resources/dev/test_exclusions.py @@ -1,11 +1,11 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier.resources.resource import Resource class TestExclusionsSpecification(ImplicitDict): - allow_private_addresses: bool | None - allow_cleartext_queries: bool | None + allow_private_addresses: Optional[bool] + allow_cleartext_queries: Optional[bool] class TestExclusionsResource(Resource[TestExclusionsSpecification]): diff --git a/monitoring/uss_qualifier/resources/eurocae/ed318/__init__.py b/monitoring/uss_qualifier/resources/eurocae/ed318/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/uss_qualifier/resources/eurocae/ed318/source_document.py b/monitoring/uss_qualifier/resources/eurocae/ed318/source_document.py new file mode 100644 index 0000000000..234f4990c7 --- /dev/null +++ b/monitoring/uss_qualifier/resources/eurocae/ed318/source_document.py @@ -0,0 +1,23 @@ +from implicitdict import ImplicitDict + +from monitoring.uss_qualifier import fileio +from monitoring.uss_qualifier.resources.resource import Resource + + +class SourceDocumentSpecification(ImplicitDict): + url: str + """Url of the ED-318 document to verify""" + + +class SourceDocument(Resource[SourceDocumentSpecification]): + specification: SourceDocumentSpecification + + raw_document: str + """Content of the document""" + + def __init__( + self, specification: SourceDocumentSpecification, resource_origin: str + ): + super().__init__(specification, resource_origin) + self.specification = specification + self.raw_document = fileio.load_content(specification.url) diff --git a/monitoring/uss_qualifier/resources/files.py b/monitoring/uss_qualifier/resources/files.py index d8ce545e34..31f3657db3 100644 --- a/monitoring/uss_qualifier/resources/files.py +++ b/monitoring/uss_qualifier/resources/files.py @@ -1,7 +1,7 @@ import hashlib import json -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier import fileio from monitoring.uss_qualifier.fileio import FileReference @@ -11,7 +11,7 @@ class ExternalFile(ImplicitDict): path: FileReference """Location of the external file.""" - hash_sha512: str | None + hash_sha512: Optional[str] """SHA-512 hash of the external file. If specified, the external file's content will be verified to have this hash or else produce an error. diff --git a/monitoring/uss_qualifier/resources/flight_planning/flight_intent.py b/monitoring/uss_qualifier/resources/flight_planning/flight_intent.py index dc926b47d6..d046730d98 100644 --- a/monitoring/uss_qualifier/resources/flight_planning/flight_intent.py +++ b/monitoring/uss_qualifier/resources/flight_planning/flight_intent.py @@ -2,7 +2,7 @@ import json -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( FlightInfoTemplate, @@ -23,7 +23,7 @@ class DeltaFlightIntent(ImplicitDict): source: FlightIntentID """Base the flight intent for this element of a FlightIntentCollection on the element of the collection identified by this field.""" - mutation: dict | None + mutation: Optional[dict] """For each leaf subfield specified in this object, override the value in the corresponding subfield of the flight intent for this element with the specified value. Consider subfields prefixed with + as leaf subfields.""" @@ -32,10 +32,10 @@ class DeltaFlightIntent(ImplicitDict): class FlightIntentCollectionElement(ImplicitDict): """Definition of a single flight intent within a FlightIntentCollection. Exactly one field must be specified.""" - full: FlightInfoTemplate | None + full: Optional[FlightInfoTemplate] """If specified, the full definition of the flight planning intent.""" - delta: DeltaFlightIntent | None + delta: Optional[DeltaFlightIntent] """If specified, a flight planning intent based on another flight intent, but with some changes.""" @@ -45,7 +45,7 @@ class FlightIntentCollection(ImplicitDict): intents: dict[FlightIntentID, FlightIntentCollectionElement] """Flight planning actions that users want to perform.""" - transformations: list[Transformation] | None + transformations: Optional[list[Transformation]] """Transformations to append to all FlightInfoTemplates.""" def resolve(self) -> dict[FlightIntentID, FlightInfoTemplate]: @@ -107,11 +107,11 @@ def resolve(self) -> dict[FlightIntentID, FlightInfoTemplate]: class FlightIntentsSpecification(ImplicitDict): """Exactly one field must be specified.""" - intent_collection: FlightIntentCollection | None + intent_collection: Optional[FlightIntentCollection] """Full flight intent collection, or a $ref to an external file containing a FlightIntentCollection.""" - file: ExternalFile | None + file: Optional[ExternalFile] """Location of file to load, containing a FlightIntentCollection""" - transformations: list[Transformation] | None + transformations: Optional[list[Transformation]] """Transformations to apply to all flight intents' 4D volumes after resolution (if specified)""" diff --git a/monitoring/uss_qualifier/resources/flight_planning/flight_intent_validation.py b/monitoring/uss_qualifier/resources/flight_planning/flight_intent_validation.py index c5d1e4991c..13a5805128 100644 --- a/monitoring/uss_qualifier/resources/flight_planning/flight_intent_validation.py +++ b/monitoring/uss_qualifier/resources/flight_planning/flight_intent_validation.py @@ -14,7 +14,7 @@ FlightInfoTemplate, ) from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection -from monitoring.monitorlib.temporal import Time, TimeDuringTest +from monitoring.monitorlib.temporal import TestTimeContext, Time, TimeDuringTest from monitoring.monitorlib.uspace import problems_with_flight_authorisation from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( FlightIntentID, @@ -22,8 +22,11 @@ FlightIntentName = str -MAX_TEST_RUN_DURATION = timedelta(minutes=45) -"""The longest a test run might take (to estimate flight intent timestamps prior to scenario execution)""" +MAX_SCENARIO_EXEC_DURATION = timedelta(minutes=45) +"""The longest a scenario run might take (to estimate flight intent timestamps prior to scenario execution)""" + +MAX_TEST_RUN_DURATION = timedelta(hours=6) +"""The longest a test run might take (to estimate flight intent timestamps prior to test run)""" @dataclass @@ -43,40 +46,70 @@ class ExpectedFlightIntent: valid_uspace_flight_auth: bool | None = None -def validate_flight_intent_templates( +def estimate_scenario_execution_max_extents( + scenario_time_context: TestTimeContext, templates: dict[FlightIntentID, FlightInfoTemplate], - expected_intents: list[ExpectedFlightIntent], ) -> Volume4D: - """ - Returns: the bounding extents of the flight intent templates - """ extents = Volume4DCollection([]) - now = Time(arrow.utcnow().datetime) - times = { - TimeDuringTest.StartOfTestRun: now, - TimeDuringTest.StartOfScenario: now, - TimeDuringTest.TimeOfEvaluation: now, - } - flight_intents = {k: v.resolve(times) for k, v in templates.items()} + scenario_start = TestTimeContext( + { + TimeDuringTest.StartOfTestRun: scenario_time_context[ + TimeDuringTest.StartOfTestRun + ], + TimeDuringTest.StartOfScenario: scenario_time_context[ + TimeDuringTest.StartOfScenario + ], + TimeDuringTest.TimeOfEvaluation: scenario_time_context[ + TimeDuringTest.StartOfScenario + ], + } + ) + flight_intents = {k: v.resolve(scenario_start) for k, v in templates.items()} for flight_intent in flight_intents.values(): extents.extend(flight_intent.basic_information.area) - validate_flight_intents(flight_intents, expected_intents, now) - later = Time(now.datetime + MAX_TEST_RUN_DURATION) - times = { - TimeDuringTest.StartOfTestRun: now, - TimeDuringTest.StartOfScenario: later, - TimeDuringTest.TimeOfEvaluation: later, + scenario_estimated_end = TestTimeContext( + { + TimeDuringTest.StartOfTestRun: scenario_time_context[ + TimeDuringTest.StartOfTestRun + ], + TimeDuringTest.StartOfScenario: scenario_time_context[ + TimeDuringTest.StartOfScenario + ], + TimeDuringTest.TimeOfEvaluation: Time( + scenario_time_context[TimeDuringTest.StartOfScenario].datetime + + MAX_SCENARIO_EXEC_DURATION + ), + } + ) + flight_intents = { + k: v.resolve(scenario_estimated_end) for k, v in templates.items() } - flight_intents = {k: v.resolve(times) for k, v in templates.items()} for flight_intent in flight_intents.values(): extents.extend(flight_intent.basic_information.area) - validate_flight_intents(flight_intents, expected_intents, later) return extents.bounding_volume +def validate_flight_intent_templates( + templates: dict[FlightIntentID, FlightInfoTemplate], + expected_intents: list[ExpectedFlightIntent], +): + """Validate that all intents templates meet the criteria from `expected_intents` over the estimated maximum duration of the test run.""" + + now = Time(arrow.utcnow().datetime) + context = TestTimeContext.all_times_are(now) + flight_intents = {k: v.resolve(context) for k, v in templates.items()} + validate_flight_intents(flight_intents, expected_intents, now) + + later = Time(now.datetime + MAX_TEST_RUN_DURATION) + context = TestTimeContext.all_times_are(later) + context[TimeDuringTest.StartOfTestRun] = now + flight_intents = {k: v.resolve(context) for k, v in templates.items()} + validate_flight_intents(flight_intents, expected_intents, later) + + def validate_flight_intents( intents: dict[FlightIntentID, FlightInfo], expected_intents: list[ExpectedFlightIntent], diff --git a/monitoring/uss_qualifier/resources/flight_planning/flight_planner.py b/monitoring/uss_qualifier/resources/flight_planning/flight_planner.py index 896933465e..53aa2771c4 100644 --- a/monitoring/uss_qualifier/resources/flight_planning/flight_planner.py +++ b/monitoring/uss_qualifier/resources/flight_planning/flight_planner.py @@ -1,6 +1,6 @@ from urllib.parse import urlparse -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib import infrastructure from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient @@ -16,13 +16,13 @@ class FlightPlannerConfiguration(ImplicitDict): participant_id: str """ID of the flight planner into which test data can be injected""" - scd_injection_base_url: str | None + scd_injection_base_url: Optional[str] """Base URL for the flight planner's implementation of the interfaces/automated_testing/scd/v1/scd.yaml API""" - v1_base_url: str | None + v1_base_url: Optional[str] """Base URL for the flight planner's implementation of the interfaces/automated_testing/flight_planning/v1/flight_planning.yaml API""" - timeout_seconds: float | None = None + timeout_seconds: Optional[float | None] = None """Number of seconds to allow for requests to this flight planner. If None, use default.""" def __init__(self, *args, **kwargs): @@ -48,12 +48,12 @@ def to_client( self, auth_adapter: infrastructure.AuthAdapter ) -> FlightPlannerClient: if "scd_injection_base_url" in self and self.scd_injection_base_url: - session = infrastructure.UTMClientSession( + session = infrastructure.utm_client_session_factory.get_session( self.scd_injection_base_url, auth_adapter, self.timeout_seconds ) return SCDFlightPlannerClient(session, self.participant_id) elif "v1_base_url" in self and self.v1_base_url: - session = infrastructure.UTMClientSession( + session = infrastructure.utm_client_session_factory.get_session( self.v1_base_url, auth_adapter, self.timeout_seconds ) return V1FlightPlannerClient(session, self.participant_id) diff --git a/monitoring/uss_qualifier/resources/flight_planning/flight_planners.py b/monitoring/uss_qualifier/resources/flight_planning/flight_planners.py index 847c0688f5..e8ffe455a0 100644 --- a/monitoring/uss_qualifier/resources/flight_planning/flight_planners.py +++ b/monitoring/uss_qualifier/resources/flight_planning/flight_planners.py @@ -1,6 +1,6 @@ from collections.abc import Iterable -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.interuss.automated_testing.flight_planning.v1.constants import ( Scope as ScopeFlightPlanning, ) @@ -91,10 +91,10 @@ def make_subset(self, select_indices: Iterable[int]) -> list[FlightPlannerResour class FlightPlannerCombinationSelectorSpecification(ImplicitDict): - must_include: list[ParticipantID] | None + must_include: Optional[list[ParticipantID]] """The set of flight planners which must be included in every combination""" - maximum_roles: dict[ParticipantID, int] | None + maximum_roles: Optional[dict[ParticipantID, int]] """Maximum number of roles a particular participant may fill in any given combination""" diff --git a/monitoring/uss_qualifier/resources/geospatial_info/geospatial_info_providers.py b/monitoring/uss_qualifier/resources/geospatial_info/geospatial_info_providers.py index 343d9d244f..4482612efc 100644 --- a/monitoring/uss_qualifier/resources/geospatial_info/geospatial_info_providers.py +++ b/monitoring/uss_qualifier/resources/geospatial_info/geospatial_info_providers.py @@ -1,6 +1,6 @@ from urllib.parse import urlparse -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.interuss.automated_testing.geospatial_map.v1.constants import ( Scope as ScopeGeospatialMap, ) @@ -9,7 +9,7 @@ from monitoring.monitorlib.clients.geospatial_info.client_geospatial_map import ( GeospatialMapClient, ) -from monitoring.monitorlib.infrastructure import AuthAdapter, UTMClientSession +from monitoring.monitorlib.infrastructure import AuthAdapter, utm_client_session_factory from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.communications import AuthAdapterResource from monitoring.uss_qualifier.resources.resource import Resource @@ -19,10 +19,10 @@ class GeospatialInfoProviderConfiguration(ImplicitDict): participant_id: str """ID of the geospatial information provider for which geospatial data can be queried""" - geospatial_map_v1_base_url: str | None + geospatial_map_v1_base_url: Optional[str] """Base URL for the geospatial information provider's implementation of the interfaces/automated_testing/geospatial_map/v1/geospatial_map.yaml API""" - timeout_seconds: float | None = None + timeout_seconds: Optional[float | None] = None """Number of seconds to allow for requests to this geospatial information provider. If None, use default.""" def __init__(self, *args, **kwargs): @@ -37,7 +37,7 @@ def __init__(self, *args, **kwargs): def to_client(self, auth_adapter: AuthAdapter) -> GeospatialInfoClient: if "geospatial_map_v1_base_url" in self and self.geospatial_map_v1_base_url: - session = UTMClientSession( + session = utm_client_session_factory.get_session( self.geospatial_map_v1_base_url, auth_adapter, self.timeout_seconds ) return GeospatialMapClient(session, self.participant_id) diff --git a/monitoring/uss_qualifier/resources/interuss/datastore/__init__.py b/monitoring/uss_qualifier/resources/interuss/datastore/__init__.py index 0d0d61ab61..170db355c5 100644 --- a/monitoring/uss_qualifier/resources/interuss/datastore/__init__.py +++ b/monitoring/uss_qualifier/resources/interuss/datastore/__init__.py @@ -1,3 +1,6 @@ +from .datastore import ( + CockroachDBNode as CockroachDBNode, +) from .datastore import ( DatastoreDBClusterResource as DatastoreDBClusterResource, ) @@ -7,3 +10,6 @@ from .datastore import ( DatastoreDBNodeResource as DatastoreDBNodeResource, ) +from .datastore import ( + YugabyteDBNode as YugabyteDBNode, +) diff --git a/monitoring/uss_qualifier/resources/interuss/datastore/datastore.py b/monitoring/uss_qualifier/resources/interuss/datastore/datastore.py index 6febf008c9..ece037ed3c 100644 --- a/monitoring/uss_qualifier/resources/interuss/datastore/datastore.py +++ b/monitoring/uss_qualifier/resources/interuss/datastore/datastore.py @@ -1,6 +1,8 @@ from __future__ import annotations import socket +import ssl +from abc import ABC, abstractmethod import psycopg from implicitdict import ImplicitDict @@ -18,6 +20,9 @@ class DatastoreDBNodeSpecification(ImplicitDict): port: int """Port to which DatastoreDB node is listening to.""" + is_yugabyte: bool = False + """True if DatastoreDB node is a YugabyteDB node.""" + def __init__(self, *args, **kwargs): super().__init__(**kwargs) @@ -34,7 +39,13 @@ def __init__( self._specification = specification def get_client(self) -> DatastoreDBNode: - return DatastoreDBNode( + if self._specification.is_yugabyte: + return YugabyteDBNode( + self._specification.participant_id, + self._specification.host, + self._specification.port, + ) + return CockroachDBNode( self._specification.participant_id, self._specification.host, self._specification.port, @@ -69,7 +80,9 @@ def __init__( ] -class DatastoreDBNode: +class DatastoreDBNode(ABC): + _NOT_IMPLEMENTED_MSG = "All methods of base DatastoreDBNode class must be implemented by each specific subclass" + participant_id: str host: str port: int @@ -84,7 +97,73 @@ def __init__( self.host = host self.port = port - def connect(self, **kwargs) -> psycopg.Connection: + def build_socket(self) -> socket.socket: + return socket.create_connection( + (self.host, self.port), + timeout=5, + ) + + def is_reachable(self) -> tuple[bool, Exception | None]: + """This is detected by attempting to open a socket with the node.""" + try: + sock = self.build_socket() + sock.close() + return True, None + except (TimeoutError, ConnectionError) as e: + return False, e + + @abstractmethod + def no_tls_rejected(self) -> tuple[bool, Exception | None]: + """Returns True if the node rejects cleartext communications.""" + raise NotImplementedError(DatastoreDBNode._NOT_IMPLEMENTED_MSG) + + @abstractmethod + def unauthenticated_rejected(self) -> tuple[bool, Exception | None]: + """Returns True if the node rejects unauthenticated communications.""" + raise NotImplementedError(DatastoreDBNode._NOT_IMPLEMENTED_MSG) + + @abstractmethod + def legacy_tls_version_rejected(self) -> tuple[bool, Exception | None]: + """Returns True if the node rejects the usage of the legacy cryptographic protocols TLSv1 and TLSv1.1.""" + raise NotImplementedError(DatastoreDBNode._NOT_IMPLEMENTED_MSG) + + def build_client_hello(self): + """Builds a client hello""" + + return bytes.fromhex( + "16" # Handshake + "0301" # TLS Version: 1.0 + "0063" # Length + "01" # Handshake type: Client hello + "00005f" # Length + "0302" # TLS Version: 1.1 + "4895335bae2d2d929e34bdd5ccc89d800807bb01bbaaa7bf86efbb83a9249206" # Random value + "00" # Session ID Length + "0012" # Cipher suite Length + "c00ac0140039c009c01300330035002f00ff" # Cipher suites + "01" # Compression method length + "00" # No compression + "0024" # Extentions length + "000b000403000102000a000c000a001d0017001e00190018002300000016000000170000" # Extensions + ) + + def is_protocol_failure(self, data): + """Tests whether the server sends a protocol failure.""" + # Format: + # 15 TLS Alert + # 03 01 TLS Version (Ignored) + # 00 02 Length (Ignored) + # 02 Level: Fatal (Ignored) + # 46 Description: Protocol version + + content_type = data[0] + alert_description = data[6] + + return content_type == 0x15 and alert_description == 0x46 + + +class CockroachDBNode(DatastoreDBNode): + def _connect(self, **kwargs) -> psycopg.Connection: return psycopg.connect( host=self.host, port=self.port, @@ -92,53 +171,118 @@ def connect(self, **kwargs) -> psycopg.Connection: **kwargs, ) - def is_reachable(self) -> tuple[bool, psycopg.Error | None]: - """ - Returns True if the node is reachable. - This is detected by attempting to establish a connection with the node - not requiring encryption and validating either 1) that the connection - fails with the error message reporting that the authentication failed; - or 2) that the connection succeeds. - """ + def no_tls_rejected(self) -> tuple[bool, Exception | None]: + try: + c = self._connect(sslmode="disable") + c.close() + except psycopg.OperationalError as e: + err_msg = str(e) + did_reject = "node is running secure mode" in err_msg + return did_reject, e + return False, Exception("Connection was successful") + + def unauthenticated_rejected(self) -> tuple[bool, Exception | None]: try: - c = self.connect( + c = self._connect( sslmode="prefer", require_auth="password", password="dummy" ) c.close() except psycopg.OperationalError as e: err_msg = str(e) - # First message is returned if password authentication is enabled - # (CockroachDB), second one if not (Yugabyte use certificates) - is_reachable = ( + did_reject = ( "password authentication failed" in err_msg or "server did not complete authentication" in err_msg or "server requested a hashed password" in err_msg ) - return is_reachable, e - return True, None + return did_reject, e + return False, Exception("Connection was successful") - def runs_in_secure_mode(self) -> tuple[bool, psycopg.Error | None]: + def legacy_tls_version_rejected(self) -> tuple[bool, Exception | None]: """ - Returns True if the node is running in secure mode. This is detected by attempting to establish a connection with the node - in insecure mode and validating that the connection fails with the error - message reporting that the node is running in secure mode. + forcing the client to use a TLS version < 1.2 and validating that the + connection fails with the expected error message. + + Modern libraries and Python have dropped support for TLS versions older than 1.2, as these are now considered legacy. + + To be able to test those old protocols, we manually send TLS packets (captured from legacy code) and parse the result. + Parsing is limited, but should be good enough for our cases. """ + try: - c = self.connect(sslmode="disable") - c.close() - except psycopg.OperationalError as e: - err_msg = str(e) - # First message is returned by CockroachDB, second one by Yugabyte - # (No hba entries for authentication outside SSL) - secure_mode = ( - "node is running secure mode" in err_msg - or "no pg_hba.conf entry for host" in err_msg - ) - return secure_mode, e - return False, None + with self.build_socket() as sock: + sock.sendall(bytes.fromhex("0000000804d2162f")) # Postgres hello + sock.recv(16) + sock.sendall(self.build_client_hello()) + data = sock.recv(1024) + + if not data: + return False, Exception("No response from server") - def legacy_ssl_version_rejected(self) -> tuple[bool, psycopg.Error | None]: + return self.is_protocol_failure(data), None + except Exception as e: + return False, e + + +class YugabyteDBNode(DatastoreDBNode): + @classmethod + def _build_dummy_rpc_request(cls): + """Builds an RPC request for service 'yb.master.MasterService', method 'GetMasterRegistration' with call ID '5'. + Reference: https://gruchalski.com/posts/2022-02-12-a-brief-look-at-yugabytedb-rpc-api/""" + + return bytes.fromhex( + "594201" # 'YB1' preamble + "0000003a" # message byte length + "38" # request header protobuf message length + "0805" # field 1: call_id + "12300a1779622e6d61737465722e4d61737465725365727669636512154765744d6173746572526567697374726174696f6e" # field 2: remote_method (service_name = 'yb.master.MasterService', method_name = 'GetMasterRegistration') + "18e0d403" # field 3: timeout_millis + "00" # request payload protobuf message length (no payload) + ) + + def _attempt_insecure_request( + self, with_tls: bool + ) -> tuple[bool, Exception | None]: + sock = self.build_socket() # we expect node to be reachable + if with_tls: + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + try: + sock = ctx.wrap_socket(sock) + sock.do_handshake(block=True) + except ssl.SSLError as e: + # if we fail to create the SSL context: insecure + sock.close() + return False, e + + sock.sendall(self._build_dummy_rpc_request()) + msg_size_bytes = sock.recv(4) # message byte length (32-bits integer) + if len(msg_size_bytes) == 0: + # server closed connection upon receiving a valid RPC through insecure channel: secure + sock.close() + return True, None + + # server did not close connection: read response, msg_bytes is expected to look like: + # 04 # protobuf message length (response header) + # 0805 # field 1: call_id + # 1000 # field 2: is_error + # 8701 # protobuf message length (response payload) + # 0a34... # protobuf response payload + msg_size = int.from_bytes(msg_size_bytes, byteorder="big") + msg_bytes = sock.recv(msg_size) + sock.close() + return False, Exception( + f"Received RPC response from node, hex: {bytes.hex(msg_bytes)}" + ) + + def no_tls_rejected(self) -> tuple[bool, Exception | None]: + return self._attempt_insecure_request(with_tls=False) + + def unauthenticated_rejected(self) -> tuple[bool, Exception | None]: + return self._attempt_insecure_request(with_tls=True) + + def legacy_tls_version_rejected(self) -> tuple[bool, Exception | None]: """ Returns True if the node rejects the usage of the legacy cryptographic protocols TLSv1 and TLSv1.1. @@ -152,50 +296,14 @@ def legacy_ssl_version_rejected(self) -> tuple[bool, psycopg.Error | None]: Parsing is limited, but should be good enough for our cases. """ - def _build_client_hello(): - """Builds a client hello""" - - return bytes.fromhex( - "16" # Handshake - "0301" # TLS Version: 1.0 - "0063" # Length - "01" # Handshake type: Client hello - "00005f" # Length - "0302" # TLS Version: 1.1 - "4895335bae2d2d929e34bdd5ccc89d800807bb01bbaaa7bf86efbb83a9249206" # Random value - "00" # Session ID Length - "0012" # Cipher suite Length - "c00ac0140039c009c01300330035002f00ff" # Cipher suites - "01" # Compression method length - "00" # No compression - "0024" # Extentions length - "000b000403000102000a000c000a001d0017001e00190018002300000016000000170000" # Extensions - ) - - def _is_protocol_failure(data): - """Tests whether the server sends a protocol failure.""" - # Format: - # 15 TLS Alert - # 03 01 TLS Version (Ignored) - # 00 02 Length (Ignored) - # 02 Level: Fatal (Ignored) - # 46 Description: Protocol version - - content_type = data[0] - alert_description = data[6] - - return content_type == 0x15 and alert_description == 0x46 - try: - with socket.create_connection((self.host, self.port), timeout=5) as sock: - sock.sendall(bytes.fromhex("0000000804d2162f")) # Postgres hello - sock.recv(16) - sock.sendall(_build_client_hello()) + with self.build_socket() as sock: + sock.sendall(self.build_client_hello()) data = sock.recv(1024) if not data: - return False, "No response from server" + return True, None # Server will close the connection without reply - return _is_protocol_failure(data), None + return self.is_protocol_failure(data), None except Exception as e: - return False, str(e) + return False, e diff --git a/monitoring/uss_qualifier/resources/interuss/datastore/datastore_test.py b/monitoring/uss_qualifier/resources/interuss/datastore/datastore_test.py index ca5b1cc2cb..dab73247d1 100644 --- a/monitoring/uss_qualifier/resources/interuss/datastore/datastore_test.py +++ b/monitoring/uss_qualifier/resources/interuss/datastore/datastore_test.py @@ -1,51 +1,66 @@ import pytest from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.wait_strategies import LogMessageWaitStrategy -from . import DatastoreDBNode +from . import CockroachDBNode, YugabyteDBNode + +COCKROACHDB_IMAGE = "cockroachdb/cockroach:v24.1.3" +INSECURE_COCKROACHDB_IMAGE = "interuss/insecurecockroach:latest" +YUGABYTE_IMAGE = "interuss/yugabyte:2025.1.2.1-interuss" +INSECURE_YUGABYTE_IMAGE = "yugabytedb/yugabyte:2.25.2.0-b359" @pytest.fixture(scope="module") def good_cockroach(request): server = DockerContainer( - image="cockroachdb/cockroach:v24.1.3", + image=COCKROACHDB_IMAGE, ports=[26257], command="start-single-node", ) + server.waiting_for(LogMessageWaitStrategy("start_node_query")) server.start() - wait_for_logs(server, "start_node_query") - return DatastoreDBNode( + return CockroachDBNode( "test", server.get_container_host_ip(), server.get_exposed_port(26257) ) @pytest.fixture(scope="module") -def no_ssl_cockroach(request): +def not_running_cockroach(request): + return CockroachDBNode("test", "127.0.0.1", 1) + + +@pytest.fixture(scope="module") +def not_running_yugabyte(request): + return YugabyteDBNode("test", "127.0.0.1", 1) + + +@pytest.fixture(scope="module") +def no_tls_cockroach(request): server = DockerContainer( - image="cockroachdb/cockroach:v24.1.3", + image=COCKROACHDB_IMAGE, ports=[26257], command="start-single-node --insecure", ) + server.waiting_for(LogMessageWaitStrategy("start_node_query")) server.start() - wait_for_logs(server, "start_node_query") - return DatastoreDBNode( + return CockroachDBNode( "test", server.get_container_host_ip(), server.get_exposed_port(26257) ) @pytest.fixture(scope="module") -def old_ssl_cockroach(request): +def old_tls_cockroach(request): server = DockerContainer( - image="interuss/insecurecockroach:latest", + image=INSECURE_COCKROACHDB_IMAGE, ports=[26257], command="start-single-node", ) + server.waiting_for(LogMessageWaitStrategy("start_node_query")) server.start() - wait_for_logs(server, "start_node_query") - return DatastoreDBNode( + return CockroachDBNode( "test", server.get_container_host_ip(), server.get_exposed_port(26257) ) @@ -53,128 +68,206 @@ def old_ssl_cockroach(request): @pytest.fixture(scope="module") def good_yugabyte(request): server = DockerContainer( - image="yugabytedb/yugabyte:2.25.2.0-b359", - ports=[5433], - command='bash -c "bin/yugabyted cert generate_server_certs --base_dir /yugabyte/certs --hostnames `hostname` && bin/yugabyted start --secure --certs_dir=/yugabyte/certs/generated_certs/`hostname` --advertise_address=`hostname` --background=false"', + image=YUGABYTE_IMAGE, + ports=[7100], + command='bash -c "bin/yugabyted cert generate_server_certs --base_dir /yugabyte/certs --hostnames `hostname` && bin/yugabyted start --secure --certs_dir=/yugabyte/certs/generated_certs/`hostname` --advertise_address=`hostname` --background=false --tserver_flags=node_to_node_encryption_use_client_certificates=true --master_flags=node_to_node_encryption_use_client_certificates=true,use_node_to_node_encryption=true"', + ) + server.waiting_for( + LogMessageWaitStrategy("Data placement constraint successfully verified") ) server.start() - wait_for_logs(server, "Data placement constraint successfully verified") - return DatastoreDBNode( - "test", server.get_container_host_ip(), server.get_exposed_port(5433) + return YugabyteDBNode( + "test", server.get_container_host_ip(), server.get_exposed_port(7100) ) @pytest.fixture(scope="module") -def no_ssl_yugabyte(request): +def yugabyte_without_client_auth(request): server = DockerContainer( - image="yugabytedb/yugabyte:2.25.2.0-b359", - ports=[5433], - command="bin/yugabyted start --background=false", + image=INSECURE_YUGABYTE_IMAGE, + ports=[7100], + command='bash -c "bin/yugabyted cert generate_server_certs --base_dir /yugabyte/certs --hostnames `hostname` && bin/yugabyted start --secure --certs_dir=/yugabyte/certs/generated_certs/`hostname` --advertise_address=`hostname` --background=false"', + ) + server.waiting_for( + LogMessageWaitStrategy("Data placement constraint successfully verified") ) server.start() - wait_for_logs(server, "Data placement constraint successfully verified") - return DatastoreDBNode( - "test", server.get_container_host_ip(), server.get_exposed_port(5433) + return YugabyteDBNode( + "test", server.get_container_host_ip(), server.get_exposed_port(7100) ) @pytest.fixture(scope="module") -def old_ssl_yugabyte(request): - import base64 - - config = '{"tserver_flags": "ysql_pg_conf_csv=\\"ssl_min_protocol_version=\'TLSv1.1\'\\""}' - config = base64.b64encode(config.encode("utf-8")) # Avoid escaping hell - +def no_tls_yugabyte(request): server = DockerContainer( - image="yugabytedb/yugabyte:2.25.2.0-b359", - ports=[5433], - command=f'bash -c "echo {config.decode("utf-8")} | base64 -d > /conf.conf && cat /conf.conf && bin/yugabyted cert generate_server_certs --base_dir /yugabyte/certs --hostnames `hostname` && bin/yugabyted start --secure --certs_dir=/yugabyte/certs/generated_certs/`hostname` --advertise_address=`hostname` --background=false --conf /conf.conf"', + image=INSECURE_YUGABYTE_IMAGE, + ports=[7100], + command="bin/yugabyted start --background=false", + ) + server.waiting_for( + LogMessageWaitStrategy("Data placement constraint successfully verified") ) server.start() - wait_for_logs(server, "Data placement constraint successfully verified") - return DatastoreDBNode( - "test", server.get_container_host_ip(), server.get_exposed_port(5433) + return YugabyteDBNode( + "test", server.get_container_host_ip(), server.get_exposed_port(7100) ) def test_datastoredbmode_connect_good_cockroach(good_cockroach): is_reachable, _ = good_cockroach.is_reachable() - assert is_reachable + assert is_reachable, "Running CockroachDB server shall be reachable" def test_datastoredbmode_connect_good_yugabyte(good_yugabyte): is_reachable, _ = good_yugabyte.is_reachable() - assert is_reachable + assert is_reachable, "Running Yugabyte server shall be reachable" -def test_datastoredbmode_connect_no_ssl_cockroach(no_ssl_cockroach): - is_reachable, _ = no_ssl_cockroach.is_reachable() - assert is_reachable +def test_datastoredbmode_connect_yugabyte_without_client_auth( + yugabyte_without_client_auth, +): + is_reachable, _ = yugabyte_without_client_auth.is_reachable() + assert is_reachable, "Running Yugabyte server shall be reachable" -def test_datastoredbmode_connect_no_ssl_yugabyte(no_ssl_yugabyte): - is_reachable, _ = no_ssl_yugabyte.is_reachable() - assert is_reachable +def test_datastoredbmode_connect_no_tls_cockroach(no_tls_cockroach): + is_reachable, _ = no_tls_cockroach.is_reachable() + assert is_reachable, "Running CockroachDB server shall be reachable" -def test_datastoredbmode_connect_old_ssl_cockroach(old_ssl_cockroach): - is_reachable, _ = old_ssl_cockroach.is_reachable() - assert is_reachable +def test_datastoredbmode_connect_no_tls_yugabyte(no_tls_yugabyte): + is_reachable, _ = no_tls_yugabyte.is_reachable() + assert is_reachable, "Running Yugabyte server shall be reachable" -def test_datastoredbmode_connect_old_ssl_yugabyte(old_ssl_yugabyte): - is_reachable, _ = old_ssl_yugabyte.is_reachable() - assert is_reachable +def test_datastoredbmode_connect_not_running_cockroach(not_running_cockroach): + is_reachable, _ = not_running_cockroach.is_reachable() + assert not is_reachable, "Non-running CockroachDB server shall not be reachable" -def test_datastoredbmode_secure_mode_good_cockroach(good_cockroach): - is_secure, _ = good_cockroach.runs_in_secure_mode() - assert is_secure +def test_datastoredbmode_connect_not_running_yugabyte(not_running_yugabyte): + is_reachable, _ = not_running_yugabyte.is_reachable() + assert not is_reachable, "Non-running Yugabyte server shall not be reachable" -def test_datastoredbmode_secure_mode_good_yugabyte(good_yugabyte): - is_secure, _ = good_yugabyte.runs_in_secure_mode() - assert is_secure +def test_datastoredbmode_no_tls_rejected_good_cockroach(good_cockroach): + no_tls_rejected, _ = good_cockroach.no_tls_rejected() + assert no_tls_rejected, ( + "Nominal CockroachDB server shall reject connections without TLS" + ) -def test_datastoredbmode_secure_mode_no_ssl_cockroach(no_ssl_cockroach): - is_secure, _ = no_ssl_cockroach.runs_in_secure_mode() - assert not is_secure +def test_datastoredbmode_no_tls_rejected_good_yugabyte(good_yugabyte): + no_tls_rejected, _ = good_yugabyte.no_tls_rejected() + assert no_tls_rejected, ( + "Nominal Yugabyte server shall reject connections without TLS" + ) -def test_datastoredbmode_secure_mode_no_ssl_yugabyte(no_ssl_yugabyte): - is_secure, _ = no_ssl_yugabyte.runs_in_secure_mode() - assert not is_secure +def test_datastoredbmode_no_tls_rejected_yugabyte_without_client_auth( + yugabyte_without_client_auth, +): + no_tls_rejected, _ = yugabyte_without_client_auth.no_tls_rejected() + assert no_tls_rejected, ( + "Yugabyte server without mTLS shall still reject connections without TLS" + ) -def test_datastoredbmode_secure_mode_old_ssl_cockroach(old_ssl_cockroach): - is_secure, _ = old_ssl_cockroach.runs_in_secure_mode() - assert is_secure +def test_datastoredbmode_no_tls_rejected_no_tls_cockroach(no_tls_cockroach): + no_tls_rejected, _ = no_tls_cockroach.no_tls_rejected() + assert not no_tls_rejected, ( + "CockroachDB server, with TLS disabled, shall not reject connections without TLS" + ) -def test_datastoredbmode_secure_mode_old_ssl_yugabyte(old_ssl_yugabyte): - is_secure, _ = old_ssl_yugabyte.runs_in_secure_mode() - assert is_secure +def test_datastoredbmode_no_tls_rejected_no_tls_yugabyte(no_tls_yugabyte): + no_tls_rejected, _ = no_tls_yugabyte.no_tls_rejected() + assert not no_tls_rejected, ( + "Yugabyte server, with TLS disabled, shall not reject connections without TLS" + ) + + +def test_datastoredbmode_no_tls_rejected_old_tls_cockroach(old_tls_cockroach): + no_tls_rejected, _ = old_tls_cockroach.no_tls_rejected() + assert no_tls_rejected, ( + "CockroachDB server, with old TLS version enabled, shall not reject connections without TLS" + ) + + +def test_datastoredbmode_unauthenticated_rejected_good_cockroach(good_cockroach): + unauthenticated_rejected, _ = good_cockroach.unauthenticated_rejected() + assert unauthenticated_rejected, ( + "Nominal CockroachDB server shall reject unauthenticated connections" + ) + + +def test_datastoredbmode_unauthenticated_rejected_good_yugabyte(good_yugabyte): + unauthenticated_rejected, _ = good_yugabyte.unauthenticated_rejected() + assert unauthenticated_rejected, ( + "Nominal Yugabyte server shall reject unauthenticated connections" + ) + + +def test_datastoredbmode_unauthenticated_rejected_yugabyte_without_client_auth( + yugabyte_without_client_auth, +): + unauthenticated_rejected, _ = ( + yugabyte_without_client_auth.unauthenticated_rejected() + ) + assert not unauthenticated_rejected, ( + "Yugabyte without mTLS shall accept unauthenticated requests (since mTLS is the authentication method)" + ) + + +def test_datastoredbmode_unauthenticated_rejected_no_tls_cockroach(no_tls_cockroach): + unauthenticated_rejected, _ = no_tls_cockroach.unauthenticated_rejected() + assert unauthenticated_rejected, ( + "CockroachDB server, with TLS disabled, shall reject authenticated requests" + ) + + +def test_datastoredbmode_unauthenticated_rejected_no_tls_yugabyte(no_tls_yugabyte): + unauthenticated_rejected, _ = no_tls_yugabyte.unauthenticated_rejected() + assert not unauthenticated_rejected, ( + "Yugabyte without TLS shall accept unauthenticated requests (since mTLS is the authentication method)" + ) + + +def test_datastoredbmode_unauthenticated_rejected_old_tls_cockroach(old_tls_cockroach): + unauthenticated_rejected, _ = old_tls_cockroach.unauthenticated_rejected() + assert unauthenticated_rejected, ( + "CockroachDB server, with old TLS version enabled, shall reject authenticated requests" + ) def test_datastoredbmode_reject_legacy_good_cockroach(good_cockroach): - legacy_rejected, _ = good_cockroach.legacy_ssl_version_rejected() - assert legacy_rejected + legacy_rejected, _ = good_cockroach.legacy_tls_version_rejected() + assert legacy_rejected, ( + "Nominal CockroachDB server shall reject connections wtih legacy TLS version" + ) def test_datastoredbmode_reject_legacy_good_yugabyte(good_yugabyte): - legacy_rejected, _ = good_yugabyte.legacy_ssl_version_rejected() - assert legacy_rejected + legacy_rejected, _ = good_yugabyte.legacy_tls_version_rejected() + assert legacy_rejected, ( + "Nominal Yugabyte server shall reject connections wtih legacy TLS version" + ) -def test_datastoredbmode_reject_legacy_old_ssl_cockroach(old_ssl_cockroach): - legacy_rejected, _ = old_ssl_cockroach.legacy_ssl_version_rejected() - assert not legacy_rejected +def test_datastoredbmode_reject_legacy_yugabyte_without_client_auth( + yugabyte_without_client_auth, +): + legacy_rejected, _ = yugabyte_without_client_auth.legacy_tls_version_rejected() + assert legacy_rejected, ( + "Yugabyte without mTLS shall reject connections wtih legacy TLS version" + ) -def test_datastoredbmode_reject_legacy_old_ssl_yugabyte(old_ssl_yugabyte): - legacy_rejected, _ = old_ssl_yugabyte.legacy_ssl_version_rejected() - assert not legacy_rejected +def test_datastoredbmode_reject_legacy_old_tls_cockroach(old_tls_cockroach): + legacy_rejected, _ = old_tls_cockroach.legacy_tls_version_rejected() + assert not legacy_rejected, ( + "CockroachDB server, with old TLS version enabled, shall not reject connections wtih legacy TLS versions" + ) diff --git a/monitoring/uss_qualifier/resources/interuss/flight_authorization/definitions.py b/monitoring/uss_qualifier/resources/interuss/flight_authorization/definitions.py index b45d017cf2..a2f9ad6f25 100644 --- a/monitoring/uss_qualifier/resources/interuss/flight_authorization/definitions.py +++ b/monitoring/uss_qualifier/resources/interuss/flight_authorization/definitions.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import StrEnum from implicitdict import ImplicitDict @@ -8,7 +8,7 @@ ) -class AcceptanceExpectation(str, Enum): +class AcceptanceExpectation(StrEnum): MustBeRejected = "MustBeRejected" """When a flight planner service provider is requested to accept the flight described in this step, the service provider must decline to create the flight. Accepting the flight successfully will cause a failed check.""" @@ -19,7 +19,7 @@ class AcceptanceExpectation(str, Enum): """The service provider may choose to accept the flight or not. Presumably this option would be accompanied by a specific conditions_expectation to ensure that conditions were present (or absent) if the flight were accepted.""" -class ConditionsExpectation(str, Enum): +class ConditionsExpectation(StrEnum): Irrelevant = "Irrelevant" """Whether conditions accompanying the flight planning attempt are present is irrelevant to this feature check.""" diff --git a/monitoring/uss_qualifier/resources/interuss/geospatial_map/README.md b/monitoring/uss_qualifier/resources/interuss/geospatial_map/README.md new file mode 100644 index 0000000000..9f82a5d011 --- /dev/null +++ b/monitoring/uss_qualifier/resources/interuss/geospatial_map/README.md @@ -0,0 +1,12 @@ +# geospatial_map automated testing + +The resources in this folder are primarily intended to be used in the [scenarios.interuss.geospatial_map.GeospatialFeatureComprehension](../../../scenarios/interuss/geospatial_map/geospatial_feature_comprehension.md) test scenario. + +When designing a FeatureCheckTable that is generated at runtime, the [generate_featurechecktable.py](./generate_featurechecktable.py) utility can be used to ease development. It accepts a path to a dict-like file containing a [FeatureCheckTableSpecification](./definitions.py) -- subelements of a file can be selected with an anchor like `#/foo/bar` -- and prints the generated FeatureCheckTable in YAML. So, for instance, to see the FeatureCheckTable generated by the `generated_feature_check_table` resource in the [library resources.yaml](../../../configurations/dev/library/resources.yaml), run (from the repo root): + +```shell +make image +docker container run -it interuss/monitoring uv run \ + ./uss_qualifier/resources/interuss/geospatial_map/generate_featurechecktable.py \ + --config-path file://./uss_qualifier/configurations/dev/library/resources.yaml#/generated_feature_check_table/specification +``` diff --git a/monitoring/uss_qualifier/resources/interuss/geospatial_map/definitions.py b/monitoring/uss_qualifier/resources/interuss/geospatial_map/definitions.py index 5ecbd834a0..e2df623361 100644 --- a/monitoring/uss_qualifier/resources/interuss/geospatial_map/definitions.py +++ b/monitoring/uss_qualifier/resources/interuss/geospatial_map/definitions.py @@ -1,11 +1,11 @@ -from enum import Enum +from enum import StrEnum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.monitorlib.geotemporal import Volume4DTemplateCollection -class ExpectedFeatureCheckResult(str, Enum): +class ExpectedFeatureCheckResult(StrEnum): Block = "Block" """When a service provider being tested as a geospatial map provider is queried for whether any features are present for the specified volumes that would cause the flight described in this feature check to be blocked, the service provider must respond affirmatively; responding negatively will cause a failed check.""" @@ -26,7 +26,7 @@ class FeatureCheck(ImplicitDict): description: str """Human-readable test step description to aid in the debugging and traceability.""" - operation_rule_set: str | None = None + operation_rule_set: Optional[str] = None """The set of operating rules (or rule set) under which the operation described in the feature check should be performed.""" volumes: Volume4DTemplateCollection @@ -35,7 +35,7 @@ class FeatureCheck(ImplicitDict): A service provider is expected to provide geospatial features relevant to any of the entire area specified and for any of the entire time specified. """ - restriction_source: str | None = None + restriction_source: Optional[str] = None """Which source for geospatial features describing restrictions should be considered when looking for the expected outcome.""" expected_result: ExpectedFeatureCheckResult diff --git a/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table.py b/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table.py index 3acdad1da1..5d6f22c666 100644 --- a/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table.py +++ b/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table.py @@ -1,13 +1,85 @@ -from implicitdict import ImplicitDict +import json +import os +import _jsonnet +import arrow +from implicitdict import ImplicitDict, Optional + +from monitoring.uss_qualifier.resources.files import ( + ExternalFile, + load_content, + load_dict, +) from monitoring.uss_qualifier.resources.interuss.geospatial_map.definitions import ( FeatureCheckTable, ) from monitoring.uss_qualifier.resources.resource import Resource +class FeatureCheckTableGenerationSpecification(ImplicitDict): + dict_resources: Optional[dict[str, ExternalFile]] + """External dict-like content (.json, .yaml, .jsonnet) to load and make available to the script. + Key defines the name of the resource accessible to the script. + """ + + jsonnet_script: ExternalFile + """Source of Jsonnet "script" that produces a FeatureCheckTable. + + This converter may access resources with std.extVars("resource_name"). Also, std.extVars("now") returns the current + datetime as an ISO string. The return value of the Jsonnet must be an object following the FeatureCheckTable + schema. `import` is not allowed. The following additional functions are available: + * std.native("timestamp_of")(s: str) -> float + * s: Datetime string + * Returns: Seconds past epoch + """ + + class FeatureCheckTableSpecification(ImplicitDict): - table: FeatureCheckTable + table: Optional[FeatureCheckTable] + """Statically-defined feature check table""" + + generate_at_runtime: Optional[FeatureCheckTableGenerationSpecification] + """Generate feature check table at runtime""" + + +def generate_feature_check_table( + spec: FeatureCheckTableGenerationSpecification, +) -> FeatureCheckTable: + # Load dict resources + if "dict_resources" in spec and spec.dict_resources: + resources = { + k: json.dumps(load_dict(v)) for k, v in spec.dict_resources.items() + } + else: + resources = {} + + # Useful functions not included in Jsonnet + native_callbacks = {"timestamp_of": (("s",), lambda s: arrow.get(s).timestamp())} + + # Useful variables not included in Jsonnet + ext_vars = {"now": arrow.utcnow().isoformat()} + + # Behavior when Jsonnet attempts to import something + def jsonnet_import_callback(folder: str, rel: str) -> tuple[str, bytes]: + raise ValueError("Jsonnet to generate FeatureCheckTable may not use `import`") + + # Load Jsonnet "script" + jsonnet_string = load_content(spec.jsonnet_script) + + # Run Jsonnet "script" + file_name = os.path.split(spec.jsonnet_script.path)[-1] + json_str = _jsonnet.evaluate_snippet( + file_name, + jsonnet_string, + ext_codes=resources, + ext_vars=ext_vars, + import_callback=jsonnet_import_callback, + native_callbacks=native_callbacks, # pyright: ignore [reportArgumentType] + ) + + # Parse output into FeatureCheckTable + dict_content = json.loads(json_str) + return ImplicitDict.parse(dict_content, FeatureCheckTable) class FeatureCheckTableResource(Resource[FeatureCheckTableSpecification]): @@ -17,4 +89,11 @@ def __init__( self, specification: FeatureCheckTableSpecification, resource_origin: str ): super().__init__(specification, resource_origin) - self.table = specification.table + if "table" in specification and specification.table: + self.table = specification.table + elif ( + "generate_at_runtime" in specification and specification.generate_at_runtime + ): + self.table = generate_feature_check_table(specification.generate_at_runtime) + else: + raise ValueError("No means to define FeatureCheckTable was specified") diff --git a/monitoring/uss_qualifier/resources/interuss/geospatial_map/generate_featurechecktable.py b/monitoring/uss_qualifier/resources/interuss/geospatial_map/generate_featurechecktable.py new file mode 100644 index 0000000000..ba56bbd4c7 --- /dev/null +++ b/monitoring/uss_qualifier/resources/interuss/geospatial_map/generate_featurechecktable.py @@ -0,0 +1,38 @@ +import argparse +import json +import sys + +import yaml +from implicitdict import ImplicitDict + +from monitoring.uss_qualifier.resources.files import ExternalFile, load_dict +from monitoring.uss_qualifier.resources.interuss.geospatial_map import ( + FeatureCheckTableResource, +) +from monitoring.uss_qualifier.resources.interuss.geospatial_map.feature_check_table import ( + FeatureCheckTableSpecification, +) + + +def parse_args(argv: list[str]): + parser = argparse.ArgumentParser(description="Generate a FeatureCheckTable ") + parser.add_argument( + "--config-path", + action="store", + dest="config_path", + type=str, + help="Path to dict-like file containing a FeatureCheckTableSpecification (may have an anchor suffix like #/foo/bar)", + ) + return parser.parse_args(argv) + + +def generate_table(path: str): + spec_dict = load_dict(ExternalFile(path=path)) + spec = ImplicitDict.parse(spec_dict, FeatureCheckTableSpecification) + resource = FeatureCheckTableResource(specification=spec, resource_origin="Script") + print(yaml.dump(json.loads(json.dumps(resource.table)), indent=2)) + + +if __name__ == "__main__": + args = parse_args(sys.argv[1:]) + generate_table(args.config_path) diff --git a/monitoring/uss_qualifier/resources/interuss/mock_uss/client.py b/monitoring/uss_qualifier/resources/interuss/mock_uss/client.py index 39d9e88bec..6d5594f196 100644 --- a/monitoring/uss_qualifier/resources/interuss/mock_uss/client.py +++ b/monitoring/uss_qualifier/resources/interuss/mock_uss/client.py @@ -1,4 +1,7 @@ -from implicitdict import ImplicitDict, StringBasedDateTime +from datetime import datetime + +import arrow +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from monitoring.monitorlib import fetch from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient @@ -14,7 +17,10 @@ PutLocalityRequest, ) from monitoring.monitorlib.fetch import QueryError, QueryType -from monitoring.monitorlib.infrastructure import AuthAdapter, UTMClientSession +from monitoring.monitorlib.infrastructure import ( + AuthAdapter, + utm_client_session_factory, +) from monitoring.monitorlib.locality import LocalityCode from monitoring.monitorlib.scd_automated_testing.scd_injection_api import ( SCOPE_SCD_QUALIFIER_INJECT, @@ -39,11 +45,16 @@ def __init__( timeout_seconds: float | None = None, ): self.base_url = base_url - self.session = UTMClientSession(base_url, auth_adapter, timeout_seconds) + self.session = utm_client_session_factory.get_session( + base_url, auth_adapter, timeout_seconds + ) self.participant_id = participant_id v1_base_url = base_url + "/flight_planning/v1" self.flight_planner = V1FlightPlannerClient( - UTMClientSession(v1_base_url, auth_adapter, timeout_seconds), participant_id + utm_client_session_factory.get_session( + v1_base_url, auth_adapter, timeout_seconds + ), + participant_id, ) def get_status(self) -> fetch.Query: @@ -82,6 +93,22 @@ def set_locality(self, locality_code: LocalityCode) -> fetch.Query: json=PutLocalityRequest(locality_code=locality_code), ) + def get_clock(self) -> tuple[datetime | None, fetch.Query]: + query = fetch.query_and_describe( + self.session, + "GET", + "/clock", + participant_id=self.participant_id, + query_type=QueryType.InterUSSMockUSSGetClock, + ) + try: + result = ( + arrow.get(query.response.body).datetime if query.response.body else None + ) + except arrow.ParserError: + result = None + return result, query + # TODO: Add other methods to interact with the mock USS in other ways (like starting/stopping message signing data collection) def get_interactions( @@ -136,7 +163,7 @@ class MockUSSSpecification(ImplicitDict): participant_id: ParticipantID """Test participant responsible for this mock USS.""" - timeout_seconds: float | None = None + timeout_seconds: Optional[float] = None """Number of seconds to allow for requests to this mock_uss instance. If None, use default.""" diff --git a/monitoring/uss_qualifier/resources/interuss/query_behavior.py b/monitoring/uss_qualifier/resources/interuss/query_behavior.py index 0259a5004a..59f24fcb90 100644 --- a/monitoring/uss_qualifier/resources/interuss/query_behavior.py +++ b/monitoring/uss_qualifier/resources/interuss/query_behavior.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from loguru import logger from monitoring.monitorlib.fetch import settings @@ -6,19 +6,19 @@ class QueryBehaviorSpecification(ImplicitDict): - connect_timeout_seconds: float | None + connect_timeout_seconds: Optional[float] """Number of seconds to allow for establishing a connection. Use 0 for no timeout.""" - read_timeout_seconds: float | None + read_timeout_seconds: Optional[float] """Number of seconds to allow for a request to complete after establishing a connection. Use 0 for no timeout.""" - attempts: int | None + attempts: Optional[int] """Number of attempts to query when experiencing a retryable error like a timeout""" - add_request_id: bool | None + add_request_id: Optional[bool] """Whether to automatically add a `request_id` field to any request with a JSON body and no pre-existing `request_id` field""" - fake_netlocs: list[str] | None + fake_netlocs: Optional[list[str]] """Network locations well-known to be fake and for which a request should fail immediately without being attempted.""" diff --git a/monitoring/uss_qualifier/resources/interuss/uss_identification.py b/monitoring/uss_qualifier/resources/interuss/uss_identification.py index 6fb0b82ed6..c35fa1110c 100644 --- a/monitoring/uss_qualifier/resources/interuss/uss_identification.py +++ b/monitoring/uss_qualifier/resources/interuss/uss_identification.py @@ -1,27 +1,35 @@ import re -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional +from loguru import logger -from monitoring.monitorlib.fetch import Query +from monitoring.monitorlib.fetch import Query, QueryType from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.resource import Resource class AccessTokenIdentifier(ImplicitDict): - issuer: str | None + issuer: Optional[str] """If specified, this identifier only applies to access tokens from this issuer. If not specified, this identifier applies to any access token.""" - subject: str | None + subject: Optional[str] """If specified, assume the participant is responsible for applicable access tokens containing this subject.""" class USSIdentifiers(ImplicitDict): - astm_url_regexes: list[str] | None - """If a URL to an ASTM (F3411, F3548, etc) endpoint matches one of these regular expressions, assume the participant is responsible for that server""" + server_url_regexes: Optional[list[str]] + """If a URL to an endpoint matches one of these regular expressions, assume the participant is responsible for that server""" - access_tokens: list[AccessTokenIdentifier] | None + access_tokens: Optional[list[AccessTokenIdentifier]] """If an access token matches one of these identifiers, assume the participant is responsible for that access token""" + def matches_server_url(self, url: str) -> bool: + if "server_url_regexes" in self and self.server_url_regexes: + for url_regex in self.server_url_regexes: + if re.fullmatch(url_regex, url): + return True + return False + class USSIdentificationSpecification(ImplicitDict): uss_identifiers: dict[ParticipantID, USSIdentifiers] @@ -40,7 +48,8 @@ def __init__( super().__init__(specification, resource_origin) self.identifiers = specification.uss_identifiers or {} - def attribute_query(self, query: Query) -> None: + def attribute_query_server(self, query: Query) -> None: + """Identify the participant ID of the server responding to this query and mutate `query` accordingly, if possible""" claims = query.request.token if "error" in claims and len(claims) == 1: claims = None @@ -48,10 +57,58 @@ def attribute_query(self, query: Query) -> None: for participant_id, identifiers in self.identifiers.items(): attribute_to_participant = False - if "astm_url_regexes" in identifiers and identifiers.astm_url_regexes: - for url_regex in identifiers.astm_url_regexes: - if re.fullmatch(url_regex, query.request.url): - attribute_to_participant = True + if "server_url_regexes" in identifiers and identifiers.server_url_regexes: + # The participant is responsible for the server end of the query when the query URL matches one of the participant's + if identifiers.matches_server_url(query.request.url): + attribute_to_participant = True + + if attribute_to_participant: + query.participant_id = participant_id + + def identify_query_client(self, query: Query) -> ParticipantID | None: + """Identify the participant ID of the client making this query if possible""" + claims = query.request.token + if "error" in claims and len(claims) == 1: + claims = None + + query_type = query.query_type if "query_type" in query else None + if query_type == QueryType.F3411v22aUSSPostIdentificationServiceArea: + url = ( + (query.request.json or {}) + .get("service_area", {}) + .get("uss_base_url", None) + ) + elif query_type == QueryType.F3411v19USSPostIdentificationServiceArea: + url = ( + (query.request.json or {}) + .get("service_area", {}) + .get("flights_url", None) + ) + elif query_type == QueryType.F3548v21USSNotifyOperationalIntentDetailsChanged: + url = ( + (query.request.json or {}) + .get("operational_intent", {}) + .get("reference", {}) + .get("uss_base_url", None) + ) + elif query_type == QueryType.F3548v21USSNotifyConstraintDetailsChanged: + url = ( + (query.request.json or {}) + .get("constraint", {}) + .get("reference", {}) + .get("uss_base_url", None) + ) + else: + url = None + + matching_participants = [] + for participant_id, identifiers in self.identifiers.items(): + attribute_to_participant = False + + if "server_url_regexes" in identifiers and identifiers.server_url_regexes: + # The participant is responsible for the client end of the query when the request body is a payload specifying a callback server operated by the participant + if url and identifiers.matches_server_url(url): + attribute_to_participant = True if "access_tokens" in identifiers and identifiers.access_tokens: for access_token_identifier in identifiers.access_tokens: @@ -70,4 +127,15 @@ def attribute_query(self, query: Query) -> None: attribute_to_participant = True if attribute_to_participant: - query.participant_id = participant_id + matching_participants.append(participant_id) + + if len(matching_participants) == 1: + return matching_participants[0] + elif len(matching_participants) > 1: + logger.warning( + "Multiple participants {} match as clients for query {} {}", + ", ".join(matching_participants), + query.request.method, + query.request.url, + ) + return None diff --git a/monitoring/uss_qualifier/resources/netrid/flight_data.py b/monitoring/uss_qualifier/resources/netrid/flight_data.py index 0408374412..6c73f321ae 100644 --- a/monitoring/uss_qualifier/resources/netrid/flight_data.py +++ b/monitoring/uss_qualifier/resources/netrid/flight_data.py @@ -1,4 +1,9 @@ -from implicitdict import ImplicitDict, StringBasedDateTime, StringBasedTimeDelta +from implicitdict import ( + ImplicitDict, + Optional, + StringBasedDateTime, + StringBasedTimeDelta, +) from uas_standards.interuss.automated_testing.rid.v1 import injection from monitoring.uss_qualifier.resources.files import ExternalFile @@ -30,7 +35,7 @@ class AdjacentCircularFlightsSimulatorConfiguration(ImplicitDict): relative to a time close to the time of test. """ - random_seed: int | None = 12345 + random_seed: Optional[int] = 12345 """Pseudorandom seed that should be used, or specify None to use default Random.""" minx: float = 7.4735784530639648 @@ -63,7 +68,7 @@ class FlightDataKMLFileConfiguration(ImplicitDict): relative to a time close to the time of test. """ - random_seed: int | None = 12345 + random_seed: Optional[int] = 12345 """Pseudorandom seed that should be used, or specify None to use default Random.""" kml_file: ExternalFile @@ -74,13 +79,13 @@ class FlightDataSpecification(ImplicitDict): flight_start_delay: StringBasedTimeDelta = StringBasedTimeDelta("15s") """Amount of time between starting the test and commencement of flights""" - record_source: ExternalFile | None + record_source: Optional[ExternalFile] """When this field is populated, flight record data will be loaded directly from this file""" - kml_source: FlightDataKMLFileConfiguration | None + kml_source: Optional[FlightDataKMLFileConfiguration] """When this field is populated, flight data will be generated from a KML file""" - adjacent_circular_flights_simulation_source: ( - AdjacentCircularFlightsSimulatorConfiguration | None - ) + adjacent_circular_flights_simulation_source: Optional[ + AdjacentCircularFlightsSimulatorConfiguration + ] """When this field is populated, flight data will be simulated with the AdjacentCircularFlightsSimulator""" diff --git a/monitoring/uss_qualifier/resources/netrid/flight_data_resources.py b/monitoring/uss_qualifier/resources/netrid/flight_data_resources.py index 5616325428..d243a5404a 100644 --- a/monitoring/uss_qualifier/resources/netrid/flight_data_resources.py +++ b/monitoring/uss_qualifier/resources/netrid/flight_data_resources.py @@ -5,14 +5,16 @@ from typing import Self import arrow -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from uas_standards.interuss.automated_testing.rid.v1.injection import ( RIDAircraftState, TestFlightDetails, ) +from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.rid_automated_testing.injection_api import TestFlight from monitoring.uss_qualifier.resources.files import load_content, load_dict +from monitoring.uss_qualifier.resources.netrid.evaluation import EvaluationConfiguration from monitoring.uss_qualifier.resources.netrid.flight_data import ( FlightDataSpecification, FlightRecordCollection, @@ -24,6 +26,7 @@ get_flight_records, ) from monitoring.uss_qualifier.resources.resource import Resource +from monitoring.uss_qualifier.scenarios.scenario import TestScenario class FlightDataResource(Resource[FlightDataSpecification]): @@ -58,6 +61,8 @@ def __init__(self, specification: FlightDataSpecification, resource_origin: str) ) self._flight_start_delay = specification.flight_start_delay.timedelta + self._validate_flights() + def get_test_flights(self) -> list[TestFlight]: t0 = arrow.utcnow() + self._flight_start_delay @@ -179,12 +184,56 @@ def drop_every_n_state(self, n: int) -> Self: flight.states = flight.states[::n] return self_copy + def _validate_flights(self): + """Ensure that loaded flights are valid""" + + for flight in self.flight_collection.flights: + self._validate_flight(flight) + + def _validate_flight(self, flight): + """Ensure flight data is valid""" + + from monitoring.uss_qualifier.scenarios.astm.netrid.common_dictionary_evaluator import ( + RIDCommonDictionaryEvaluator, + ) # Circular import + + # We pass the flight throught a "normal" TestFlight (some processing is + # done inside) + details = TestFlightDetails( + effective_after=StringBasedDateTime(arrow.utcnow()), + details=flight.flight_details, + ) + + test_flight = TestFlight( + injection_id=str(uuid.uuid4()), + telemetry=flight.states[::], + details_responses=[details], + aircraft_type=flight.aircraft_type, + filter_invalid_telemetry=False, + ) + + class DummyTestScenario(TestScenario): + def __init__(self): + pass + + def run(self, *args, **kwargs): + pass + + # We ask the evaluator to process it + evaluator = RIDCommonDictionaryEvaluator( + EvaluationConfiguration(), DummyTestScenario(), RIDVersion.f3411_22a + ) + for state in test_flight.raw_telemetry or []: + evaluator.evaluate_injected_flight( + state, test_flight, test_flight.details_responses[0].details + ) + class FlightDataStorageSpecification(ImplicitDict): - flight_record_collection_path: str | None + flight_record_collection_path: Optional[str] """Path, usually ending with .json, at which to store the FlightRecordCollection""" - geojson_tracks_path: str | None + geojson_tracks_path: Optional[str] """Path (folder) in which to store track_XX.geojson files, 1 for each flight""" diff --git a/monitoring/uss_qualifier/resources/netrid/flight_data_resources_test.py b/monitoring/uss_qualifier/resources/netrid/flight_data_resources_test.py new file mode 100644 index 0000000000..1511c8d01e --- /dev/null +++ b/monitoring/uss_qualifier/resources/netrid/flight_data_resources_test.py @@ -0,0 +1,97 @@ +import pytest + +from monitoring.uss_qualifier.resources.files import ExternalFile + +from .flight_data import ( + AdjacentCircularFlightsSimulatorConfiguration, + FlightDataKMLFileConfiguration, + FlightDataSpecification, +) +from .flight_data_resources import FlightDataResource + + +def test_unknown_type(): + specs = FlightDataSpecification() + + with pytest.raises(ValueError): + FlightDataResource(specs, "test") + + +def test_record(): + specs = FlightDataSpecification( + record_source=ExternalFile(path="file://./test_data/test/zurich.json") + ) + FlightDataResource(specs, "test") + + +def test_invalid_record(): + for file in [ + "test/invalid_wrong_ua_type.json", + "test/invalid_no_timestamp.json", + "test/invalid_wrong_timestamp.json", + "test/invalid_no_timestamp_accuracy.json", + "test/invalid_wrong_timestamp_accuracy.json", + "test/invalid_wrong_operational_status.json", + "test/invalid_no_alt.json", + "test/invalid_no_accuracy_v.json", + "test/invalid_wrong_accuracy_v.json", + "test/invalid_no_accuracy_h.json", + "test/invalid_wrong_accuracy_h.json", + "test/invalid_no_speed_accuracy.json", + "test/invalid_wrong_speed_accuracy.json", + "test/invalid_no_vertical_speed.json", + "test/invalid_wrong_vertical_speed.json", + "test/invalid_no_speed.json", + "test/invalid_wrong_speed.json", + "test/invalid_no_track.json", + "test/invalid_wrong_track.json", + "test/invalid_no_height.json", + "test/invalid_no_height_type.json", + "test/invalid_wrong_height_type.json", + "test/invalid_no_uas_id.json", + "test/invalid_wrong_serial_number.json", + "test/invalid_wrong_registration_id.json", + "test/invalid_wrong_utm_id.json", + ]: + specs = FlightDataSpecification( + record_source=ExternalFile(path=f"file://./test_data/{file}") + ) + with pytest.raises(Exception): + FlightDataResource(specs, "test") + + +def test_kmls(): + # We test all known KMLs, who should be valid + for file in ["usa/netrid/dcdemo.kml", "usa/kentland/rid.kml", "che/rid/zurich.kml"]: + specs = FlightDataSpecification( + kml_source=FlightDataKMLFileConfiguration( + kml_file=ExternalFile(path=f"file://./test_data/{file}") + ) + ) + FlightDataResource(specs, "test") + + +def test_invalid_kmls(): + # accuracy_h is a field in position, speed_accuracy in state. Notice it's + # hard to generate invalid values from others fields as kml parser will + # crash / generate most of them + for file in [ + "test/invalid_no_accuracy_h.kml", + "test/invalid_no_speed_accuracy.kml", + "test/invalid_wrong_serial_number.kml", + "test/invalid_wrong_ua_type.kml", + ]: + specs = FlightDataSpecification( + kml_source=FlightDataKMLFileConfiguration( + kml_file=ExternalFile(path=f"file://./test_data/{file}") + ) + ) + with pytest.raises(Exception): + FlightDataResource(specs, "test") + + +def test_adjacent_circular_flights_simuation_source(): + specs = FlightDataSpecification( + adjacent_circular_flights_simulation_source=AdjacentCircularFlightsSimulatorConfiguration() + ) + FlightDataResource(specs, "test") diff --git a/monitoring/uss_qualifier/resources/netrid/observers.py b/monitoring/uss_qualifier/resources/netrid/observers.py index 9d8dd3e550..67def4c8ce 100644 --- a/monitoring/uss_qualifier/resources/netrid/observers.py +++ b/monitoring/uss_qualifier/resources/netrid/observers.py @@ -8,7 +8,6 @@ from monitoring.monitorlib import fetch, infrastructure from monitoring.monitorlib.fetch import QueryType -from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.uss_qualifier.resources.communications import AuthAdapterResource from monitoring.uss_qualifier.resources.resource import Resource @@ -24,7 +23,9 @@ def __init__( base_url: str, auth_adapter: infrastructure.AuthAdapter, ): - self.session = UTMClientSession(base_url, auth_adapter) + self.session = infrastructure.utm_client_session_factory.get_session( + base_url, auth_adapter + ) self.participant_id = participant_id self.base_url = base_url diff --git a/monitoring/uss_qualifier/resources/netrid/service_area.py b/monitoring/uss_qualifier/resources/netrid/service_area.py index c00b5279a9..128da6711d 100644 --- a/monitoring/uss_qualifier/resources/netrid/service_area.py +++ b/monitoring/uss_qualifier/resources/netrid/service_area.py @@ -1,9 +1,11 @@ import datetime from typing import Any -from implicitdict import ImplicitDict, StringBasedDateTime +import arrow +from implicitdict import ImplicitDict -from monitoring.monitorlib.geo import LatLngPoint +from monitoring.monitorlib.temporal import TestTimeContext, Time +from monitoring.uss_qualifier.resources import VolumeResource from monitoring.uss_qualifier.resources.resource import Resource @@ -15,35 +17,78 @@ class ServiceAreaSpecification(ImplicitDict): This URL will probably not identify a real resource in tests.""" - footprint: list[LatLngPoint] - """2D outline of service area""" - altitude_min: float = 0 - """Lower altitude bound of service area, meters above WGS84 ellipsoid""" - - altitude_max: float = 3048 - """Upper altitude bound of service area, meters above WGS84 ellipsoid""" - - reference_time: StringBasedDateTime - """Reference time used to adjust start and end times at runtime""" - - time_start: StringBasedDateTime - """Start time of service area (relative to reference_time)""" +class ServiceAreaResource(Resource[ServiceAreaSpecification]): + specification: ServiceAreaSpecification + _volume: VolumeResource + + def __init__( + self, + specification: ServiceAreaSpecification, + resource_origin: str, + volume: VolumeResource, + ): + super().__init__(specification, resource_origin) + self.specification = specification + self._volume = volume - time_end: StringBasedDateTime - """End time of service area (relative to reference_time)""" + now = Time(arrow.utcnow().datetime) + resolved_for_tests = self._volume.specification.template.resolve( + TestTimeContext.all_times_are(now) + ) - def shifted_time_start( - self, new_reference_time: datetime.datetime - ) -> datetime.datetime: - dt = new_reference_time - self.reference_time.datetime - return self.time_start.datetime + dt + if ( + resolved_for_tests.volume.altitude_lower is None + or resolved_for_tests.volume.altitude_upper is None + ): + raise ValueError( + f"In order to be usable for a ServiceAreaResource, the provided VolumeResource must declare altitude bounds. The volume template was obtained from: {resource_origin}" + ) + + if resolved_for_tests.time_start is None or resolved_for_tests.time_end is None: + raise ValueError( + f"In order to be usable for a ServiceAreaResource, the provided VolumeResource must declare time bounds. The volume template was obtained from: {resource_origin}" + ) + + def s2_vertices(self): + return self._volume.specification.s2_vertices() + + @property + def altitude_min(self) -> float: + """Lower altitude bound of service area, meters above WGS84 ellipsoid""" + v3d = self._volume.specification.template.resolve_3d() + if v3d.altitude_lower is None: + # Note this should not happen as we check at construction time that this bound exists + raise ValueError( + "The underlying volume does not have a lower altitude bound" + ) + return v3d.altitude_lower.to_w84_m() + + @property + def altitude_max(self) -> float: + """Upper altitude bound of service area, meters above WGS84 ellipsoid""" + v3d = self._volume.specification.template.resolve_3d() + if v3d.altitude_upper is None: + # Note this should not happen as we check at construction time that this bound exists + raise ValueError( + "The underlying volume does not have a lower altitude bound" + ) + return v3d.altitude_upper.to_w84_m() + + def resolved_time_bounds( + self, context: TestTimeContext + ) -> tuple[datetime.datetime, datetime.datetime]: + time_start, time_end = self._volume.specification.template.resolve_times( + context + ) + if time_start is None or time_end is None: + # Note this should not happen as we check at construction time that these bounds exist + raise ValueError("The underlying volume does not have time bounds") + return time_start.datetime, time_end.datetime - def shifted_time_end( - self, new_reference_time: datetime.datetime - ) -> datetime.datetime: - dt = new_reference_time - self.reference_time.datetime - return self.time_end.datetime + dt + @property + def base_url(self) -> str: + return self.specification.base_url def get_new_subscription_params( self, sub_id: str, start_time: datetime.datetime, duration: datetime.timedelta @@ -54,18 +99,10 @@ def get_new_subscription_params( """ return dict( sub_id=sub_id, - area_vertices=[vertex.as_s2sphere() for vertex in self.footprint], + area_vertices=self.s2_vertices(), alt_lo=self.altitude_min, alt_hi=self.altitude_max, start_time=start_time, end_time=start_time + duration, uss_base_url=self.base_url, ) - - -class ServiceAreaResource(Resource[ServiceAreaSpecification]): - specification: ServiceAreaSpecification - - def __init__(self, specification: ServiceAreaSpecification, resource_origin: str): - super().__init__(specification, resource_origin) - self.specification = specification diff --git a/monitoring/uss_qualifier/resources/netrid/service_providers.py b/monitoring/uss_qualifier/resources/netrid/service_providers.py index 8654a4eea1..4e2e55929a 100644 --- a/monitoring/uss_qualifier/resources/netrid/service_providers.py +++ b/monitoring/uss_qualifier/resources/netrid/service_providers.py @@ -50,7 +50,7 @@ def __init__( ): self.participant_id = participant_id self.injection_base_url = injection_base_url - self.injection_client = infrastructure.UTMClientSession( + self.injection_client = infrastructure.utm_client_session_factory.get_session( injection_base_url, auth_adapter ) diff --git a/monitoring/uss_qualifier/resources/netrid/simulation/README.md b/monitoring/uss_qualifier/resources/netrid/simulation/README.md index 8dbe13424a..ccd422a638 100644 --- a/monitoring/uss_qualifier/resources/netrid/simulation/README.md +++ b/monitoring/uss_qualifier/resources/netrid/simulation/README.md @@ -1,19 +1,81 @@ -# USS Qualifier RID Test Data Generation - -This directory contains a series of tools for generating flight data for qualifying Network Remote ID compliance. - -## Flight track dataset generator -[`adjacent_circular_flights_simulator.py`](adjacent_circular_flights_simulator.py): An API to generate multiple flight paths and patterns within a specified bounding box. You can specify a bounding box in any part of the world the generator will create a grid for flights for the bounds provided. In addition, circular flight paths will be generated within that grid. The output of this API are flight data file artifacts in GeoJSON [FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) format and the flight tracks are converted to a `RIDAircraftState` data: by adding metadata and timestamps to the points. The conversion of flight track points to RIDAircraftState can be considered as a simplified simulation. - -## Create Flight Record from KML -[`kml_flights.py`](kml_flights.py) accepts a KML file with one/many flights defined in the KML folders and produce a set of JSON files for each such flight, snapshotting the aircraft's state every sample_rate. Every flight needs exactly one path (LineString) and this is the path the aircraft takes over the ground. Speed and altitude of the flight are defined by the polygons surrounding the path. - -Following are the specifications for input KML: - -- **Flight path**: KML must contain one folder for each flight path. Folder name should be prefixed with "flight: " and the flight ID of the flight, e.g. "flight: fly_north". Every flight needs exactly one path (LineString) and this is the path the aircraft should take over the ground. Rest of the characteristics of the flight are defined in the folder's description, including the sample_rate (in Hertz). - -- **Speed zones**: Speed zones for a flight should be defined as polygons nested in the flight folder. Each speed polygon must be prefixed with "speed: " and include m/s in parenthesis. For example: "speed: Mission (2.5)". - -- **Altitude zones**: Like speed, the polygon names prefixed with "alt: " are considered Altitude polygons for a flight path. Altitude of each point in the flight path is interpolated based on the distance of the point from the nearby altitude polygons. - -- **Speed and Altitude interpolation**: Speed and Altitude of each point is interpolated by adding weight of surrounding zones where weigh of each zone is 1/distance of the zone from the point. For example: If zone 1 was at 10m altitude 10m away, zone 2 was at 20m altitude 50m away, and zone 3 was at 30m altitude 100m away, the weights would be 1/10 for zone 1, 1/50 for zone 2, and 1/100 for zone 3. So, the aircraft altitude would be (10/10 + 20/50 + 30/100) / (1/10 + 1/50 + 1/100) = 13.1m. +# USS Qualifier RID Test Data Generation + +This directory contains a series of tools for generating flight data for qualifying Network Remote ID compliance. + +## Flight track dataset generator +[`adjacent_circular_flights_simulator.py`](adjacent_circular_flights_simulator.py): An API to generate multiple flight paths and patterns within a specified bounding box. You can specify a bounding box in any part of the world the generator will create a grid for flights for the bounds provided. In addition, circular flight paths will be generated within that grid. The output of this API are flight data file artifacts in GeoJSON [FeatureCollection](https://tools.ietf.org/html/rfc7946#section-3.3) format and the flight tracks are converted to a `RIDAircraftState` data: by adding metadata and timestamps to the points. The conversion of flight track points to RIDAircraftState can be considered as a simplified simulation. + +## Create Flight Record from KML + +The KML is designed with [Google Earth](https://earth.google.com/) (desktop version), and it's intended to be the set of information a human user could reasonably draw to describe the flight. + +It's hard to draw in three dimensions (let alone four), so the areas define behavior in the extra two dimensions.  + +An altitude area sets the altitude of the aircraft inside it, the speed area sets the speed. + +The actual flight path of the aircraft is constructed from this information.  + +The flight path is just the path the aircraft will take and the key points are not the points the aircraft will report -- instead, the aircraft will report points along the path depending on its speed. + +[`kml_flights.py`](kml_flights.py) accepts a KML file with one/many flights defined in the KML folders and produce a set of JSON files for each such flight, snapshotting the aircraft's state every sample_rate. + +Following are the specifications for input KML: + +### Tree hierachy example + +``` +kentland: # Root folder + flight: example # Definition of a flight. There may be multiple flights + - Flight path + - alt: Base + - alt: High + - speed: Departure (5) + - speed: Enroute (30) + - operator_location +``` + + +### Flight + +KML must contain one folder for each flight path. Folder name should be prefixed with "flight: " and the flight ID of the flight, e.g. "flight: fly_north". + +The metadata of a flight are stored in its folder description, as simple `key: value` format. + +Example description: + +``` +serial_number: EXPL3123 +operation_description: U-turn south of takeoff +operator_id: EXAMPLE_OPERATOR2 +registration_number: N.123456 +aircraft_type: HybridLift +operator_name: UFT Participant 2 +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown +``` + +### Flight path + +Every flight needs exactly one path (LineString) and this is the path the aircraft should take over the ground. + +### Speed zones + +Speed zones for a flight should be defined as polygons nested in the flight folder. Each speed polygon must be prefixed with "speed: " and include m/s in parenthesis. For example: "speed: Mission (2.5)". + +### Altitude zones + +Like speed, the polygon names prefixed with "alt: " are considered Altitude polygons for a flight path. Only the altitude of the first point is used. Altitude of each point in the flight path is interpolated based on the distance of the point from the nearby altitude polygons. + +### Speed and Altitude interpolation + +Speed and Altitude of each point is interpolated by adding weight of surrounding zones where weigh of each zone is 1/distance of the zone from the point. For example: If zone 1 was at 10m altitude 10m away, zone 2 was at 20m altitude 50m away, and zone 3 was at 30m altitude 100m away, the weights would be 1/10 for zone 1, 1/50 for zone 2, and 1/100 for zone 3. So, the aircraft altitude would be (10/10 + 20/50 + 30/100) / (1/10 + 1/50 + 1/100) = 13.1m. + +If a point on the path is inside a polygone, the value from it is directly used. + + +## Operator location + +The location of the operator should be defined as a sngle point, named `operator_location`. diff --git a/monitoring/uss_qualifier/resources/netrid/simulation/kml_flights.py b/monitoring/uss_qualifier/resources/netrid/simulation/kml_flights.py index 51b336aea9..a8fa59175b 100755 --- a/monitoring/uss_qualifier/resources/netrid/simulation/kml_flights.py +++ b/monitoring/uss_qualifier/resources/netrid/simulation/kml_flights.py @@ -12,6 +12,9 @@ from shapely.geometry import LineString, Point, Polygon from uas_standards.astm.f3411.v22a import constants from uas_standards.interuss.automated_testing.rid.v1 import injection +from uas_standards.interuss.automated_testing.rid.v1.injection import ( + OperatorAltitudeAltitudeType, +) from monitoring.monitorlib.geo import flatten, unflatten from monitoring.monitorlib.kml.parsing import get_kml_content, get_polygon_speed @@ -266,6 +269,12 @@ def generate_flight_record( eu_classification=eu_classification, ) + if operator_location.get("alt"): + rid_details.operator_altitude = injection.OperatorAltitude( + altitude=float(operator_location.get("alt")), + altitude_type=OperatorAltitudeAltitudeType.Fixed, + ) + return FullFlightRecord( reference_time=StringBasedDateTime(now_isoformat), states=flight_telemetry, diff --git a/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py b/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py index 85bb834c4a..c65e1815e6 100644 --- a/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py +++ b/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py @@ -17,8 +17,10 @@ def generate_serial_number(self): return str(SerialNumber.generate_valid()) def generate_registration_number(self, prefix="CHE"): + if not prefix.endswith("."): + prefix = f"{prefix}." registration_number = prefix + "".join( - self.random.choices(string.ascii_lowercase + string.digits, k=13) + self.random.choices(string.ascii_uppercase + string.digits, k=13) ) return registration_number diff --git a/monitoring/uss_qualifier/resources/planning_area.py b/monitoring/uss_qualifier/resources/planning_area.py index abb48a29aa..fd486c0d14 100644 --- a/monitoring/uss_qualifier/resources/planning_area.py +++ b/monitoring/uss_qualifier/resources/planning_area.py @@ -1,6 +1,6 @@ import datetime -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.astm.f3548.v21.api import ( EntityID, EntityOVN, @@ -11,18 +11,22 @@ UssBaseURL, ) -from monitoring.monitorlib.geo import Volume3D, make_latlng_rect +from monitoring.monitorlib.geo import make_latlng_rect from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.subscription_params import SubscriptionParams -from monitoring.monitorlib.temporal import Time +from monitoring.monitorlib.temporal import TestTimeContext, Time from monitoring.monitorlib.testing import make_fake_url from monitoring.uss_qualifier.resources.resource import Resource +from monitoring.uss_qualifier.resources.volume import VolumeResource class PlanningAreaSpecification(ImplicitDict): - """Specifies a 2D or 3D volume along with USS related information to create test resources that require them.""" + """Specifies a 2D, 3D or 4D volume to be used in flight planning activities. + - The base_url is directly declared in this specification + - The volume itself is declared separately and passed as a dependency: see resource.VolumeResource for details. + """ - base_url: str | None + base_url: Optional[str] """Base URL for the USS Note that this is the base URL for the F3548-21 USS API, not the flights or any other specific URL. @@ -32,23 +36,40 @@ class PlanningAreaSpecification(ImplicitDict): If not specified, a fake URL will be generated at runtime according to the test in which the resource is being used.""" - volume: Volume3D - """3D volume of service area""" - - def get_volume4d( - self, time_start: datetime.datetime, time_end: datetime.datetime - ) -> Volume4D: - return Volume4D( - volume=self.volume, - time_start=Time(time_start), - time_end=Time(time_end), - ) - def get_base_url(self, frames_above: int = 1): if "base_url" in self and self.base_url is not None: return self.base_url return make_fake_url(frames_above=frames_above + 1) + +class PlanningAreaResource(Resource[PlanningAreaSpecification]): + specification: PlanningAreaSpecification + _volume: VolumeResource + + def __init__( + self, + specification: PlanningAreaSpecification, + resource_origin: str, + volume: VolumeResource, + ): + super().__init__(specification, resource_origin) + self.specification = specification + self._volume = volume + + def resolved_volume4d(self, context: TestTimeContext) -> Volume4D: + return self._volume.specification.template.resolve(context) + + def resolved_volume4d_with_times( + self, time_start: datetime.datetime | None, time_end: datetime.datetime | None + ) -> Volume4D: + """resolves this resource's underlying volume template to a 3D volume and use it as a base for a 4D volume + with the provided time bounds""" + return Volume4D( + volume=self._volume.specification.template.resolve_3d(), + time_start=Time(time_start) if time_start else None, + time_end=Time(time_end) if time_end else None, + ) + def get_new_subscription_params( self, subscription_id: str, @@ -61,22 +82,23 @@ def get_new_subscription_params( Builds a dict of parameters that can be used to create a subscription, using this ISA's parameters and the passed start time and duration """ + v4d = self.resolved_volume4d_with_times(start_time, start_time + duration) return SubscriptionParams( sub_id=subscription_id, - area_vertices=make_latlng_rect(self.volume), + area_vertices=make_latlng_rect(v4d.volume), min_alt_m=( None - if self.volume.altitude_lower is None - else self.volume.altitude_lower_wgs84_m() + if v4d.volume.altitude_lower is None + else v4d.volume.altitude_lower_wgs84_m() ), max_alt_m=( None - if self.volume.altitude_upper is None - else self.volume.altitude_upper_wgs84_m() + if v4d.volume.altitude_upper is None + else v4d.volume.altitude_upper_wgs84_m() ), start_time=start_time, end_time=start_time + duration, - base_url=self.get_base_url(frames_above=2), + base_url=self.specification.get_base_url(frames_above=2), notify_for_op_intents=notify_for_op_intents, notify_for_constraints=notify_for_constraints, ) @@ -102,7 +124,9 @@ def get_new_operational_intent_ref_params( Note that this method allows building inconsistent parameters: """ return PutOperationalIntentReferenceParameters( - extents=[self.get_volume4d(time_start, time_end).to_f3548v21()], + extents=[ + self.resolved_volume4d_with_times(time_start, time_end).to_f3548v21() + ], key=key, state=state, uss_base_url=uss_base_url, @@ -130,14 +154,8 @@ def get_new_constraint_ref_params( as for testing authentication or parameter validation. """ return PutConstraintReferenceParameters( - extents=[self.get_volume4d(time_start, time_end).to_f3548v21()], - uss_base_url=self.get_base_url(frames_above=2), + extents=[ + self.resolved_volume4d_with_times(time_start, time_end).to_f3548v21() + ], + uss_base_url=self.specification.get_base_url(frames_above=2), ) - - -class PlanningAreaResource(Resource[PlanningAreaSpecification]): - specification: PlanningAreaSpecification - - def __init__(self, specification: PlanningAreaSpecification, resource_origin: str): - super().__init__(specification, resource_origin) - self.specification = specification diff --git a/monitoring/uss_qualifier/resources/versioning/client.py b/monitoring/uss_qualifier/resources/versioning/client.py index b5caea4c71..5ae7a0da36 100644 --- a/monitoring/uss_qualifier/resources/versioning/client.py +++ b/monitoring/uss_qualifier/resources/versioning/client.py @@ -1,11 +1,13 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from uas_standards.interuss.automated_testing.versioning.constants import Scope from monitoring.monitorlib.clients.versioning.client import VersioningClient from monitoring.monitorlib.clients.versioning.client_interuss import ( InterUSSVersioningClient, ) -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import ( + utm_client_session_factory, +) from monitoring.monitorlib.inspection import fullname from monitoring.uss_qualifier.reports.report import ParticipantID from monitoring.uss_qualifier.resources.communications import AuthAdapterResource @@ -18,7 +20,7 @@ class InterUSSVersionProvider(ImplicitDict): class VersionProviderSpecification(ImplicitDict): - interuss: InterUSSVersionProvider | None = None + interuss: Optional[InterUSSVersionProvider] = None """Populated when the version provider is using the InterUSS automated testing versioning API to provide versioning information.""" participant_id: ParticipantID @@ -48,7 +50,7 @@ def __init__( self.version_providers = [] for instance in specification.instances: if "interuss" in instance and instance.interuss: - session = UTMClientSession( + session = utm_client_session_factory.get_session( prefix_url=instance.interuss.base_url, auth_adapter=auth_adapter.adapter, ) diff --git a/monitoring/uss_qualifier/resources/volume.py b/monitoring/uss_qualifier/resources/volume.py index 50c96ef162..0bfc2a06d7 100644 --- a/monitoring/uss_qualifier/resources/volume.py +++ b/monitoring/uss_qualifier/resources/volume.py @@ -20,7 +20,7 @@ def s2_vertices(self) -> list[s2sphere.LatLng]: """Returns the vertices of the 2D area represented by this volume specification, after application of the template's transformations. Note that if the underlying volume contains a Circle, the vertices of its bounding rectangle are returned. """ - return self.template.resolve({}).volume.s2_vertices() + return self.template.resolve_3d().s2_vertices() def vertices(self) -> list[LatLngPoint]: """Returns the vertices of the 2D area represented by this volume specification, after application of the template's transformations. diff --git a/monitoring/uss_qualifier/run_locally.sh b/monitoring/uss_qualifier/run_locally.sh index 206673cbf5..b614338344 100755 --- a/monitoring/uss_qualifier/run_locally.sh +++ b/monitoring/uss_qualifier/run_locally.sh @@ -78,6 +78,7 @@ docker run ${docker_args} --name uss_qualifier \ -e PYTHONBUFFERED=1 \ -e AUTH_SPEC=${AUTH_SPEC} \ -e AUTH_SPEC_2=${AUTH_SPEC_2} \ + -e PROJ_NETWORK \ ${PRIVATE_REPOS_ENV_FLAG} \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ -v "$(pwd)/$OUTPUT_DIR:/app/$OUTPUT_DIR" \ diff --git a/monitoring/uss_qualifier/scenarios/README.md b/monitoring/uss_qualifier/scenarios/README.md index a243d7ce5b..f1a13bdfb8 100644 --- a/monitoring/uss_qualifier/scenarios/README.md +++ b/monitoring/uss_qualifier/scenarios/README.md @@ -42,6 +42,10 @@ Test scenarios will usually be enumerated/identified/created by mapping a list o See [CONTRIBUTING.md](../../../CONTRIBUTING.md#ussqualifier-test-scenarios) for more information on how to develop test scenarios. +### Delays + +Scenarios should avoid delays when possible as automated tests are more valuable when they run more quickly. When delays are necessary, only use `.sleep` -- do not use `time.sleep` nor `monitorlib.delay.sleep` nor any other means to cause an intentional delay. The use of `.sleep` allows delays to be more easily tracked and audited as they are often a prime target of interest when attempting to reduce automated testing run time. + ## Resources Most test scenarios will require [test resources](../resources/README.md) (like NetRID telemetry to inject, NetRID service providers under test, etc) usually customized to the ecosystem in which the tests are being performed. A test scenario declares what kind of resource(s) it requires, and a test suite identifies which available resources should be used to fulfill each test scenario's needs. diff --git a/monitoring/uss_qualifier/scenarios/astm/dss/datastore_access.py b/monitoring/uss_qualifier/scenarios/astm/dss/datastore_access.py index d1d867d928..4a035cefb2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/dss/datastore_access.py +++ b/monitoring/uss_qualifier/scenarios/astm/dss/datastore_access.py @@ -50,13 +50,23 @@ def _attempt_connection(self) -> None: self.begin_test_step("Attempt to connect in insecure mode") for node in self.datastore_nodes: with self.check( - "Node runs in secure mode", + "Node enforces encryption of its communications", node.participant_id, ) as check: - secure_mode, e = node.runs_in_secure_mode() - if not secure_mode: + encrypted, e = node.no_tls_rejected() + if not encrypted: check.record_failed( - "Node is not in secure mode", + "Node did not reject cleartext communication", + details=f"Reported connection error (if any): {e}", + ) + with self.check( + "Node enforces authentication of its communications", + node.participant_id, + ) as check: + authenticated, e = node.unauthenticated_rejected() + if not authenticated: + check.record_failed( + "Node did not reject unauthenticated communication", details=f"Reported connection error (if any): {e}", ) self.end_test_step() @@ -67,7 +77,7 @@ def _attempt_connection(self) -> None: "Node rejects legacy encryption protocols", node.participant_id, ) as check: - rejected, e = node.legacy_ssl_version_rejected() + rejected, e = node.legacy_tls_version_rejected() if not rejected: check.record_failed( "Node did not reject connection with legacy encryption protocol", diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/operator_interactions.py b/monitoring/uss_qualifier/scenarios/astm/dss/pool_info.py similarity index 74% rename from monitoring/uss_qualifier/scenarios/astm/netrid/common/operator_interactions.py rename to monitoring/uss_qualifier/scenarios/astm/dss/pool_info.py index c2da3d2d69..856b4faccb 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/operator_interactions.py +++ b/monitoring/uss_qualifier/scenarios/astm/dss/pool_info.py @@ -2,11 +2,12 @@ from monitoring.uss_qualifier.suites.suite import ExecutionContext -class OperatorInteractions(GenericTestScenario): - def __init__(self): +class PoolInfo(GenericTestScenario): + def __init__( + self, + ): super().__init__() def run(self, context: ExecutionContext): self.begin_test_scenario(context) - # TODO: Implement self.end_test_scenario() diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py index 92aebae8e2..e571a0bf58 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py @@ -35,13 +35,13 @@ class AggregateChecks(GenericTestScenario): def __init__( self, service_providers: NetRIDServiceProviders, - observers: NetRIDObserversResource, dss_instances: DSSInstancesResource, + observers: NetRIDObserversResource | None = None, test_exclusions: TestExclusionsResource | None = None, ): super().__init__() self._service_providers = service_providers.service_providers - self._observers = observers.observers + self._observers = observers.observers if observers else [] self._dss_instances = dss_instances.dss_instances # identify SPs and observers by their base URL diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py index be7c248dd8..5621f2509b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dp_behavior.py @@ -33,7 +33,10 @@ direction_filter, get_mock_uss_interactions, ) -from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario +from monitoring.uss_qualifier.scenarios.scenario import ( + DISTANCE_ERROR_TOLERANCE_FRACTION, + GenericTestScenario, +) from monitoring.uss_qualifier.suites.suite import ExecutionContext @@ -67,8 +70,8 @@ def __init__( self._mock_uss = mock_uss.mock_uss self._dss_wrapper = DSSWrapper(self, dss_pool.dss_instances[0]) self._isa_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._isa = isa.specification - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() self._identification = uss_identification isa_center = geo.center_of_mass(self._isa_area) @@ -81,28 +84,21 @@ def __init__( Angle.from_degrees(1 * degree_per_km) ) - limit_side_km = self._rid_version.max_diagonal_km / math.sqrt(2) + limit_diagonal_length_ok = self._rid_version.max_diagonal_km * ( + 1 - DISTANCE_ERROR_TOLERANCE_FRACTION + ) + + limit_side_km = limit_diagonal_length_ok / math.sqrt(2) self._limit_rect = LatLngRect.from_point(isa_center).convolve_with_cap( Angle.from_degrees(limit_side_km * degree_per_km / 2) ) - # Make sure the limit_rect is close to the allowed diagonal limit - assert ( - self._rid_version.max_diagonal_km * 0.99 - < geo.get_latlngrect_diagonal_km(self._limit_rect) - <= self._rid_version.max_diagonal_km - ), ( - f"{geo.get_latlngrect_diagonal_km(self._limit_rect)} > {self._rid_version.max_diagonal_km}" + + limit_diagonal_length_fail = self._rid_version.max_diagonal_km * ( + 1 + DISTANCE_ERROR_TOLERANCE_FRACTION ) - # Make the too big rect 1% larger than the allowed diagonal limit self._too_big_rect = LatLngRect.from_point(isa_center).convolve_with_cap( - Angle.from_degrees(limit_side_km * 1.01 * degree_per_km / 2) - ) - assert ( - geo.get_latlngrect_diagonal_km(self._too_big_rect) - > self._rid_version.max_diagonal_km - ), ( - f"{geo.get_latlngrect_diagonal_km(self._too_big_rect)} <= {self._rid_version.max_diagonal_km}" + Angle.from_degrees(limit_diagonal_length_fail * degree_per_km / 2) ) @property @@ -137,7 +133,20 @@ def run(self, context: ExecutionContext): self.begin_test_case("Display Provider Behavior") for obs in self._observers: - test_step_start_time = arrow.utcnow().datetime + self.begin_test_step("Note remote clock") + test_case_start_time, query = self._mock_uss.get_clock() + self.record_query(query) + with self.check( + "mock_uss clock time retrievable", self._mock_uss.participant_id + ) as check: + if test_case_start_time is None: + check.record_failed( + "mock_uss clock time was not retrievable", + f"mock_uss responded {query.response.status_code} without a valid clock time; is mock_uss running the latest version of `monitoring`?", + queries=query, + ) + self.end_test_step() + self.begin_test_step("Query acceptable diagonal area") # Query the DP for the exact area of the ISA self._step_query_ok_diagonal(obs) @@ -152,9 +161,10 @@ def run(self, context: ExecutionContext): self._step_query_too_big_diagonal(obs) self.end_test_step() - self.begin_test_step("Verify query to SP") - self._step_validate_queries_to_sp(obs, test_step_start_time) - self.end_test_step() + if test_case_start_time: + self.begin_test_step("Verify query to SP") + self._step_validate_queries_to_sp(obs, test_case_start_time) + self.end_test_step() self.end_test_case() self.end_test_scenario() @@ -186,7 +196,7 @@ def _step_create_isa(self): if self._identification is not None: # Attribute notifications to participants when possible for base_url, notification in isa_change.notifications.items(): - self._identification.attribute_query(notification.query) + self._identification.attribute_query_server(notification.query) # For any attributed notifications, check that the recipient acknowledged them correctly for base_url, notification in isa_change.notifications.items(): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/heavy_traffic_concurrent.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/heavy_traffic_concurrent.py index 3856c00a7b..369060f020 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/heavy_traffic_concurrent.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/heavy_traffic_concurrent.py @@ -3,7 +3,6 @@ from datetime import UTC, datetime import aiohttp -import arrow import requests from uas_standards.astm.f3411 import v19, v22a @@ -66,8 +65,8 @@ def __init__( self._dss_wrapper = DSSWrapper(self, dss.dss_instance) self._isa_versions: dict[str, str] = {} - self._isa = isa.specification - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() # Note that when the test scenario ends prematurely, we may end up with an unclosed session. self._async_session = AsyncUTMTestSession( @@ -90,7 +89,7 @@ def __init__( ) def run(self, context: ExecutionContext): - self._shift_isa_time_relative_to_now() + self._resolve_isa_time_bounds() self.begin_test_scenario(context) @@ -129,10 +128,10 @@ def run(self, context: ExecutionContext): self.end_test_case() self.end_test_scenario() - def _shift_isa_time_relative_to_now(self): - now = arrow.utcnow().datetime - self._isa_params["start_time"] = self._isa.shifted_time_start(now) - self._isa_params["end_time"] = self._isa.shifted_time_end(now) + def _resolve_isa_time_bounds(self): + start, end = self._isa.resolved_time_bounds(self.time_context.evaluate_now()) + self._isa_params["start_time"] = start + self._isa_params["end_time"] = end def _delete_isas_if_exists(self): """Delete test ISAs if they exist. Done sequentially.""" @@ -207,6 +206,7 @@ async def _get_isa(self, isa_id): "GET", url, ) + # TODO: Do not rely on a prepared request that is not actually used in order to create the Query RequestDescription; instead build it from the request actually made prep = self._dss.client.prepare_request(r) t0 = datetime.now(UTC) req_descr = describe_request(prep, t0) @@ -243,6 +243,7 @@ async def _create_isa(self, isa_id): url, json=payload, ) + # TODO: Do not rely on a prepared request that is not actually used in order to create the Query RequestDescription; instead build it from the request actually made prep = self._dss.client.prepare_request(r) t0 = datetime.now(UTC) req_descr = describe_request(prep, t0) @@ -273,6 +274,7 @@ async def _delete_isa(self, isa_id, isa_version): "DELETE", url, ) + # TODO: Do not rely on a prepared request that is not actually used in order to create the Query RequestDescription; instead build it from the request actually made prep = self._dss.client.prepare_request(r) t0 = datetime.now(UTC) req_descr = describe_request(prep, t0) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_expiry.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_expiry.py index c083866eb1..b2bf4033ff 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_expiry.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_expiry.py @@ -1,8 +1,5 @@ import datetime -import arrow - -from monitoring.monitorlib.delay import sleep from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstanceResource from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource @@ -35,12 +32,11 @@ def __init__( self._dss_wrapper = DSSWrapper(self, dss.dss_instance) self._isa_id = id_generator.id_factory.make_id(ISAExpiry.ISA_TYPE) self._isa_version: str | None = None - self._isa = isa.specification - - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() def run(self, context: ExecutionContext): - self._shift_isa_time_relative_to_now() + self._resolve_isa_time_bounds() self.begin_test_scenario(context) @@ -55,10 +51,10 @@ def run(self, context: ExecutionContext): self.end_test_case() self.end_test_scenario() - def _shift_isa_time_relative_to_now(self): - now = arrow.utcnow().datetime - self._isa_start_time = self._isa.shifted_time_start(now) - self._isa_end_time = self._isa.shifted_time_end(now) + def _resolve_isa_time_bounds(self): + self._isa_start_time, self._isa_end_time = self._isa.resolved_time_bounds( + self.time_context.evaluate_now() + ) def _check_expiry_behaviors(self): """ @@ -85,7 +81,7 @@ def _check_expiry_behaviors(self): ) # Wait for it to expire - sleep(5, "we need to wait for the short-lived ISA to expire") + self.sleep(5, "we need to wait for the short-lived ISA to expire") # Search for ISAs: we should not find the expired one with self.check( diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_simple.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_simple.py index 6c3966d684..960c3b1719 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_simple.py @@ -1,6 +1,5 @@ import datetime -import arrow import s2sphere from monitoring.monitorlib.fetch import rid as fetch @@ -39,12 +38,13 @@ def __init__( self._dss_wrapper = DSSWrapper(self, dss.dss_instance) self._isa_id = id_generator.id_factory.make_id(ISASimple.ISA_TYPE) self._isa_version: str | None = None - self._isa = isa.specification - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + + self._isa_area = isa.s2_vertices() self._huge_area = problematically_big_area.specification.s2_vertices() def run(self, context: ExecutionContext): - self._shift_isa_time_relative_to_now() + self._resolve_isa_time_bounds() self.begin_test_scenario(context) @@ -55,10 +55,10 @@ def run(self, context: ExecutionContext): self.end_test_scenario() - def _shift_isa_time_relative_to_now(self): - now = arrow.utcnow().datetime - self._isa_start_time = self._isa.shifted_time_start(now) - self._isa_end_time = self._isa.shifted_time_end(now) + def _resolve_isa_time_bounds(self): + self._isa_start_time, self._isa_end_time = self._isa.resolved_time_bounds( + self.time_context.evaluate_now() + ) def _setup_case(self): self.begin_test_case("Setup") diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py index 4bee611107..4aed362da0 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_subscription_interactions.py @@ -1,5 +1,3 @@ -import arrow - from monitoring.monitorlib import geo from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstanceResource @@ -36,8 +34,9 @@ def __init__( ) self._isa_version: str | None = None - self._isa = isa.specification - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() + self._slight_overlap_area = geo.generate_slight_overlap_area(self._isa_area) self._isa_params = dict( @@ -59,7 +58,7 @@ def __init__( ) def run(self, context: ExecutionContext): - self._shift_resources_time_relative_to_now() + self._resolve_isa_time_bounds() self.begin_test_scenario(context) @@ -91,12 +90,12 @@ def run(self, context: ExecutionContext): self.end_test_case() self.end_test_scenario() - def _shift_resources_time_relative_to_now(self): - now = arrow.utcnow().datetime - self._isa_params["start_time"] = self._isa.shifted_time_start(now) - self._isa_params["end_time"] = self._isa.shifted_time_end(now) - self._sub_params["start_time"] = self._isa.shifted_time_start(now) - self._sub_params["end_time"] = self._isa.shifted_time_end(now) + def _resolve_isa_time_bounds(self): + start, end = self._isa.resolved_time_bounds(self.time_context.evaluate_now()) + self._isa_params["start_time"] = start + self._isa_params["end_time"] = end + self._sub_params["start_time"] = start + self._sub_params["end_time"] = end def _new_subscription_in_isa_step(self): """ diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py index c09db9c652..053c4feac4 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/isa_validation.py @@ -2,7 +2,6 @@ import datetime from typing import Any -import arrow import s2sphere from uas_standards.astm.f3411 import v19, v22a @@ -45,9 +44,9 @@ def __init__( self._dss_wrapper = DSSWrapper(self, dss.dss_instance) self._isa_id = id_generator.id_factory.make_id(ISAValidation.ISA_TYPE) self._isa_version: str | None = None - self._isa = isa.specification + self._isa = isa - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa_area = isa.s2_vertices() self._huge_area = problematically_big_area.specification.s2_vertices() @@ -65,7 +64,7 @@ def __init__( ValueError(f"Unsupported RID version '{self._dss.rid_version}'") def run(self, context: ExecutionContext): - self._shift_isa_time_relative_to_now() + self._resolve_isa_time_bounds() self.begin_test_scenario(context) @@ -89,10 +88,10 @@ def run(self, context: ExecutionContext): self.end_test_case() self.end_test_scenario() - def _shift_isa_time_relative_to_now(self): - now = arrow.utcnow().datetime - self._isa_start_time = self._isa.shifted_time_start(now) - self._isa_end_time = self._isa.shifted_time_end(now) + def _resolve_isa_time_bounds(self): + self._isa_start_time, self._isa_end_time = self._isa.resolved_time_bounds( + self.time_context.evaluate_now() + ) def _setup_case(self): self.begin_test_case("Setup") @@ -202,8 +201,8 @@ def _isa_start_time_after_time_end(self): area_vertices=self._isa_area, alt_lo=self._isa.altitude_min, alt_hi=self._isa.altitude_max, - start_time=self._isa.time_end.datetime, - end_time=self._isa.time_start.datetime, + start_time=self._isa_end_time, + end_time=self._isa_start_time, uss_base_url=self._isa.base_url, isa_id=self._isa_id, isa_version=self._isa_version, @@ -226,8 +225,8 @@ def _isa_vertices_are_valid(self): area_vertices=INVALID_VERTICES, alt_lo=self._isa.altitude_min, alt_hi=self._isa.altitude_max, - start_time=self._isa.time_start.datetime, - end_time=self._isa.time_end.datetime, + start_time=self._isa_start_time, + end_time=self._isa_end_time, uss_base_url=self._isa.base_url, isa_id=self._isa_id, isa_version=self._isa_version, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_simple.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_simple.py index 6c2656e9c6..b8760cabdc 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_simple.py @@ -65,8 +65,8 @@ def __init__( self._dss = dss.dss_instance self._dss_wrapper = DSSWrapper(self, self._dss) self._base_sub_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._isa = isa.specification - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() # List of vertices that has the same first and last point: # Used to validate some special-case handling by the DSS self._isa_area_loop = self._isa_area.copy() diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_validation.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_validation.py index 0b107a6a97..477367ee5f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/subscription_validation.py @@ -42,8 +42,8 @@ def __init__( # TODO: the id_factory seems to generate static IDs: # for creating different subscriptions this probably won't do. self._sub_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._isa = isa.specification - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() def run(self, context: ExecutionContext): self.begin_test_scenario(context) @@ -212,7 +212,7 @@ def _check_properly_truncated( def _default_subscription_params(self, duration: datetime.timedelta) -> dict: now = datetime.datetime.now(datetime.UTC) return dict( - area_vertices=[vertex.as_s2sphere() for vertex in self._isa.footprint], + area_vertices=self._isa_area, alt_lo=self._isa.altitude_min, alt_hi=self._isa.altitude_max, start_time=now, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/token_validation.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/token_validation.py index 7d164fe751..304efc11e8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/token_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss/token_validation.py @@ -1,6 +1,5 @@ import datetime -import arrow import s2sphere from uas_standards.astm.f3411 import v19, v22a @@ -44,23 +43,24 @@ def __init__( self._dss_wrapper = DSSWrapper(self, dss.dss_instance) self._isa_id = id_generator.id_factory.make_id(ISASimple.ISA_TYPE) self._isa_version: str | None = None - self._isa = isa.specification - - self._isa_area = [vertex.as_s2sphere() for vertex in self._isa.footprint] + self._isa = isa + self._isa_area = isa.s2_vertices() # correctly formed and signed using an unrecognized private key # (should cause requests to be rejected) - self._unsigned_token_session = infrastructure.UTMClientSession( - self._dss.base_url, InvalidTokenSignatureAuth() + self._unsigned_token_session = ( + infrastructure.utm_client_session_factory.get_session( + self._dss.base_url, InvalidTokenSignatureAuth() + ) ) # Session that won't provide a token at all # (should cause requests to be rejected) - self._no_token_session = infrastructure.UTMClientSession( + self._no_token_session = infrastructure.utm_client_session_factory.get_session( self._dss.base_url, None ) def run(self, context: ExecutionContext): - self._shift_isa_time_relative_to_now() + self._resolve_isa_time_bounds() self.begin_test_scenario(context) @@ -86,10 +86,10 @@ def run(self, context: ExecutionContext): self.end_test_scenario() - def _shift_isa_time_relative_to_now(self): - now = arrow.utcnow().datetime - self._isa_start_time = self._isa.shifted_time_start(now) - self._isa_end_time = self._isa.shifted_time_end(now) + def _resolve_isa_time_bounds(self): + self._isa_start_time, self._isa_end_time = self._isa.resolved_time_bounds( + self.time_context.evaluate_now() + ) def _wrong_auth_put(self): # Try to create an ISA with a read scope diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss_interoperability.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss_interoperability.py index 4b5b950423..c5793f39c9 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss_interoperability.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/dss_interoperability.py @@ -3,12 +3,11 @@ import socket import uuid from dataclasses import dataclass -from enum import Enum +from enum import StrEnum from urllib.parse import urlparse import s2sphere -from monitoring.monitorlib.delay import sleep from monitoring.monitorlib.fetch.rid import ISA from monitoring.monitorlib.geo import get_latlngrect_vertices, make_latlng_rect from monitoring.uss_qualifier.resources import PlanningAreaResource @@ -19,7 +18,6 @@ from monitoring.uss_qualifier.resources.dev.test_exclusions import ( TestExclusionsResource, ) -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.netrid.dss_wrapper import DSSWrapper from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext @@ -30,7 +28,7 @@ DEFAULT_UPPER_ALT_M = 400 -class EntityType(str, Enum): +class EntityType(StrEnum): ISA = "ISA" Sub = "Sub" @@ -55,7 +53,7 @@ class DSSInteroperability(GenericTestScenario): _allow_private_addresses: bool = False _context: dict[str, TestEntity] _area_vertices: list[s2sphere.LatLng] - _planning_area: PlanningAreaSpecification + _planning_area: PlanningAreaResource def __init__( self, @@ -72,9 +70,11 @@ def __init__( if not dss.is_same_as(primary_dss_instance.dss_instance) ] - self._planning_area = planning_area.specification + self._planning_area = planning_area self._area_vertices = get_latlngrect_vertices( - make_latlng_rect(self._planning_area.volume) + make_latlng_rect( + self._planning_area.resolved_volume4d_with_times(None, None).volume + ) ) if test_exclusions is not None: self._allow_private_addresses = test_exclusions.allow_private_addresses @@ -128,9 +128,20 @@ def _test_env_reqs(self): "DSS instance is publicly addressable", [dss.participant_id] ) as check: parsed_url = urlparse(dss.base_url) - ip_addr = socket.gethostbyname(parsed_url.hostname) + try: + if not parsed_url.hostname: + raise ValueError( + f"Invalid hostname from urlparse: {parsed_url.hostname}" + ) + ip_addr = socket.gethostbyname(parsed_url.hostname) + except (socket.gaierror, ValueError) as e: + ip_addr = None + check.record_failed( + summary=f"DSS host {parsed_url.netloc} could not be checked for public addressability", + details=f"DSS (URL: {dss.base_url}, netloc: {parsed_url.netloc}), could not resolve to an IP because {str(e)}", + ) - if ipaddress.ip_address(ip_addr).is_private: + if ip_addr and ipaddress.ip_address(ip_addr).is_private: if self._allow_private_addresses: check.skip() else: @@ -565,7 +576,7 @@ def step9(self): """Expired ISA automatically removed, ISA modifications accessible from all non-primary DSSs""" - sleep( + self.sleep( SHORT_WAIT_SEC, "ISA_1 needs to expire so we can check it is automatically removed", ) @@ -661,7 +672,7 @@ def step11(self): def step12(self): """Expired Subscriptions don’t trigger subscription notification requests""" - sleep( + self.sleep( SHORT_WAIT_SEC, "Subscriptions needs to expire so we can check they don't trigger notifications", ) @@ -816,18 +827,14 @@ def step17(self): def _default_params(self, duration: datetime.timedelta) -> dict: now = datetime.datetime.now().astimezone() - + v4d = self._planning_area.resolved_volume4d_with_times(now, now + duration) return dict( area_vertices=self._area_vertices, - alt_lo=self._planning_area.volume.altitude_lower_wgs84_m( - DEFAULT_LOWER_ALT_M - ), - alt_hi=self._planning_area.volume.altitude_upper_wgs84_m( - DEFAULT_UPPER_ALT_M - ), + alt_lo=v4d.volume.altitude_lower_wgs84_m(DEFAULT_LOWER_ALT_M), + alt_hi=v4d.volume.altitude_upper_wgs84_m(DEFAULT_UPPER_ALT_M), start_time=now, end_time=now + duration, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), ) def cleanup(self): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py index dbc732fc6e..e5f64f744a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py @@ -8,7 +8,9 @@ from monitoring.monitorlib import auth, geo from monitoring.monitorlib.errors import stacktrace_string from monitoring.monitorlib.fetch import rid -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import ( + utm_client_session_factory, +) from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstancesResource from monitoring.uss_qualifier.resources.netrid import ( @@ -85,10 +87,6 @@ def run(self, context: ExecutionContext): self.begin_test_step("Invalid search area") self._poll_during_flights( - [ - self._rid_version.max_diagonal_km * 1000 - - 100, # valid diagonal required for sps urls discovery - ], self._evaluate_and_test_too_large_area_requests, dict(), ) @@ -96,11 +94,6 @@ def run(self, context: ExecutionContext): self.begin_test_step("Unauthenticated requests") self._poll_during_flights( - [ - self._rid_version.max_diagonal_km * 1000 + 500, # too large - self._rid_version.max_diagonal_km * 1000 - 100, # clustered - self._rid_version.max_details_diagonal_km * 1000 - 100, # details - ], self._evaluate_and_test_authentication, { "auth": auth.NoAuth(aud_override=""), @@ -112,11 +105,6 @@ def run(self, context: ExecutionContext): self.begin_test_step("Incorrectly authenticated requests") self._poll_during_flights( - [ - self._rid_version.max_diagonal_km * 1000 + 500, # too large - self._rid_version.max_diagonal_km * 1000 - 100, # clustered - self._rid_version.max_details_diagonal_km * 1000 - 100, # details - ], self._evaluate_and_test_authentication, { "auth": auth.InvalidTokenSignatureAuth(), @@ -136,7 +124,6 @@ def _inject_flights(self): def _poll_during_flights( self, - diagonals_m: list[float], evaluation_func: Callable[ [LatLngRect, Unpack[dict[str, auth.AuthAdapter | str]]], set[str] ], @@ -145,7 +132,6 @@ def _poll_during_flights( """ Poll until every injected flights have been observed. - :param diagonals_m: List of diagonals in meters used by the virtual observer to fetch flights. :param evaluation_func: This method is called on each polling tick with the area to observe. It is responsible to fetch flights and to return the list of observed injected ids. """ @@ -156,6 +142,7 @@ def _poll_during_flights( min_query_diagonal_m=config.min_query_diagonal, relevant_past_data_period=self._rid_version.realtime_period + config.max_propagation_latency.timedelta, + sleep=self.sleep, ) remaining_injection_ids = set( @@ -173,14 +160,16 @@ def poll_func(rect: LatLngRect) -> bool: virtual_observer.start_polling( config.min_polling_interval.timedelta, - diagonals_m, + [self._rid_version.max_diagonal_km * 1000 - 100], poll_func, ) + def _is_area_too_large(self, rect: s2sphere.LatLngRect) -> bool: + return geo.get_latlngrect_diagonal_km(rect) > self._rid_version.max_diagonal_km + def _fetch_flights_from_dss(self, rect: LatLngRect) -> dict[str, TelemetryMapping]: # We grab all flights from the SPs (which we know how to reach by first querying the DSS). # This is authenticated and is expected to succeed - # TODO: Add the following requests to the documentation. Possibly split it as a test step. sp_observation = rid.all_flights( rect, include_recent_positions=True, @@ -190,14 +179,23 @@ def _fetch_flights_from_dss(self, rect: LatLngRect) -> dict[str, TelemetryMappin dss_participant_id=self._dss.participant_id, ) + self.record_queries(sp_observation.queries) + mapping_by_injection_id = ( display_data_evaluator.map_fetched_to_injected_flights( self._injected_flights, list(sp_observation.uss_flight_queries.values()), + list(sp_observation.uss_flight_details_queries.values()), self._query_cache, ) ) - self.record_queries(sp_observation.queries) + + display_data_evaluator.check_fetched_flights( + sp_observation, + self, + self._dss.participant_id, + self._is_area_too_large(rect), + ) return mapping_by_injection_id @@ -274,7 +272,7 @@ def _evaluate_and_test_authentication( participant_id = mapping.injected_flight.uss_participant_id flights_url = mapping.observed_flight.query.flights_url - invalid_session = UTMClientSession(flights_url, auth) + invalid_session = utm_client_session_factory.get_session(flights_url, auth) self.record_note( f"{participant_id}/{injection_id}/missing_credentials_queries", diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/networked_uas_disconnect.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/networked_uas_disconnect.py index da37eade9d..fd61cfd274 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/networked_uas_disconnect.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/networked_uas_disconnect.py @@ -91,6 +91,7 @@ def _poll_during_flights(self): min_query_diagonal_m=config.min_query_diagonal, relevant_past_data_period=self._rid_version.realtime_period + config.max_propagation_latency.timedelta, + sleep=self.sleep, ) evaluator = display_data_evaluator.DisconnectedUASObservationEvaluator( diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py index 0771221a9a..266b4885c6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/nominal_behavior.py @@ -10,6 +10,7 @@ NetRIDObserversResource, NetRIDServiceProviders, ) +from monitoring.uss_qualifier.resources.netrid.observers import RIDSystemObserver from monitoring.uss_qualifier.scenarios.astm.netrid import ( display_data_evaluator, injection, @@ -31,7 +32,7 @@ class NominalBehavior(GenericTestScenario): _flights_data: FlightDataResource _service_providers: NetRIDServiceProviders - _observers: NetRIDObserversResource + _observers: list[RIDSystemObserver] _evaluation_configuration: EvaluationConfigurationResource _injected_flights: list[InjectedFlight] @@ -41,14 +42,14 @@ def __init__( self, flights_data: FlightDataResource, service_providers: NetRIDServiceProviders, - observers: NetRIDObserversResource, evaluation_configuration: EvaluationConfigurationResource, + observers: NetRIDObserversResource | None = None, dss_pool: DSSInstancesResource | None = None, ): super().__init__() self._flights_data = flights_data self._service_providers = service_providers - self._observers = observers + self._observers = observers.observers if observers else [] self._evaluation_configuration = evaluation_configuration self._dss_pool = dss_pool self._injected_tests = [] @@ -87,6 +88,7 @@ def _poll_during_flights(self): + config.max_propagation_latency.timedelta # add two 'min_polling_interval' to make sure we poll at least once after flights are over + (config.min_polling_interval.timedelta * 2), + sleep=self.sleep, ) evaluator = display_data_evaluator.RIDObservationEvaluator( self, @@ -97,7 +99,7 @@ def _poll_during_flights(self): ) def poll_fct(rect: LatLngRect) -> bool: - evaluator.evaluate_system_instantaneously(self._observers.observers, rect) + evaluator.evaluate_system_instantaneously(self._observers, rect) return False virtual_observer.start_polling( diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py index cef5518948..027931cace 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_notification_behavior.py @@ -1,29 +1,27 @@ from collections.abc import Callable -from datetime import timedelta +from datetime import datetime, timedelta from typing import TypeVar -from future.backports.datetime import datetime -from implicitdict import ImplicitDict -from requests.exceptions import RequestException from s2sphere import LatLngRect -from uas_standards.astm.f3411.v19 import api as api_v19 -from uas_standards.astm.f3411.v22a import api as api_v22a from monitoring.monitorlib.clients.mock_uss.interactions import ( Interaction, QueryDirection, ) -from monitoring.monitorlib.delay import sleep -from monitoring.monitorlib.errors import stacktrace_string +from monitoring.monitorlib.fetch import Query, QueryType from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.temporal import Time from monitoring.prober.infrastructure import register_resource_type +from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstancesResource from monitoring.uss_qualifier.resources.interuss import IDGeneratorResource from monitoring.uss_qualifier.resources.interuss.mock_uss.client import ( MockUSSClient, MockUSSResource, ) +from monitoring.uss_qualifier.resources.interuss.uss_identification import ( + USSIdentificationResource, +) from monitoring.uss_qualifier.resources.netrid import ( FlightDataResource, NetRIDServiceProviders, @@ -37,11 +35,16 @@ from monitoring.uss_qualifier.scenarios.interuss.mock_uss.test_steps import ( direction_filter, get_mock_uss_interactions, - status_code_filter, + query_type_filter, +) +from monitoring.uss_qualifier.scenarios.scenario import ( + GenericTestScenario, + ScenarioDidNotStopError, ) -from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext +TOperationResult = TypeVar("TOperationResult") + class ServiceProviderNotificationBehavior(GenericTestScenario): SUB_TYPE = register_resource_type(399, "Subscription") @@ -49,6 +52,7 @@ class ServiceProviderNotificationBehavior(GenericTestScenario): _flights_data: FlightDataResource _service_providers: NetRIDServiceProviders _mock_uss: MockUSSClient + _uss_identification: USSIdentificationResource _injected_flights: list[InjectedFlight] _injected_tests: list[InjectedTest] @@ -63,11 +67,13 @@ def __init__( mock_uss: MockUSSResource, id_generator: IDGeneratorResource, dss_pool: DSSInstancesResource, + uss_identification: USSIdentificationResource, ): super().__init__() self._flights_data = flights_data self._service_providers = service_providers self._mock_uss = mock_uss.mock_uss + self._uss_identification = uss_identification self._dss_wrapper = DSSWrapper(self, dss_pool.dss_instances[0]) self._injected_tests = [] @@ -97,14 +103,23 @@ def run(self, context: ExecutionContext): self.end_test_step() self.begin_test_step("Injection") + injection_time, query = self._mock_uss.get_clock() + self.record_query(query) + with self.check( + "mock_uss clock time retrievable", self._mock_uss.participant_id + ) as check: + if injection_time is None: + check.record_failed( + "mock_uss clock time was not retrievable", + f"mock_uss responded {query.response.status_code} without a valid clock time; is mock_uss running the latest version of `monitoring`?", + queries=query, + ) + raise ScenarioDidNotStopError(check) self._inject_flights() self.end_test_step() self.begin_test_step("Validate Mock USS received notification") - # Given that we know when flights are injected, we could narrow down the time window for - # which we are looking for notifications to something more precise than scenario start time. - # TODO tracked in #1052 - self._validate_mock_uss_notifications(context.start_time) + self._validate_mock_uss_notifications(injection_time) self.end_test_step() self.end_test_case() @@ -153,42 +168,56 @@ def _validate_mock_uss_notifications(self, notifications_received_after: datetim [tf.uss_participant_id for tf in self._injected_flights] ) - def post_isa_filter(interaction: Interaction): - return ( - interaction.query.request.method == "POST" - and "/uss/identification_service_areas/" - in interaction.query.request.url - ) - - def fetch_interactions() -> list[Interaction]: + def due_to_subscription_filter(interaction: Interaction) -> bool: + """Select only interactions that are notifications due to subscription we established""" + subscriptions = [ + sub.get("subscription_id", None) + for sub in (interaction.query.request.json or {}).get( + "subscriptions", [] + ) + ] + if self._subscription_id not in subscriptions: + return False + return True + + def fetch_interactions() -> tuple[list[Interaction], Query]: + if self._rid_version == RIDVersion.f3411_22a: + query_type = QueryType.F3411v22aUSSPostIdentificationServiceArea + elif self._rid_version == RIDVersion.f3411_19: + query_type = QueryType.F3411v19USSPostIdentificationServiceArea + else: + raise NotImplementedError() return get_mock_uss_interactions( self, self._mock_uss, Time(notifications_received_after), direction_filter(QueryDirection.Incoming), - status_code_filter(204), - post_isa_filter, - )[0] + query_type_filter(query_type), + due_to_subscription_filter, + ) - def includes_all_notifications(raw_interactions: list[Interaction]) -> bool: + def includes_all_notifications( + raw_interactions: tuple[list[Interaction], Query], + ) -> bool: pids_having_notified = self._relevant_notified_subs( - raw_interactions, relevant_participant_ids, notifications_received_after + raw_interactions[0], relevant_participant_ids ) # We're done once we have a notification from each SP we injected a flight in return len(pids_having_notified) == len(relevant_participant_ids) # notifications are not immediate: we optimistically try early, and retry until # the permissible delay has passed, or we have received all notifications. - interactions = _retry_with_backoff( + interactions, query = self._retry_with_backoff( fetch_interactions, retries=3, delay_s=1, delay_reason="waiting for expected notifications to be delivered", was_successful=includes_all_notifications, ) + # fish out the notification times per participant ID notifs_by_participant = self._relevant_notified_subs( - interactions, relevant_participant_ids, notifications_received_after + interactions, relevant_participant_ids ) # For each of the service providers we injected flights in, # check that we received a notification. We don't check the latency as a single datapoint does not allow @@ -207,61 +236,27 @@ def includes_all_notifications(raw_interactions: list[Interaction]) -> bool: check.record_failed( summary="No notification received", details=f"No notification received within roughly {self._rid_version.dp_data_resp_percentile99_s} seconds from {test_flight.uss_participant_id} for subscription {self._subscription_id} about flight {test_flight.test_id} that happened within the subscription's boundaries.", + queries=query, ) continue - def _notif_operation_id(self) -> api_v19.OperationID | api_v22a.OperationID: - if self._rid_version.f3411_19: - return api_v19.OperationID.PostIdentificationServiceArea - elif self._rid_version.f3411_22a: - return api_v22a.OperationID.PostIdentificationServiceArea - else: - raise ValueError(f"Unsupported RID version: {self._rid_version}") - - def _notif_param_type( - self, - ) -> type[ - api_v19.PutIdentificationServiceAreaNotificationParameters - | api_v22a.PutIdentificationServiceAreaNotificationParameters - ]: - if self._rid_version == RIDVersion.f3411_19: - return api_v19.PutIdentificationServiceAreaNotificationParameters - elif self._rid_version == RIDVersion.f3411_22a: - return api_v22a.PutIdentificationServiceAreaNotificationParameters - else: - raise ValueError(f"Unsupported RID version: {self._rid_version}") - def _relevant_notified_subs( self, raw_interactions: list[Interaction], relevant_pids: set[str], - received_after: datetime, ) -> dict[str, list[datetime]]: - # Parse version-specific notification parameters - PutIsaParamsType = self._notif_param_type() - - relevant = [] + notifs_by_participant: dict[ParticipantID, list[datetime]] = {} for interaction in raw_interactions: - received_at = interaction.query.request.received_at.datetime - notification: PutIsaParamsType = ImplicitDict.parse( - interaction.query.request.json, PutIsaParamsType + # Associate queries' clients to participants where possible + participant_id = self._uss_identification.identify_query_client( + interaction.query ) - for sub in notification.subscriptions: - if ( - sub.subscription_id == self._subscription_id - and "service_area" - in notification # deletion notification don't have this field - and notification.service_area.owner in relevant_pids - # We may sometimes receive slightly older and unrelated notifications which we filter out - and received_at > received_after - ): - relevant.append((received_at, notification.service_area.owner)) - - notifs_by_participant: dict[str, list[datetime]] = {} - for received_at, participant_id in relevant: - if participant_id not in notifs_by_participant: - notifs_by_participant[participant_id] = [] - notifs_by_participant[participant_id].append(received_at) + if not participant_id or participant_id not in relevant_pids: + continue + notifs = notifs_by_participant.get(participant_id, []) + notifs.append(interaction.query.request.received_at.datetime) + notifs_by_participant[participant_id] = notifs + return notifs_by_participant def cleanup(self): @@ -284,40 +279,33 @@ def cleanup(self): ) sp = matching_sps[0] check = self.check("Successful test deletion", [sp.participant_id]) - try: - query = sp.delete_test(injected_test.test_id, injected_test.version) - self.record_query(query) - if query.status_code != 200: - raise ValueError( - f"Received status code {query.status_code} after attempting to delete test {injected_test.test_id} at version {injected_test.version} from service provider {sp.participant_id}" - ) + query = sp.delete_test(injected_test.test_id, injected_test.version) + self.record_query(query) + if query.status_code == 200: check.record_passed() - except (RequestException, ValueError) as e: - stacktrace = stacktrace_string(e) + else: check.record_failed( - summary="Error while trying to delete test flight", - details=f"While trying to delete a test flight from {sp.participant_id}, encountered error:\n{stacktrace}", + summary="Test flight deletion was unsuccessful", + details=f"Received status code {query.status_code} after attempting to delete test {injected_test.test_id} at version {injected_test.version} from service provider {sp.participant_id}", + queries=query, ) self.end_cleanup() - -TOperationResult = TypeVar("TOperationResult") - - -def _retry_with_backoff( - operation: Callable[[], TOperationResult], - retries: int, - delay_s: float, - delay_reason: str, - was_successful: Callable[[TOperationResult], bool], -) -> TOperationResult: - """Retry an operation with a delay, up to a certain number of retries, - until the condition is met or retries are exhausted. - """ - for attempt in range(retries + 1): + def _retry_with_backoff( + self, + operation: Callable[[], TOperationResult], + retries: int, + delay_s: float, + delay_reason: str, + was_successful: Callable[[TOperationResult], bool], + ) -> TOperationResult: + """Retry an operation with a delay, up to a certain number of retries, + until the condition is met or retries are exhausted. + """ result = operation() - if was_successful(result): - return result - if attempt < retries: - sleep(timedelta(seconds=delay_s), delay_reason) - return result + for attempt in range(retries): + if was_successful(result): + return result + self.sleep(timedelta(seconds=delay_s), delay_reason) + result = operation() + return result diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py index ebd93691e6..b9d17110ba 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_missing_fields.py @@ -133,6 +133,7 @@ def _poll_during_flights(self): min_query_diagonal_m=config.min_query_diagonal, relevant_past_data_period=self._rid_version.realtime_period + config.max_propagation_latency.timedelta, + sleep=self.sleep, ) evaluator = display_data_evaluator.NotificationsEvaluator( self, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_slow_update.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_slow_update.py index 9ed86a8624..1b9457d491 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_slow_update.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/sp_operator_notify_slow_update.py @@ -134,6 +134,7 @@ def _poll_during_flights(self): min_query_diagonal_m=config.min_query_diagonal, relevant_past_data_period=self._rid_version.realtime_period + config.max_propagation_latency.timedelta, + sleep=self.sleep, ) evaluator = display_data_evaluator.NotificationsEvaluator( 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 266f8b9b06..4d37138b26 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -37,7 +37,14 @@ ) from monitoring.monitorlib.fetch.rid import Flight, FlightDetails, Position -from monitoring.monitorlib.geo import Altitude, LatLngPoint, validate_lat, validate_lng +from monitoring.monitorlib.geo import ( + Altitude, + AltitudeDatum, + DistanceUnits, + LatLngPoint, + validate_lat, + validate_lng, +) from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.netrid.evaluation import EvaluationConfiguration @@ -47,6 +54,10 @@ T2 = TypeVar("T2") +class NoObservedFlight(ValueError): + pass + + class RIDCommonDictionaryEvaluator: flight_evaluators = [ "_evaluate_ua_type", @@ -86,6 +97,45 @@ def __init__( self._test_scenario = test_scenario self._rid_version = rid_version + def evaluate_injected_flight( + self, injected_telemetry, injected_flight, injected_flight_details + ) -> None: + """Helper for generator of flights that raise an exception if an injected flight is invalid""" + + for generics_evaluator in self.flight_evaluators: + try: + getattr(self, generics_evaluator)( + injected=injected_flight, + sp_observed=None, + dp_observed=None, + participant=[], + query_timestamp=datetime.datetime.now(), + ) + except NoObservedFlight: + continue + for generics_evaluator in self.details_evaluators: + try: + getattr(self, generics_evaluator)( + injected=injected_flight_details, + sp_observed=None, + dp_observed=None, + participant=[], + query_timestamp=datetime.datetime.now(), + ) + except NoObservedFlight: + continue + for generics_evaluator in self.telemetry_evaluators: + try: + getattr(self, generics_evaluator)( + injected=injected_telemetry, + sp_observed=None, + dp_observed=None, + participant=[], + query_timestamp=datetime.datetime.now(), + ) + except NoObservedFlight: + continue + def evaluate_sp_flight( self, injected_telemetry: injection.RIDAircraftState, @@ -169,10 +219,17 @@ def evaluate_sp_details( ) self._evaluate_operator_id(None, observed_details.operator_id, [participant_id]) + + operator_altitude_inj = injected_details.get("operator_altitude", {}).get( + "altitude", None + ) + operator_altitude_type_inj = injected_details.get("operator_altitude", {}).get( + "altitude_type", None + ) self._evaluate_operator_location( None, - None, - None, + operator_altitude_inj, + operator_altitude_type_inj, observed_details.operator_location, observed_details.operator_altitude, observed_details.operator_altitude_type, @@ -211,9 +268,7 @@ def evaluate_dp_details( operator_altitude_inj = injected_details.get("operator_altitude", {}) self._evaluate_operator_location( injected_details.get("operator_location", None), - operator_altitude_inj.get( - "altitude", None - ), # should be of the correct type already + operator_altitude_inj.get("altitude", None), operator_altitude_inj.get("altitude_type", None), operator_obs.get("location", None), Altitude.w84m(value=operator_altitude_value_obs), @@ -223,14 +278,14 @@ def evaluate_dp_details( def _evaluate_uas_id( self, - injected: injection.RIDFlightDetails, # unused but required by callers + injected: injection.RIDFlightDetails, sp_observed: FlightDetails | None, dp_observed: observation_api.GetDetailsResponse | None, participant: ParticipantID, query_timestamp: datetime.datetime, ): """ - Evaluates UAS id Exactly one of sp_observed or dp_observed must be provided. + Evaluates UAS id Exactly one of sp_observed or dp_observed must be provided, if not, will only evaluate injected values. See as well `common_dictionary_evaluator.md`. Raises: @@ -241,6 +296,24 @@ def _evaluate_uas_id( # skip if evaluating DP: the UAS ID may be None, and if present is evaluated by evaluators specific to UAS ID types return + if sp_observed is None: + injected_values = [] + + for field in [ + "uas_id.specific_session_id", + "uas_id.serial_number", + "uas_id.registration_id", + "uas_id.utm_id", + ]: + injected_values.append(_dotted_get(injected, field)) + + if not any(injected_values): + raise ValueError( + "No valid UAS ID provided" + ) # NB: We may enounter invalid data for f3411_19, as a smaller subset of fields is needed, but we don't know how flight is going to be injected. + + return NoObservedFlight("No observed flight") + # We check that there is at least one value set with self._test_scenario.check( "UAS ID is exposed correctly", participant @@ -292,7 +365,9 @@ def value_validator(val: SerialNumber) -> SerialNumber: serial_number = SerialNumber(val) if not serial_number.valid: - raise ValueError("Invalid serial number") + raise ValueError( + f"Invalid serial number {SerialNumber.generate_valid()}" + ) return serial_number @@ -760,7 +835,7 @@ def value_comparator( def _evaluate_operator_location( self, position_inj: LatLngPoint | None, - altitude_inj: Altitude | None, + altitude_inj: injection.Altitude | None, altitude_type_inj: injection.OperatorAltitudeAltitudeType | None, position_obs: LatLngPoint | None, altitude_obs: Altitude | None, @@ -830,10 +905,11 @@ def _evaluate_operator_location( participants, ) as check: if ( - alt.units != altitude_inj.units - or alt.reference != altitude_inj.reference - or abs(alt.value - altitude_inj.value) - > 1 # TODO replace with constants.MinOperatorAltitudeResolution + # right now we inject only altitude in meters with W84 reference + alt.units != DistanceUnits.M + or alt.reference != AltitudeDatum.W84 + or abs(alt.value - altitude_inj) + > constants.MinOperatorAltitudeResolution ): check.record_failed( "Observed operator altitude inconsistent with injected one", @@ -1212,7 +1288,7 @@ def _evaluate_ua_classification( ) and dp_observed.uas.has_field_with_value("eu_classification"): observed_ua_classification = "eu_classification" else: - raise ValueError("No observed flight provided.") + raise NoObservedFlight("No observed flight provided.") with self._test_scenario.check( "UA classification type is consistent with injected value", @@ -1398,7 +1474,13 @@ def _generic_evaluator( elif dp_observed is not None: observed_val = _dotted_get(dp_observed, dp_field_name) else: - raise ValueError("No observed flight provided.") + # Check that injected value is valid if required + if injected_val is None and injection_required_field: + raise ValueError( + f"Field {field_human_name} is not defined, but is requiered." + ) + + raise NoObservedFlight("No observed flight provided.") if skip_eval: skip_reason = skip_eval(injected_val, observed_val) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index 25be87ff62..3a5cfbfdae 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -9,9 +9,7 @@ from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a from uas_standards.astm.f3411.v22a.api import ( - Altitude, HorizontalAccuracy, - LatLngPoint, RIDHeightReference, RIDOperationalStatus, SpeedAccuracy, @@ -23,6 +21,7 @@ OperatorAltitudeAltitudeType, ) +from monitoring.monitorlib import geo from monitoring.monitorlib.fetch.rid import Flight from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.resources.netrid.evaluation import EvaluationConfiguration @@ -95,45 +94,45 @@ def step_under_test(self: UnitTestScenario): def test_operator_location(): valid_locations: list[ tuple[ - LatLngPoint | None, - Altitude | None, - OperatorAltitudeAltitudeType | None, - LatLngPoint | None, - Altitude | None, + geo.LatLngPoint | None, + injection.Altitude | None, + injection.OperatorAltitudeAltitudeType | None, + geo.LatLngPoint | None, + geo.Altitude | None, OperatorAltitudeAltitudeType | None, int, ] ] = [ ( - LatLngPoint(lat=1.0, lng=1.0), + geo.LatLngPoint(lat=1.0, lng=1.0), None, None, - LatLngPoint(lat=1.0, lng=1.0), + geo.LatLngPoint(lat=1.0, lng=1.0), None, None, 2, ), ( - LatLngPoint(lat=-90.0, lng=180.0), + geo.LatLngPoint(lat=-90.0, lng=180.0), None, None, - LatLngPoint(lat=-90.0, lng=180.0), + geo.LatLngPoint(lat=-90.0, lng=180.0), None, None, 2, ), ( - LatLngPoint( + geo.LatLngPoint( lat=46.2, lng=6.1, ), - Altitude(value=1), - OperatorAltitudeAltitudeType("Takeoff"), - LatLngPoint( + injection.Altitude(1), + injection.OperatorAltitudeAltitudeType("Takeoff"), + geo.LatLngPoint( lat=46.2, lng=6.1, ), - Altitude(value=1), + geo.Altitude.w84m(1), OperatorAltitudeAltitudeType("Takeoff"), 6, ), @@ -143,31 +142,34 @@ def test_operator_location(): invalid_locations: list[ tuple[ - LatLngPoint | None, - Altitude | None, + geo.LatLngPoint | None, + injection.Altitude | None, + injection.OperatorAltitudeAltitudeType | None, + geo.LatLngPoint | None, + geo.Altitude | None, OperatorAltitudeAltitudeType | None, int, int, ] ] = [ ( - LatLngPoint(lat=-90.001, lng=0), # out of range and valid + geo.LatLngPoint(lat=-90.001, lng=0), # out of range and valid None, None, - LatLngPoint(lat=-90.001, lng=0), # out of range and valid + geo.LatLngPoint(lat=-90.001, lng=0), # out of range and valid None, None, 1, 1, ), ( - LatLngPoint( + geo.LatLngPoint( lat=0, # valid lng=180.001, # out of range ), None, None, - LatLngPoint( + geo.LatLngPoint( lat=0, # valid lng=180.001, # out of range ), @@ -177,23 +179,23 @@ def test_operator_location(): 1, ), ( - LatLngPoint(lat=-90.001, lng=180.001), # both out of range + geo.LatLngPoint(lat=-90.001, lng=180.001), # both out of range None, None, - LatLngPoint(lat=-90.001, lng=180.001), # both out of range + geo.LatLngPoint(lat=-90.001, lng=180.001), # both out of range None, None, 0, 2, ), ( - LatLngPoint( + geo.LatLngPoint( lat=46.2, lng=6.1, ), None, None, - LatLngPoint( + geo.LatLngPoint( lat="46°12'7.99 N", # Float required lng="6°08'44.48 E", # Float required ), @@ -203,44 +205,40 @@ def test_operator_location(): 2, ), ( - LatLngPoint( + geo.LatLngPoint( lat=46.2, lng=6.1, ), - Altitude(value=1), - "invalid", # Invalid value - LatLngPoint( + injection.Altitude(1), + injection.OperatorAltitudeAltitudeType("Fixed"), + geo.LatLngPoint( lat=46.2, lng=6.1, ), - Altitude(value=1), - "invalid", # Invalid value + geo.Altitude.w84m(1), + OperatorAltitudeAltitudeType("Takeoff"), 5, 1, ), ( - LatLngPoint( + geo.LatLngPoint( lat=46.2, lng=6.1, ), - Altitude( - value=1000.9, - units="FT", # Invalid value - reference="UNKNOWN", # Invalid value - ), - "Takeoff", - LatLngPoint( + injection.Altitude(1000.9), + injection.OperatorAltitudeAltitudeType("Takeoff"), + geo.LatLngPoint( lat=46.2, lng=6.1, ), - Altitude( + geo.Altitude( value=1000.9, units="FT", # Invalid value reference="UNKNOWN", # Invalid value ), - "Takeoff", - 5, - 2, + OperatorAltitudeAltitudeType("Takeoff"), + 4, + 3, ), ] for invalid_location in invalid_locations: @@ -595,7 +593,7 @@ def _assert_generic_evaluator_result( ) -def _build_generic_evaluator_objects( +def _build_generic_evaluator_objects[T]( injected_field_setter: Callable[[Any, T], list[Any]], sp_field_setter: Callable[[Any, T], Any], dp_field_setter: Callable[[Any, T], Any], @@ -622,7 +620,7 @@ def _build_generic_evaluator_objects( return injected, sp_observed, dp_observed -def _assert_generic_evaluator_correct_field_is_used( +def _assert_generic_evaluator_correct_field_is_used[T]( *fct_and_setters: list[Any], valid_value: T, valid_value_2: T ): """ @@ -684,7 +682,7 @@ def _assert_generic_evaluator_valid_value[T]( ) -def _assert_generic_evaluator_invalid_value( +def _assert_generic_evaluator_invalid_value[T]( *fct_and_setters: list[Any], invalid_value: T, valid_value: T, @@ -790,7 +788,7 @@ def _assert_generic_evaluator_defaults[T2, T]( ) -def _assert_generic_evaluator_dont_have_default( +def _assert_generic_evaluator_dont_have_default[T]( *fct_and_setters: list[Any], valid_value: T, valid_value_2: T ): """ @@ -844,7 +842,7 @@ def _assert_generic_evaluator_dont_have_default( ) -def _assert_generic_evaluator_equivalent( +def _assert_generic_evaluator_equivalent[T]( *fct_and_setters: list[Any], v1: T, v2: T, rid_version: RIDVersion | None = None ): """ @@ -867,7 +865,7 @@ def _assert_generic_evaluator_equivalent( ) -def _assert_generic_evaluator_not_equivalent( +def _assert_generic_evaluator_not_equivalent[T]( *fct_and_setters: list[Any], v1: T, v2: T, rid_version: RIDVersion | None = None ): """ 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 36fce382eb..254dec2e81 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -16,6 +16,7 @@ from monitoring.monitorlib.fetch import Query from monitoring.monitorlib.fetch.rid import ( FetchedFlights, + FetchedUSSFlightDetails, FetchedUSSFlights, Position, all_flights, @@ -41,7 +42,10 @@ from monitoring.uss_qualifier.scenarios.astm.netrid.virtual_observer import ( VirtualObserver, ) -from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.scenarios.scenario import ( + GenericTestScenario, + TestScenario, +) SPEED_PRECISION = 0.05 HEIGHT_PRECISION_M = 1 @@ -102,8 +106,10 @@ class FetchedToInjectedCache: mappings: dict[str, ParticipantID] = field(default_factory=dict) """Mapping is a map of URL -> ParticipantID that we found until now""" - unmapped: list[FetchedUSSFlights] = field(default_factory=list) - """Unmapped is the list of flights we didn't attributed yet""" + unmapped: list[FetchedUSSFlights | FetchedUSSFlightDetails] = field( + default_factory=list + ) + """Unmapped is the list of flights or flight details we didn't attributed yet""" def map_observations_to_injected_flights( @@ -173,9 +179,60 @@ def map_observations_to_injected_flights( return mapping +def check_fetched_flights( + fetched_flights: FetchedFlights, + scenario: GenericTestScenario, + dss_participant_id: str, + area_too_large: bool, +): + """Perform checks on a FetchedFlights result from 'rid.all_flights' helper""" + + with scenario.check("Successful ISA query", [dss_participant_id]) as check: + if not fetched_flights.dss_isa_query.success: + check.record_failed( + summary="Could not query ISAs from DSS", + details=f"Query to {dss_participant_id}'s DSS at failed: {', '.join(fetched_flights.dss_isa_query.errors)}", + query_timestamps=[ + fetched_flights.dss_isa_query.request.initiated_at.datetime + ], + ) + + if area_too_large: # Don't perform flight queries checks as they should fail + return + + for flight_query in fetched_flights.uss_flight_queries.values(): + with scenario.check( + "Successful flight query", flight_query.participant_id + ) as check: + if not flight_query.success: + check.record_failed( + summary="Flight query not successful", + details=f"Query to {flight_query.query.request.url} failed: {', '.join(flight_query.errors)}", + query_timestamps=[flight_query.query.request.initiated_at.datetime] + if flight_query.query.request.initiated_at + else [], + ) + + for flight_details_query in fetched_flights.uss_flight_details_queries.values(): + with scenario.check( + "Successful flight details query", flight_details_query.participant_id + ) as check: + if not flight_details_query.success: + check.record_failed( + summary="Flight details query not successful", + details=f"Query to {flight_details_query.query.request.url} failed: {', '.join(flight_details_query.errors)}", + query_timestamps=[ + flight_details_query.query.request.initiated_at.datetime + ] + if flight_details_query.query.request.initiated_at + else [], + ) + + def map_fetched_to_injected_flights( injected_flights: list[InjectedFlight], fetched_flights: list[FetchedUSSFlights], + fetched_flights_details: list[FetchedUSSFlightDetails], query_cache: FetchedToInjectedCache, ) -> dict[str, TelemetryMapping]: """Identify which of the fetched flights (if any) matches to each of the injected flights. @@ -185,6 +242,7 @@ def map_fetched_to_injected_flights( :param injected_flights: Flights injected into RID Service Providers under test. :param fetched_flights: Flight observed from an RID Display Provider under test. + :param fetched_flights_details: Flight details observed from an RID Display Provider under test. :param query_cache: A FetchedToInjectedCache, used to maintain participant_id in various queries :return: Mapping between InjectedFlight and observed Flight, indexed by injection_id. """ @@ -205,7 +263,7 @@ def map_fetched_to_injected_flights( ) # Add to the todo list queries to map - for uss_query in fetched_flights: + for uss_query in fetched_flights + fetched_flights_details: if not uss_query.has_field_with_value("participant_id"): query_cache.unmapped.append(uss_query) @@ -253,6 +311,7 @@ def __init__( min_query_diagonal_m=config.min_query_diagonal, relevant_past_data_period=rid_version.realtime_period + config.max_propagation_latency.timedelta, + sleep=test_scenario.sleep, ) self._config = config self._rid_version = rid_version @@ -262,9 +321,8 @@ def __init__( raise ValueError( f"Cannot evaluate a system using RID version {rid_version} with a DSS using RID version {dss.rid_version}" ) - self._retrieved_flight_details: set[str] = ( - set() - ) # Contains the observed IDs of the flights whose details were retrieved. + # dict of observer (identified by its base_url) to set of flight ID for which details were retrieved. + self._retrieved_flight_details: dict[str, set[str]] = {} def evaluate_system_instantaneously( self, @@ -284,14 +342,23 @@ def evaluate_system_instantaneously( dss_participant_id=self._dss.participant_id, ) + for q in sp_observation.queries: + self._test_scenario.record_query(q) + # map observed flights to injected flight and attribute participant ID mapping_by_injection_id = map_fetched_to_injected_flights( self._injected_flights, list(sp_observation.uss_flight_queries.values()), + list(sp_observation.uss_flight_details_queries.values()), self._query_cache, ) - for q in sp_observation.queries: - self._test_scenario.record_query(q) + + check_fetched_flights( + sp_observation, + self._test_scenario, + self._dss.participant_id, + self._is_area_too_large(rect), + ) # Evaluate observations self._evaluate_sp_observation(rect, sp_observation, mapping_by_injection_id) @@ -325,6 +392,9 @@ def evaluate_system_instantaneously( # TODO: If bounding rect is smaller than area-too-large threshold, expand slightly above area-too-large threshold and re-observe self._test_scenario.end_test_step() + def _is_area_too_large(self, rect: s2sphere.LatLngRect) -> bool: + return geo.get_latlngrect_diagonal_km(rect) > self._rid_version.max_diagonal_km + def _evaluate_observation( self, observer: RIDSystemObserver, @@ -333,10 +403,8 @@ def _evaluate_observation( query: fetch.Query, verified_sps: set[str], ) -> None: - diagonal_km = ( - rect.lo().get_distance(rect.hi()).degrees * geo.EARTH_CIRCUMFERENCE_KM / 360 - ) - if diagonal_km > self._rid_version.max_diagonal_km: + diagonal_km = geo.get_latlngrect_diagonal_km(rect) + if self._is_area_too_large(rect): self._evaluate_area_too_large_observation( observer, rect, diagonal_km, query ) @@ -358,7 +426,6 @@ def _evaluate_observation( else: self._evaluate_normal_observation( observer, - rect, observation, query, verified_sps, @@ -367,7 +434,6 @@ def _evaluate_observation( def _evaluate_normal_observation( self, observer: RIDSystemObserver, - rect: s2sphere.LatLngRect, observation: GetDisplayDataResponse, query: fetch.Query, verified_sps: set[str], @@ -456,14 +522,18 @@ def _evaluate_normal_observation( ), ) - # Check details of flights (once per flight) + # Check details of flights (once per flight and per observer) for mapping in mapping_by_injection_id.values(): with self._test_scenario.check( "Successful details observation", [mapping.injected_flight.uss_participant_id], ) as check: - # query for flight details only once per flight - if mapping.observed_flight.id in self._retrieved_flight_details: + # query for flight details only once per flight and per observer + if ( + observer.base_url in self._retrieved_flight_details + and mapping.observed_flight.id + in self._retrieved_flight_details[observer.base_url] + ): continue details_obs, query = observer.observe_flight_details( @@ -486,7 +556,13 @@ def _evaluate_normal_observation( details_inj = mapping.injected_flight.flight.get_details( telemetry_inj.timestamp.datetime ) - self._retrieved_flight_details.add(mapping.observed_flight.id) + + if observer.base_url not in self._retrieved_flight_details: + self._retrieved_flight_details[observer.base_url] = set() + self._retrieved_flight_details[observer.base_url].add( + mapping.observed_flight.id + ) + self._common_dictionary_evaluator.evaluate_dp_details( details_inj, details_obs, @@ -747,19 +823,6 @@ def _evaluate_sp_observation( # endpoints (and therefore cannot provide a callback/base URL), calling the one-time query endpoint # is currently much cleaner. If this test is applied to a DSS that does not implement the one-time # ISA query endpoint, this check can be adapted. - with self._test_scenario.check( - "ISA query", [self._dss.participant_id] - ) as check: - if not sp_observation.dss_isa_query.success: - check.record_failed( - summary="Could not query ISAs from DSS", - details=f"Query to {self._dss.participant_id}'s DSS at {sp_observation.dss_isa_query.query.request.url} failed {sp_observation.dss_isa_query.query.status_code}", - query_timestamps=[ - sp_observation.dss_isa_query.query.request.initiated_at.datetime - ], - ) - return - diagonal_km = ( requested_area.lo().get_distance(requested_area.hi()).degrees * geo.EARTH_CIRCUMFERENCE_KM @@ -1024,9 +1087,6 @@ def __init__( raise ValueError( f"Cannot evaluate a system using RID version {rid_version} with a DSS using RID version {dss.rid_version}" ) - self._retrieved_flight_details: set[str] = ( - set() - ) # Contains the observed IDs of the flights whose details were retrieved. # Keep track of the flights that we have observed as having been 'disconnected' # (last observed telemetry corresponds to last injected one within the window where data is returned) @@ -1065,14 +1125,23 @@ def evaluate_disconnected_flights( dss_participant_id=self._dss.participant_id, ) + for q in sp_observation.queries: + self._test_scenario.record_query(q) + # map observed flights to injected flight and attribute participant ID mapping_by_injection_id = map_fetched_to_injected_flights( self._injected_flights, list(sp_observation.uss_flight_queries.values()), + list(sp_observation.uss_flight_details_queries.values()), self._query_cache, ) - for q in sp_observation.queries: - self._test_scenario.record_query(q) + + check_fetched_flights( + sp_observation, + self._test_scenario, + self._dss.participant_id, + False, + ) # Evaluate observations self._evaluate_sp_observation(sp_observation, mapping_by_injection_id) @@ -1092,19 +1161,6 @@ def _evaluate_sp_observation( # endpoints (and therefore cannot provide a callback/base URL), calling the one-time query endpoint # is currently much cleaner. If this test is applied to a DSS that does not implement the one-time # ISA query endpoint, this check can be adapted. - with self._test_scenario.check( - "ISA query", [self._dss.participant_id] - ) as check: - if not sp_observation.dss_isa_query.success: - check.record_failed( - summary="Could not query ISAs from DSS", - details=f"Query to {self._dss.participant_id}'s DSS at {sp_observation.dss_isa_query.query.request.url} failed {sp_observation.dss_isa_query.query.status_code}", - query_timestamps=[ - sp_observation.dss_isa_query.query.request.initiated_at.datetime - ], - ) - return - self._evaluate_sp_observation_of_disconnected_flights(sp_observation, mappings) def _evaluate_sp_observation_of_disconnected_flights( diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py b/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py index 64ad2f0d22..d1f64dd9d3 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/dss_wrapper.py @@ -215,7 +215,13 @@ def get_isa( self.handle_query_result(check, isa, f"Failed to get ISA {isa_id}") - if isa_id != isa.isa.id: + if isa.isa is None: + check.record_failed( + summary="DSS did not return an ISA", + details=f"Expected ISA ID {isa_id} but got nothing", + query_timestamps=[isa.query.request.timestamp], + ) + elif isa_id != isa.isa.id: check.record_failed( summary="DSS did not return correct ISA", details=f"Expected ISA ID {isa_id} but got {isa.id}", @@ -531,7 +537,7 @@ def cleanup_isa( check, isa, f"Failed to get ISA {isa_id}", {404, 200} ) - if isa.status_code == 404: + if isa.status_code == 404 or isa.isa is None: return None del_isa = mutate.delete_isa( @@ -671,7 +677,13 @@ def get_sub( self.handle_query_result(check, sub, f"Failed to get subscription {sub_id}") - if sub_id != sub.subscription.id: + if sub.subscription is None: + check.record_failed( + summary="DSS did not return a subscription", + details=f"Expected Subscription ID {sub_id} but got nothing", + query_timestamps=[sub.query.request.timestamp], + ) + elif sub_id != sub.subscription.id: check.record_failed( summary="DSS did not return correct subscription", details=f"Expected Subscription ID {sub_id} but got {sub.subscription.id}", @@ -869,7 +881,13 @@ def del_sub( check, del_sub, f"Failed to delete subscription {sub_id}" ) - if sub_version != del_sub.subscription.version: + if del_sub.subscription is None: + check.record_failed( + summary="Deleted subscription not returned", + details="DSS reported not subscription during deletion", + query_timestamps=[del_sub.query.request.timestamp], + ) + elif sub_version != del_sub.subscription.version: check.record_failed( summary="Deleted subscription did not match", details=f"DSS reported deletion of version {sub_version} while expecting {del_sub.subscription.version}", @@ -929,6 +947,7 @@ def cleanup_sub( :return: the DSS response if the subscription exists """ + check = None try: with self._scenario.check( "Subscription can be queried by ID", [self.participant_id] @@ -944,7 +963,7 @@ def cleanup_sub( check, sub, f"Failed to get subscription {sub_id}", {404, 200} ) - if sub.status_code == 404: + if sub.status_code == 404 or sub.subscription is None: return None with self._scenario.check( @@ -968,7 +987,8 @@ def cleanup_sub( return del_sub except QueryError as e: - self._handle_query_error(check, e) + if check: + self._handle_query_error(check, e) raise RuntimeError( "DSS query was not successful, but a High Severity issue didn't interrupt execution" ) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/injection.py b/monitoring/uss_qualifier/scenarios/astm/netrid/injection.py index 7d2532c13c..ab76ff1aa1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/injection.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/injection.py @@ -1,8 +1,11 @@ import uuid -from datetime import datetime +from datetime import datetime, timedelta import arrow from implicitdict import ImplicitDict +from uas_standards.astm.f3548.v21.constants import ( + TimeSyncMaxDifferentialSeconds, +) from uas_standards.interuss.automated_testing.rid.v1.injection import ChangeTestResponse from monitoring.monitorlib import geo @@ -187,6 +190,7 @@ def get_user_notifications( details=f"Expected response code 200 from {target.participant_id} but received {query.status_code} while trying to retrieve user notifications", query_timestamps=[query.request.timestamp], ) + continue if response is None: check.record_failed( summary="Error while trying to retrieve user notifications", @@ -194,7 +198,23 @@ def get_user_notifications( query_timestamps=[query.request.timestamp], ) notifications[target.participant_id] = [] - else: - notifications[target.participant_id] = response.user_notifications + continue + + if any( + [ + notification.observed_at.value.datetime + > arrow.now() + timedelta(seconds=TimeSyncMaxDifferentialSeconds) + for notification in response.user_notifications + ] + ): + check.record_failed( + summary="Error while trying to retrieve user notifications", + details=f"Response from {target.participant_id} returned notifications in the future.", + query_timestamps=[query.request.timestamp], + ) + notifications[target.participant_id] = [] + continue + + notifications[target.participant_id] = response.user_notifications return notifications diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/__init__.py b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/__init__.py index c280d82cc6..c7bbc7f8ab 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/__init__.py @@ -4,7 +4,6 @@ from .misbehavior import Misbehavior as Misbehavior from .networked_uas_disconnect import NetworkedUASDisconnect as NetworkedUASDisconnect from .nominal_behavior import NominalBehavior as NominalBehavior -from .operator_interactions import OperatorInteractions as OperatorInteractions from .sp_notification_behavior import ( ServiceProviderNotificationBehavior as ServiceProviderNotificationBehavior, ) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/aggregate_checks.py b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/aggregate_checks.py index f792e5d06f..de2694a3da 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/aggregate_checks.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/aggregate_checks.py @@ -15,9 +15,9 @@ class AggregateChecks(TestScenario, CommonAggregateChecks): def __init__( self, service_providers: NetRIDServiceProviders, - observers: NetRIDObserversResource, dss_instances: DSSInstancesResource, + observers: NetRIDObserversResource | None = None, test_exclusions: TestExclusionsResource | None = None, ): - super().__init__(service_providers, observers, dss_instances, test_exclusions) + super().__init__(service_providers, dss_instances, observers, test_exclusions) self._rid_version = RIDVersion.f3411_19 diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dp_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dp_behavior.md index 1aaf52c5a3..0898d2b12b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dp_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dp_behavior.md @@ -66,6 +66,14 @@ Prior to ISA creation, the Display Providers of one or more observers may have e ## Display Provider Behavior test case +### Note remote clock test step + +To check whether the mock_uss, acting as Service Provider, received a valid request from the Display Provider due to the following test steps, we must known the earliest time such a request could have been made, according to mock_uss. In this step, we retrieve mock_uss's clock time for "now" so we can only ask for interactions after "now" later in this case. + +#### ⚠️ mock_uss clock time retrievable check + +If mock_uss's current time isn't retrievable, the mock_uss provider's mock_uss does not meet **[interuss.mock_uss.hosted_instance.ExposeInterface](../../../../requirements/interuss/mock_uss/hosted_instance.md)**. + ### Query acceptable diagonal area test step This test step queries the Display Provider for the exact area of the ISA. @@ -112,6 +120,6 @@ If the Display Provider has issued such requests, it is in violation of this req The cleanup phase of this test scenario attempts to remove injected data from all SPs. -### [Clean ISA](./dss/test_steps/clean_workspace.md) +### [Clean ISA](./dss/test_steps/clean_workspace_during_cleanup.md) Remove the created ISA from the DSS. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/datastore_access.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/datastore_access.md index 09ffcd3975..8f13d383b2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/datastore_access.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/datastore_access.md @@ -3,7 +3,9 @@ ## Overview Attempt to directly access the datastore (CockroachDB or Yugabyte) nodes intercommunicating to form the DSS Airspace Representation for the DSS instances under test, for the purpose of determining compliance to certain DSS interoperability requirements. -The psycopg library is used to attempt standard PostgreSQL connections to the nodes as it is the most straightforward way of connecting directly to the datastore nodes while controlling the connection parameters (such as for encryption). + +For CockroachDB, the psycopg library is used to attempt standard PostgreSQL connections to the nodes as it is the most straightforward way of connecting directly to the datastore nodes while controlling the connection parameters (such as for encryption). +For YugabyteDB, RPC messages are sent to the nodes directly. This scenario aims at validating the following requirements: - **[astm.f3411.v19.DSS0110](../../../../../requirements/astm/f3411/v19.md)** @@ -15,12 +17,10 @@ DatastoreDBClusterResource that provides access to a set of CockroachDB or Yugab ## Setup test case ### Validate nodes are reachable test step -Attempt connection with nodes of the cluster to validate that they are reachable. -To do so, this step attempts a connection to each node without strictly requiring an encrypted connection and forcing the use of a password authentication with a dummy password. -As such we expect the node to respond with a failed password authentication. +Attempt connection with nodes of the cluster to validate that they are reachable, by opening a TCP connection to the node. #### 🛑 Node is reachable check -This check succeeds if the node can be reached and that the password authentication attempted by the USS qualifier is rejected. +This check succeeds if the node is reachable. It fails in all other cases. ## Verify security interoperability test case @@ -28,12 +28,13 @@ It fails in all other cases. Attempt connection with nodes of the cluster in insecure mode. It is expected that the connection attempts are rejected due to the fact that all nodes are running in secure mode. -#### ⚠️ Node runs in secure mode check +#### ⚠️ Node enforces encryption of its communications check This check succeeds if the node rejects the insecure connection attempt by the USS qualifier because it is in secure mode. -It fails in all other cases. +If it is in insecure mode, it means that the node does not encrypt its communications as it should per **[astm.f3411.v19.DSS0120](../../../../../requirements/astm/f3411/v19.md)**. +#### ⚠️ Node enforces authentication of its communications check +This check succeeds if the node rejects the insecure connection attempt by the USS qualifier because it is in secure mode. If it is in insecure mode, it means that the node does not authenticate incoming connection attempt as it should per **[astm.f3411.v19.DSS0110](../../../../../requirements/astm/f3411/v19.md)**. -If it is in insecure mode, it means that the node does not encrypt its communications as it should per **[astm.f3411.v19.DSS0120](../../../../../requirements/astm/f3411/v19.md)**. ### Attempt to connect with legacy encryption protocol test step Attempt connection with nodes of the cluster forcing the use of legacy encryption protocols, namely between TLSv1 and TLSv1.1. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_simple.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_simple.md index 7fe7c50dd9..6a9bd55cc5 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/isa_simple.md @@ -212,11 +212,11 @@ The ISA search are parameter cover the resource ISA, but it has been previously The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely. -### 🛑 Successful ISA query check +### ⚠️ Successful ISA query check **[interuss.f3411.dss_endpoints.GetISA](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. -### 🛑 Removed pre-existing ISA check +### ⚠️ Removed pre-existing ISA check If an ISA with the intended ID is still present in the DSS, it needs to be removed before exiting the test. If that ISA cannot be deleted, then the **[astm.f3411.v19.DSS0030,b](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the ISA deletion endpoint might not be met. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_simple.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_simple.md index b10a742217..89fbe5c2b1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_simple.md @@ -387,11 +387,11 @@ If the DSS returns the deleted subscription in a search that covers the area it The cleanup phase of this test scenario removes the subscription with the known test ID if it has not been removed before. -#### 🛑 Subscription can be queried by ID check +#### ⚠️ Subscription can be queried by ID check If the DSS cannot be queried for the existing test ID, the DSS is likely not implementing **[astm.f3411.v19.DSS0030,e](../../../../../requirements/astm/f3411/v19.md)** correctly. -#### 🛑 Subscription can be deleted check +#### ⚠️ Subscription can be deleted check An attempt to delete a subscription when the correct version is provided should succeed, otherwise the DSS is in violation of **[astm.f3411.v19.DSS0030,d](../../../../../requirements/astm/f3411/v19.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_validation.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_validation.md index ff261de178..5e5158c71d 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/subscription_validation.md @@ -93,10 +93,10 @@ The ability to delete an existing subscription is required in **[astm.f3411.v19. The cleanup phase of this test scenario will remove any subscription that may have been created during the test and that intersects with the test ISA. -### 🛑 Successful subscription search query check +### ⚠️ Successful subscription search query check If the query for subscriptions fails, the "GET Subscriptions" portion of **[astm.f3411.v19.DSS0030,f](../../../../../requirements/astm/f3411/v19.md)** was not met. -### 🛑 Subscription can be deleted check +### ⚠️ Subscription can be deleted check If the deletion attempt fails, the "DELETE Subscription" portion of **[astm.f3411.v19.DSS0030,d](../../../../../requirements/astm/f3411/v19.md)** was not met. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/test_steps/clean_workspace_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/test_steps/clean_workspace_during_cleanup.md new file mode 100644 index 0000000000..9339c17a0a --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/test_steps/clean_workspace_during_cleanup.md @@ -0,0 +1,27 @@ +# Ensure clean workspace test step fragment + +This page describes the content of a common test step that ensures a clean workspace for testing interactions with a DSS + +## ⚠️ Successful ISA query check + +**[interuss.f3411.dss_endpoints.GetISA](../../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. + +## ⚠️ Removed pre-existing ISA check + +If an ISA with the intended ID is already present in the DSS, it needs to be removed before proceeding with the test. If that ISA cannot be deleted, then the **[astm.f3411.v19.DSS0030,d](../../../../../../requirements/astm/f3411/v19.md)** requirement to implement the ISA deletion endpoint might not be met. + +## ⚠️ Notified subscriber check + +When a pre-existing ISA needs to be deleted to ensure a clean workspace, any subscribers to ISAs in that area must be notified (as specified by the DSS). If a notification cannot be delivered, then the **[astm.f3411.v19.NET0730](../../../../../../requirements/astm/f3411/v19.md)** requirement to implement the POST ISAs endpoint isn't met. + +## ⚠️ Successful subscription search query check + +**[astm.f3411.v19.DSS0030,f](../../../../../../requirements/astm/f3411/v19.md)** requires the implementation of the DSS endpoint to allow callers to retrieve the subscriptions they created. + +## ⚠️ Subscription can be queried by ID check + +If the DSS cannot be queried for the existing test ID, the DSS is likely not implementing **[astm.f3411.v19.DSS0030,e](../../../../../../requirements/astm/f3411/v19.md)** correctly. + +## ⚠️ Subscription can be deleted check + +**[astm.f3411.v19.DSS0030,d](../../../../../../requirements/astm/f3411/v19.md)** requires the implementation of the DSS endpoint to allow callers to delete subscriptions they created. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/token_validation.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/token_validation.md index 5f5960d5e0..43f2752ef8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/token_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/dss/token_validation.md @@ -107,11 +107,11 @@ When an ISA is deleted, subscribers must be notified. If a subscriber cannot be The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely. -### 🛑 Successful ISA query check +### ⚠️ Successful ISA query check **[interuss.f3411.dss_endpoints.GetISA](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. -### 🛑 Removed pre-existing ISA check +### ⚠️ Removed pre-existing ISA check If an ISA with the intended ID is still present in the DSS, it needs to be removed before exiting the test. If that ISA cannot be deleted, then the **[astm.f3411.v19.DSS0030,b](../../../../../requirements/astm/f3411/v19.md)** requirement to implement the ISA deletion endpoint might not be met. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_polling_queries.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_polling_queries.md index ac9330f45a..7756ca7263 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_polling_queries.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_polling_queries.md @@ -2,10 +2,14 @@ uss_qualifier acts as a Display Provider to query Service Providers under test in this step. -## ⚠️ ISA query check +## ⚠️ Successful ISA query check **[interuss.f3411.dss_endpoints.SearchISAs](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires a USS providing a DSS instance to implement the DSS endpoints of the OpenAPI specification. If uss_qualifier is unable to query the DSS for ISAs, this check will fail. +## ⚠️ Successful flight query check + +**[astm.f3411.v19.NET0710,1](../../../../../requirements/astm/f3411/v19.md)** and **[astm.f3411.v19.NET0340](../../../../../requirements/astm/f3411/v19.md) require a Service Provider to implement the GET flight endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. + ## ⚠️ Successful flight details query check **[astm.f3411.v19.NET0710,2](../../../../../requirements/astm/f3411/v19.md)** and **[astm.f3411.v19.NET0340](../../../../../requirements/astm/f3411/v19.md) require a Service Provider to implement the GET flight details endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_simple_queries.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_simple_queries.md new file mode 100644 index 0000000000..9b1a6821a5 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/fragments/sp_simple_queries.md @@ -0,0 +1,11 @@ +# Service provider queries test step fragment + +uss_qualifier acts as a Display Provider to query Service Providers under test in this step, without fetching details. + +## ⚠️ Successful ISA query check + +**[interuss.f3411.dss_endpoints.SearchISAs](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires a USS providing a DSS instance to implement the DSS endpoints of the OpenAPI specification. If uss_qualifier is unable to query the DSS for ISAs, this check will fail. + +## ⚠️ Successful flight query check + +**[astm.f3411.v19.NET0710,1](../../../../../requirements/astm/f3411/v19.md)** and **[astm.f3411.v19.NET0340](../../../../../requirements/astm/f3411/v19.md) require a Service Provider to implement the GET flight endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md index c0c5f7fd37..8f5bd240ca 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md @@ -24,24 +24,13 @@ A [`DSSInstanceResource`](../../../../resources/astm/f3411/dss.py) is required f ## Invalid requests test case -### Injection test step - -In this step, uss_qualifier injects a single nominal flight into each SP under test, usually with a start time in the future. Each SP is expected to queue the provided telemetry and later simulate that telemetry coming from an aircraft at the designated timestamps. - -#### 🛑 Successful injection check - -Per **[interuss.automated_testing.rid.injection.UpsertTestSuccess](../../../../requirements/interuss/automated_testing/rid/injection.md)**, the injection attempt of the valid flight should succeed for every NetRID Service Provider under test. - -**[astm.f3411.v19.NET0500](../../../../requirements/astm/f3411/v19.md)** requires a Service Provider to provide a persistently supported test instance of their implementation. -This check will fail if the flight was not successfully injected. - -#### 🛑 Identifiable flights check - -This particular test requires each flight to be uniquely identifiable by its 2D telemetry position; the same (lat, lng) pair may not appear in two different telemetry points, even if the two points are in different injected flights. This should generally be achieved by injecting appropriate data. +### [Injection test step](./fragments/flight_injection.md) ### Invalid search area test step -This step will attempt to search for flights in a rectangular area with a diagonal greater than [NetMaxDisplayAreaDiagonal] km. +This step will attempt to search for flights in a rectangular area with a diagonal greater than [NetMaxDisplayAreaDiagonal] km. First, the Service Providers with service in the large area will be determined from the DSS and then each Service Provider will be queried for flights (this should succeed). Then each Service Provider will be queried again for flights, this time using an unacceptably-large area (this should fail). + +#### [Service provider queries test step](../v19/fragments/sp_simple_queries.md) #### ⚠️ Area too large check @@ -49,11 +38,12 @@ This step will attempt to search for flights in a rectangular area with a diagon ### Unauthenticated requests test step -In order to properly test whether the SP handles authentication correctly, this step will first attempt to do a request with the proper credentials -to confirm that the requested data is indeed available to any authorized query. +in order to properly test whether the SP handles authentication correctly, after identifying the SP contact information via its ISA in the DSS, this step will first attempt to do a flights request with the proper credentials to confirm that the requested data is indeed available to any authorized query. It then repeats the exact same request without credentials, and expects this to fail. +#### [Service provider queries test step](../v19/fragments/sp_simple_queries.md) + #### ⚠️ Missing credentials check This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v19.NET0210](../../../../requirements/astm/f3411/v19.md)**, @@ -63,6 +53,8 @@ and that requests for existing flights that are executed with missing credential This step is similar to unauthenticated requests, but uses incorrectly-authenticated requests instead. +#### [Service provider queries test step](../v19/fragments/sp_simple_queries.md) + #### ⚠️ Invalid credentials check This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v19.NET0210](../../../../requirements/astm/f3411/v19.md)**, and that requests for existing flights that are executed with incorrect credentials fail. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/networked_uas_disconnect.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/networked_uas_disconnect.md index 3efb5598ae..be138d52d6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/networked_uas_disconnect.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/networked_uas_disconnect.md @@ -52,11 +52,7 @@ This particular test requires each flight to be uniquely identifiable by its 2D ### Service Provider polling test step -uss_qualifier acts as a Display Provider to query Service Providers under test in this step. - -#### ⚠️ ISA query check - -**[interuss.f3411.dss_endpoints.SearchISAs](../../../../requirements/interuss/f3411/dss_endpoints.md)** requires a USS providing a DSS instance to implement the DSS endpoints of the OpenAPI specification. If uss_qualifier is unable to query the DSS for ISAs, this check will fail. +#### [Service Provider polling test step](./fragments/sp_polling.md) #### [Flight presence checks](./display_data_evaluator_flight_presence.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/operator_interactions.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/operator_interactions.md deleted file mode 100644 index ecb57ff4d6..0000000000 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/operator_interactions.md +++ /dev/null @@ -1,97 +0,0 @@ -# ASTM NetRID: Operator interactions test scenario - -## Overview - -Set up situations in which operator interactions (notifications) are required, and verify that those notifications are observed for the USS under test. - -Note that none of this scenario is implemented yet. - -## Resources - -## Future resources - -### service_provider - -A singular `NetRIDServiceProvider` to be tested via the injection of RID flight data. - -TODO: Create this resource - -### operator_notifications - -Means by which to ask "What user/operator notifications have been observed for user/operator X (from the USS under test) over time period Z". - -TODO: Create this resource - -### flights_data - -A [`FlightDataResource`](../../../../resources/netrid/flight_data.py) containing 1 flight. This flight must: -* (Phase 1): Start out nominal -* (Phase 2): Then contain a pause (lack of telemetry) sufficient to trigger NET0040 -* (Phase 3): Then resume nominal telemetry -* (Phase 4): Then contain insufficient data to trigger NET0030 - -### orchestrated_dss - -A DSS instance that is equipped to fail on command, and will be used by the USS under test. - -TODO: Create this resource - -## Failed ISA test case - -### Verify no ISAs test step - -uss_qualifier checks the DSS to ensure that the Service Provider under test does not have any ISAs in the system. If ISAs are present, the Service Provider is instructed to clear the area of active flights, after which uss_qualifier reverifies the absence of ISAs. - -### Disable DSS test step - -uss_qualifier commands the orchestrated DSS to fail when interacting with normal clients. - -### Enumerate pre-existing operator notifications test step - -uss_qualifier retrieves the current (pre-existing) set of operator notifications. - -### Inject flight test step - -uss_qualifier attempts to inject a flight into the Service Provider under test, knowing that the Service Provider will not be able to create an ISA. - -#### 🛑 Flight failed check - -Since the DSS is known to fail when attempting to create an ISA, if the Service Provider successfully creates the flight, they will have not met **[astm.f3411.v19.NET0610](../../../../requirements/astm/f3411/v19.md)**. - -TODO: Implement - -### Enumerate operator notifications test step - -uss_qualifier retrieves the current (after failed flight) set of operator notifications. - -#### 🛑 Operator notified of discoverability failure check - -The "after" set of operator notifications should contain at least one more entry than the "before" set of operator notifications. If there was no new operator notification, the Service Provider will not have met **[astm.f3411.v19.NET0620](../../../../requirements/astm/f3411/v19.md)**. - -TODO: Implement - -## In-flight notifications test case - -### Inject flight test step - -uss_qualifier injects the flight into the Service Provider under test with the intention of observing the whole flight. - -### Enumerate pre-existing operator notifications test step - -uss_qualifier retrieves the current (pre-existing) set of operator notifications. - -### Poll Service Provider test step - -Throughout the duration of the flight, uss_qualifier periodically polls for operator notifications. - -#### 🛑 Insufficient telemetry operator notification check - -If the Service Provider under test does not provide an operator notification in Phase 2 (regarding the telemetry feed stopping), it will not have complied with **[astm.f3411.v19.NET0040](../../../../requirements/astm/f3411/v19.md)**. - -TODO: Implement - -#### 🛑 Missing data operator notification check - -If the Service Provider under test does not provide an operator notification in Phase 4 (regarding missing data fields in reported telemetry), it will not have complied with **[astm.f3411.v19.NET0030](../../../../requirements/astm/f3411/v19.md)**. - -TODO: Implement diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/operator_interactions.py b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/operator_interactions.py deleted file mode 100644 index c914c7c691..0000000000 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/operator_interactions.py +++ /dev/null @@ -1,8 +0,0 @@ -from monitoring.uss_qualifier.scenarios.astm.netrid.common.operator_interactions import ( - OperatorInteractions as CommonOperatorInteractions, -) -from monitoring.uss_qualifier.scenarios.scenario import TestScenario - - -class OperatorInteractions(TestScenario, CommonOperatorInteractions): - pass diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md index 249d43fae1..04c9f50c84 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/sp_notification_behavior.md @@ -28,6 +28,10 @@ A [`DSSInstancesResource`](../../../../resources/astm/f3411/dss.py) from which a [`IDGeneratorResource`](../../../../resources/interuss/id_generator.py) providing the Subscription ID for this scenario. +### uss_identification + +[`USSIdentificationResource`](../../../../resources/interuss/uss_identification.py) describing how to identify participants responsible for observed notifications. + ## Setup test case ### [Clean workspace test step](./dss/test_steps/clean_workspace.md) @@ -48,6 +52,10 @@ start and end time missing, provided all the required parameters are valid. In this step, uss_qualifier injects a single nominal flight into each SP under test, usually with a start time in the future. Each SP is expected to queue the provided telemetry and later simulate that telemetry coming from an aircraft at the designated timestamps. +#### 🛑 mock_uss clock time retrievable check + +We need to know mock_uss's clock time to later request observed interactions after the injection time. If mock_uss's current time isn't retrievable, the mock_uss provider's mock_uss does not meet **[interuss.mock_uss.hosted_instance.ExposeInterface](../../../../requirements/interuss/mock_uss/hosted_instance.md)**. + ### Validate Mock USS received notification test step This test step verifies that the mock_uss for which a subscription was registered before flight injection properly received a notification from each Service Provider @@ -71,6 +79,6 @@ The cleanup phase of this test scenario attempts to remove injected data from al **[interuss.automated_testing.rid.injection.DeleteTestSuccess](../../../../requirements/interuss/automated_testing/rid/injection.md)** -### [Clean Subscriptions](./dss/test_steps/clean_workspace.md) +### [Clean Subscriptions](./dss/test_steps/clean_workspace_during_cleanup.md) Remove all created subscriptions from the DSS. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/__init__.py b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/__init__.py index c280d82cc6..c7bbc7f8ab 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/__init__.py @@ -4,7 +4,6 @@ from .misbehavior import Misbehavior as Misbehavior from .networked_uas_disconnect import NetworkedUASDisconnect as NetworkedUASDisconnect from .nominal_behavior import NominalBehavior as NominalBehavior -from .operator_interactions import OperatorInteractions as OperatorInteractions from .sp_notification_behavior import ( ServiceProviderNotificationBehavior as ServiceProviderNotificationBehavior, ) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/aggregate_checks.py b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/aggregate_checks.py index 140be819e4..5c2b2abfd1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/aggregate_checks.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/aggregate_checks.py @@ -15,9 +15,9 @@ class AggregateChecks(TestScenario, CommonAggregateChecks): def __init__( self, service_providers: NetRIDServiceProviders, - observers: NetRIDObserversResource, dss_instances: DSSInstancesResource, + observers: NetRIDObserversResource | None = None, test_exclusions: TestExclusionsResource | None = None, ): - super().__init__(service_providers, observers, dss_instances, test_exclusions) + super().__init__(service_providers, dss_instances, observers, test_exclusions) self._rid_version = RIDVersion.f3411_22a diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight_details.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight_details.md index afb4eb4175..083bf4d9c4 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight_details.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight_details.md @@ -93,6 +93,14 @@ NET0260 requires that relevant Remote ID data, consistent with the common data d NET0260 requires that relevant Remote ID data, consistent with the common data dictionary, be reported by the Service Provider. This check validates that if the Operator Altitude is based on WGS-84 height above ellipsoid (HAE) and is provided in meters. (**[astm.f3411.v22a.NET0260,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) +## ⚠️ Operator Altitude is consistent with injected one check + +If the operator altitude is exposed by the SP API, but is inconsistent with the injected value, this check will fail per **[astm.f3411.v22a.NET0260,Table1,25](../../../../requirements/astm/f3411/v22a.md)**. + ## ⚠️ Operator Altitude Type consistency with Common Dictionary check NET0260 requires that relevant Remote ID data, consistent with the common data dictionary, be reported by the Service Provider. This check validates that if the Operator Altitude Type is valid, if present. (**[astm.f3411.v22a.NET0260,Table1,26](../../../../requirements/astm/f3411/v22a.md)**) + +## ⚠️ Operator Altitude Type is consistent with injected one check + +If the operator altitude type is exposed by the SP API, but is inconsistent with the injected value, this check will fail per **[astm.f3411.v22a.NET0260,Table1,26](../../../../requirements/astm/f3411/v22a.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/display_data_evaluator_flight_presence.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/display_data_evaluator_flight_presence.md index 8eb16f0e10..f50111fa46 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/display_data_evaluator_flight_presence.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/display_data_evaluator_flight_presence.md @@ -10,9 +10,9 @@ The timestamps of the injected telemetry usually start in the future. If a flig **[astm.f3411.v22a.NET0610](../../../../requirements/astm/f3411/v22a.md)** requires that SPs make all UAS operations discoverable over the duration of the flight plus *NetMaxNearRealTimeDataPeriod*, so each injected flight should be observable during this time. If a flight is not observed during its appropriate time period, this check will fail. -**[astm.f3411.v22a.NET0710,1](../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0340](../../../../requirements/astm/f3411/v22a.md) require a Service Provider to implement the GET flights endpoint. This check will also fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. +**[astm.f3411.v22a.NET0710,1](../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0340](../../../../requirements/astm/f3411/v22a.md)** require a Service Provider to implement the GET flights endpoint. This check will also fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. -The identity of flights is determined by precisely matching the known injected positions. If the flight can be found, the USS may not have met **[astm.f3411.v22a.NET0260,Table1,10](../../../../requirements/astm/f3411/v22a.md)** or **[astm.f3411.v22a.NET0260,Table1,11](../../../../requirements/astm/f3411/v22a.md)** prescribing provision of position data consistent with the common data dictionary. +The identity of flights is determined by precisely matching the known injected positions. If the flight cannot be found, the USS may not have met **[astm.f3411.v22a.NET0260,Table1,10](../../../../requirements/astm/f3411/v22a.md)** or **[astm.f3411.v22a.NET0260,Table1,11](../../../../requirements/astm/f3411/v22a.md)** prescribing provision of position data consistent with the common data dictionary. ## ⚠️ Lingering flight check diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dp_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dp_behavior.md index d48e7dda46..babd52231f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dp_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dp_behavior.md @@ -66,6 +66,14 @@ Prior to ISA creation, the Display Providers of one or more observers may have e ## Display Provider Behavior test case +### Note remote clock test step + +To check whether the mock_uss, acting as Service Provider, received a valid request from the Display Provider due to the following test steps, we must known the earliest time such a request could have been made, according to mock_uss. In this step, we retrieve mock_uss's clock time for "now" so we can only ask for interactions after "now" later in this case. + +#### ⚠️ mock_uss clock time retrievable check + +If mock_uss's current time isn't retrievable, the mock_uss provider's mock_uss does not meet **[interuss.mock_uss.hosted_instance.ExposeInterface](../../../../requirements/interuss/mock_uss/hosted_instance.md)**. + ### Query acceptable diagonal area test step This test step queries the Display Provider for the exact area of the ISA. @@ -112,6 +120,6 @@ If the Display Provider has issued such requests, it is in violation of this req The cleanup phase of this test scenario attempts to remove injected data from all SPs. -### [Clean ISA](./dss/test_steps/clean_workspace.md) +### [Clean ISA](./dss/test_steps/clean_workspace_during_cleanup.md) Remove the created ISA from the DSS. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/datastore_access.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/datastore_access.md index 409cf0343c..b3f028ddb2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/datastore_access.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/datastore_access.md @@ -3,7 +3,9 @@ ## Overview Attempt to directly access the datastore (CockroachDB or Yugabyte) nodes intercommunicating to form the DSS Airspace Representation for the DSS instances under test, for the purpose of determining compliance to certain DSS interoperability requirements. -The psycopg library is used to attempt standard PostgreSQL connections to the nodes as it is the most straightforward way of connecting directly to the datastore nodes while controlling the connection parameters (such as for encryption). + +For CockroachDB, the psycopg library is used to attempt standard PostgreSQL connections to the nodes as it is the most straightforward way of connecting directly to the datastore nodes while controlling the connection parameters (such as for encryption). +For YugabyteDB, RPC messages are sent to the nodes directly. This scenario aims at validating the following requirements: - **[astm.f3411.v22a.DSS0110](../../../../../requirements/astm/f3411/v22a.md)** @@ -15,12 +17,10 @@ DatastoreDBClusterResource that provides access to a set of CockroachDB or Yugab ## Setup test case ### Validate nodes are reachable test step -Attempt connection with nodes of the cluster to validate that they are reachable. -To do so, this step attempts a connection to each node without strictly requiring an encrypted connection and forcing the use of a password authentication with a dummy password. -As such we expect the node to respond with a failed password authentication. +Attempt connection with nodes of the cluster to validate that they are reachable, by opening a TCP connection to the node. #### 🛑 Node is reachable check -This check succeeds if the node can be reached and that the password authentication attempted by the USS qualifier is rejected. +This check succeeds if the node is reachable. It fails in all other cases. ## Verify security interoperability test case @@ -28,12 +28,13 @@ It fails in all other cases. Attempt connection with nodes of the cluster in insecure mode. It is expected that the connection attempts are rejected due to the fact that all nodes are running in secure mode. -#### ⚠️ Node runs in secure mode check +#### ⚠️ Node enforces encryption of its communications check This check succeeds if the node rejects the insecure connection attempt by the USS qualifier because it is in secure mode. -It fails in all other cases. +If it is in insecure mode, it means that the node does not encrypt its communications as it should per **[astm.f3411.v22a.DSS0120](../../../../../requirements/astm/f3411/v22a.md)**. +#### ⚠️ Node enforces authentication of its communications check +This check succeeds if the node rejects the insecure connection attempt by the USS qualifier because it is in secure mode. If it is in insecure mode, it means that the node does not authenticate incoming connection attempt as it should per **[astm.f3411.v22a.DSS0110](../../../../../requirements/astm/f3411/v22a.md)**. -If it is in insecure mode, it means that the node does not encrypt its communications as it should per **[astm.f3411.v22a.DSS0120](../../../../../requirements/astm/f3411/v22a.md)**. ### Attempt to connect with legacy encryption protocol test step Attempt connection with nodes of the cluster forcing the use of legacy encryption protocols, namely between TLSv1 and TLSv1.1. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_simple.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_simple.md index f3b3dca894..56acb64ed1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/isa_simple.md @@ -212,11 +212,11 @@ The ISA search are parameter cover the resource ISA, but it has been previously The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely. -### 🛑 Successful ISA query check +### ⚠️ Successful ISA query check **[interuss.f3411.dss_endpoints.GetISA](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. -### 🛑 Removed pre-existing ISA check +### ⚠️ Removed pre-existing ISA check If an ISA with the intended ID is still present in the DSS, it needs to be removed before exiting the test. If that ISA cannot be deleted, then the **[astm.f3411.v22a.DSS0030,b](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the ISA deletion endpoint might not be met. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_simple.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_simple.md index 5a4dce86fd..b45f79a6ef 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_simple.md @@ -380,10 +380,10 @@ If the DSS returns the deleted subscription in a search that covers the area it The cleanup phase of this test scenario removes the subscription with the known test ID if it has not been removed before. -#### 🛑 Subscription can be queried by ID check +#### ⚠️ Subscription can be queried by ID check If the DSS cannot be queried for the existing test ID, the DSS is likely not implementing **[astm.f3411.v22a.DSS0030,e](../../../../../requirements/astm/f3411/v22a.md)** correctly. -#### 🛑 Subscription can be deleted check +#### ⚠️ Subscription can be deleted check An attempt to delete a subscription when the correct version is provided should succeed, otherwise the DSS is in violation of **[astm.f3411.v22a.DSS0030,d](../../../../../requirements/astm/f3411/v22a.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_validation.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_validation.md index 7eb5ea4fdc..6cb33f691c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/subscription_validation.md @@ -93,10 +93,10 @@ The ability to delete an existing subscription is required in **[astm.f3411.v22a The cleanup phase of this test scenario will remove any subscription that may have been created during the test and that intersects with the test ISA. -### 🛑 Successful subscription search query check +### ⚠️ Successful subscription search query check If the query for subscriptions fails, the "GET Subscriptions" portion of **[astm.f3411.v22a.DSS0030,f](../../../../../requirements/astm/f3411/v22a.md)** was not met. -### 🛑 Subscription can be deleted check +### ⚠️ Subscription can be deleted check If the deletion attempt fails, the "DELETE Subscription" portion of **[astm.f3411.v22a.DSS0030,d](../../../../../requirements/astm/f3411/v22a.md)** was not met. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/test_steps/clean_workspace_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/test_steps/clean_workspace_during_cleanup.md new file mode 100644 index 0000000000..c0d15bbdc7 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/test_steps/clean_workspace_during_cleanup.md @@ -0,0 +1,27 @@ +# Ensure clean workspace test step fragment + +This page describes the content of a common test step that ensures a clean workspace for testing interactions with a DSS + +## ⚠️ Successful ISA query check + +**[interuss.f3411.dss_endpoints.GetISA](../../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. + +## ⚠️ Removed pre-existing ISA check + +If an ISA with the intended ID is already present in the DSS, it needs to be removed before proceeding with the test. If that ISA cannot be deleted, then the **[astm.f3411.v22a.DSS0030,d](../../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the ISA deletion endpoint might not be met. + +## ⚠️ Notified subscriber check + +When a pre-existing ISA needs to be deleted to ensure a clean workspace, any subscribers to ISAs in that area must be notified (as specified by the DSS). If a notification cannot be delivered, then the **[astm.f3411.v22a.NET0730](../../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the POST ISAs endpoint isn't met. + +## ⚠️ Successful subscription search query check + +**[astm.f3411.v22a.DSS0030,f](../../../../../../requirements/astm/f3411/v22a.md)** requires the implementation of the DSS endpoint to allow callers to retrieve the subscriptions they created. + +## ⚠️ Subscription can be queried by ID check + +If the DSS cannot be queried for the existing test ID, the DSS is likely not implementing **[astm.f3411.v22a.DSS0030,e](../../../../../../requirements/astm/f3411/v22a.md)** correctly. + +## ⚠️ Subscription can be deleted check + +**[astm.f3411.v22a.DSS0030,d](../../../../../../requirements/astm/f3411/v22a.md)** requires the implementation of the DSS endpoint to allow callers to delete subscriptions they created. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/token_validation.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/token_validation.md index 72dbe5a774..e29c05b5fa 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/token_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss/token_validation.md @@ -107,11 +107,11 @@ When an ISA is deleted, subscribers must be notified. If a subscriber cannot be The cleanup phase of this test scenario attempts to remove the ISA if the test ended prematurely. -### 🛑 Successful ISA query check +### ⚠️ Successful ISA query check **[interuss.f3411.dss_endpoints.GetISA](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires the implementation of the DSS endpoint enabling retrieval of information about a specific ISA; if the individual ISA cannot be retrieved and the error isn't a 404, then this requirement isn't met. -### 🛑 Removed pre-existing ISA check +### ⚠️ Removed pre-existing ISA check If an ISA with the intended ID is still present in the DSS, it needs to be removed before exiting the test. If that ISA cannot be deleted, then the **[astm.f3411.v22a.DSS0030,b](../../../../../requirements/astm/f3411/v22a.md)** requirement to implement the ISA deletion endpoint might not be met. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss_interoperability.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss_interoperability.md index e508ad790b..257b41d5ee 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss_interoperability.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/dss_interoperability.md @@ -55,12 +55,12 @@ the note to wait >D seconds from a particular time ### Test environment requirements test step -#### 🛑 DSS instance is publicly addressable check +#### ⚠️ DSS instance is publicly addressable check As per **[astm.f3411.v22a.DSS0210](../../../../requirements/astm/f3411/v22a.md)** the DSS instance should be publicly addressable. As such, this check will fail if the resolved IP of the DSS host is a private IP address. This check is skipped if the test exclusion `allow_private_addresses` is set to `True`. -#### 🛑 DSS instance is reachable check +#### ⚠️ DSS instance is reachable check As per **[astm.f3411.v22a.DSS0210](../../../../requirements/astm/f3411/v22a.md)** the DSS instance should be publicly addressable. As such, this check will fail if the DSS is not reachable with a dummy query, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_polling_queries.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_polling_queries.md index ba5684bad1..853d699122 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_polling_queries.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_polling_queries.md @@ -2,10 +2,14 @@ uss_qualifier acts as a Display Provider to query Service Providers under test in this step. -## ⚠️ ISA query check +## ⚠️ Successful ISA query check -**[interuss.f3411.dss_endpoints.SearchISAs](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires a USS providing a DSS instance to implement the DSS endpoints of the OpenAPI specification. If uss_qualifier is unable to query the DSS for ISAs, this check will fail. +**[interuss.f3411.dss_endpoints.SearchISAs](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires a USS providing a DSS instance to implement the DSS endpoints of the OpenAPI specification. If uss_qualifier is unable to query the DSS for ISAs, this check will fail. + +## ⚠️ Successful flight query check + +**[astm.f3411.v22a.NET0710,1](../../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0340](../../../../../requirements/astm/f3411/v22a.md)** require a Service Provider to implement the GET flight endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. ## ⚠️ Successful flight details query check -**[astm.f3411.v22a.NET0710,2](../../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0340](../../../../../requirements/astm/f3411/v22a.md) require a Service Provider to implement the GET flight details endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. +**[astm.f3411.v22a.NET0710,2](../../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0340](../../../../../requirements/astm/f3411/v22a.md)** require a Service Provider to implement the GET flight details endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_simple_queries.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_simple_queries.md new file mode 100644 index 0000000000..3fda05711c --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/fragments/sp_simple_queries.md @@ -0,0 +1,11 @@ +# Service provider queries test step fragment + +uss_qualifier acts as a Display Provider to query Service Providers under test in this step, without fetching details. + +## ⚠️ Successful ISA query check + +**[interuss.f3411.dss_endpoints.SearchISAs](../../../../../requirements/interuss/f3411/dss_endpoints.md)** requires a USS providing a DSS instance to implement the DSS endpoints of the OpenAPI specification. If uss_qualifier is unable to query the DSS for ISAs, this check will fail. + +## ⚠️ Successful flight query check + +**[astm.f3411.v22a.NET0710,1](../../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0340](../../../../../requirements/astm/f3411/v22a.md) require a Service Provider to implement the GET flight endpoint. This check will fail if uss_qualifier cannot query that endpoint (specified in the ISA present in the DSS) successfully. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md index e958a23532..54ca78787b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md @@ -28,7 +28,9 @@ A [`DSSInstanceResource`](../../../../resources/astm/f3411/dss.py) is required f ### Invalid search area test step -This step will attempt to search for flights in a rectangular area with a diagonal greater than [NetMaxDisplayAreaDiagonal] km. +This step will attempt to search for flights in a rectangular area with a diagonal greater than [NetMaxDisplayAreaDiagonal] km. First, the Service Providers with service in the large area will be determined from the DSS and then each Service Provider will be queried for flights (this should succeed). Then each Service Provider will be queried again for flights, this time using an unacceptably-large area (this should fail). + +#### [Service provider queries test step](../v22a/fragments/sp_simple_queries.md) #### ⚠️ Area too large check @@ -36,11 +38,12 @@ This step will attempt to search for flights in a rectangular area with a diagon ### Unauthenticated requests test step -In order to properly test whether the SP handles authentication correctly, this step will first attempt to do a request with the proper credentials -to confirm that the requested data is indeed available to any authorized query. +in order to properly test whether the SP handles authentication correctly, after identifying the SP contact information via its ISA in the DSS, this step will first attempt to do a flights request with the proper credentials to confirm that the requested data is indeed available to any authorized query. It then repeats the exact same request without credentials, and expects this to fail. +#### [Service provider queries test step](../v22a/fragments/sp_simple_queries.md) + #### ⚠️ Missing credentials check This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v22a.NET0210](../../../../requirements/astm/f3411/v22a.md)**, @@ -50,6 +53,8 @@ and that requests for existing flights that are executed with missing credential This step is similar to unauthenticated requests, but uses incorrectly-authenticated requests instead. +#### [Service provider queries test step](../v22a/fragments/sp_simple_queries.md) + #### ⚠️ Invalid credentials check This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v22a.NET0210](../../../../requirements/astm/f3411/v22a.md)**, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/operator_interactions.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/operator_interactions.md deleted file mode 100644 index b684d1cd13..0000000000 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/operator_interactions.md +++ /dev/null @@ -1,97 +0,0 @@ -# ASTM NetRID: Operator interactions test scenario - -## Overview - -Set up situations in which operator interactions (notifications) are required, and verify that those notifications are observed for the USS under test. - -Note that none of this scenario is implemented yet. - -## Resources - -## Future resources - -### service_provider - -A singular `NetRIDServiceProvider` to be tested via the injection of RID flight data. - -TODO: Create this resource - -### operator_notifications - -Means by which to ask "What user/operator notifications have been observed for user/operator X (from the USS under test) over time period Z". - -TODO: Create this resource - -### flights_data - -A [`FlightDataResource`](../../../../resources/netrid/flight_data.py) containing 1 flight. This flight must: -* (Phase 1): Start out nominal -* (Phase 2): Then contain a pause (lack of telemetry) sufficient to trigger NET0040 -* (Phase 3): Then resume nominal telemetry -* (Phase 4): Then contain insufficient data to trigger NET0030 - -### orchestrated_dss - -A DSS instance that is equipped to fail on command, and will be used by the USS under test. - -TODO: Create this resource - -## Failed ISA test case - -### Verify no ISAs test step - -uss_qualifier checks the DSS to ensure that the Service Provider under test does not have any ISAs in the system. If ISAs are present, the Service Provider is instructed to clear the area of active flights, after which uss_qualifier reverifies the absence of ISAs. - -### Disable DSS test step - -uss_qualifier commands the orchestrated DSS to fail when interacting with normal clients. - -### Enumerate pre-existing operator notifications test step - -uss_qualifier retrieves the current (pre-existing) set of operator notifications. - -### Inject flight test step - -uss_qualifier attempts to inject a flight into the Service Provider under test, knowing that the Service Provider will not be able to create an ISA. - -#### 🛑 Flight failed check - -Since the DSS is known to fail when attempting to create an ISA, if the Service Provider successfully creates the flight, they will have not met **[astm.f3411.v22a.NET0610](../../../../requirements/astm/f3411/v22a.md)**. - -TODO: Implement - -### Enumerate operator notifications test step - -uss_qualifier retrieves the current (after failed flight) set of operator notifications. - -#### 🛑 Operator notified of discoverability failure check - -The "after" set of operator notifications should contain at least one more entry than the "before" set of operator notifications. If there was no new operator notification, the Service Provider will not have met **[astm.f3411.v22a.NET0620](../../../../requirements/astm/f3411/v22a.md)**. - -TODO: Implement - -## In-flight notifications test case - -### Inject flight test step - -uss_qualifier injects the flight into the Service Provider under test with the intention of observing the whole flight. - -### Enumerate pre-existing operator notifications test step - -uss_qualifier retrieves the current (pre-existing) set of operator notifications. - -### Poll Service Provider test step - -Throughout the duration of the flight, uss_qualifier periodically polls for operator notifications. - -#### 🛑 Insufficient telemetry operator notification check - -If the Service Provider under test does not provide an operator notification in Phase 2 (regarding the telemetry feed stopping), it will not have complied with **[astm.f3411.v22a.NET0040](../../../../requirements/astm/f3411/v22a.md)**. - -TODO: Implement - -#### 🛑 Missing data operator notification check - -If the Service Provider under test does not provide an operator notification in Phase 4 (regarding missing data fields in reported telemetry), it will not have complied with **[astm.f3411.v22a.NET0030](../../../../requirements/astm/f3411/v22a.md)**. - -TODO: Implement diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/operator_interactions.py b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/operator_interactions.py deleted file mode 100644 index c914c7c691..0000000000 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/operator_interactions.py +++ /dev/null @@ -1,8 +0,0 @@ -from monitoring.uss_qualifier.scenarios.astm.netrid.common.operator_interactions import ( - OperatorInteractions as CommonOperatorInteractions, -) -from monitoring.uss_qualifier.scenarios.scenario import TestScenario - - -class OperatorInteractions(TestScenario, CommonOperatorInteractions): - pass diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md index c8d1cc819c..90e485ec52 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/sp_notification_behavior.md @@ -28,6 +28,10 @@ A [`DSSInstancesResource`](../../../../resources/astm/f3411/dss.py) from which a [`IDGeneratorResource`](../../../../resources/interuss/id_generator.py) providing the Subscription ID for this scenario. +### uss_identification + +[`USSIdentificationResource`](../../../../resources/interuss/uss_identification.py) describing how to identify participants responsible for observed notifications. + ## Setup test case ### [Clean workspace test step](./dss/test_steps/clean_workspace.md) @@ -48,6 +52,10 @@ start and end time missing, provided all the required parameters are valid. In this step, uss_qualifier injects a single nominal flight into each SP under test, usually with a start time in the future. Each SP is expected to queue the provided telemetry and later simulate that telemetry coming from an aircraft at the designated timestamps. +#### 🛑 mock_uss clock time retrievable check + +We need to know mock_uss's clock time to later request observed interactions after the injection time. If mock_uss's current time isn't retrievable, the mock_uss provider's mock_uss does not meet **[interuss.mock_uss.hosted_instance.ExposeInterface](../../../../requirements/interuss/mock_uss/hosted_instance.md)**. + ### Validate Mock USS received notification test step This test step verifies that the mock_uss for which a subscription was registered before flight injection properly received a notification from each Service Provider @@ -71,6 +79,6 @@ The cleanup phase of this test scenario attempts to remove injected data from al **[interuss.automated_testing.rid.injection.DeleteTestSuccess](../../../../requirements/interuss/automated_testing/rid/injection.md)** -### [Clean Subscriptions](./dss/test_steps/clean_workspace.md) +### [Clean Subscriptions](./dss/test_steps/clean_workspace_during_cleanup.md) Remove all created subscriptions from the DSS. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/virtual_observer.py b/monitoring/uss_qualifier/scenarios/astm/netrid/virtual_observer.py index 79c4fe0799..a0dd73cc40 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/virtual_observer.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/virtual_observer.py @@ -5,7 +5,6 @@ from loguru import logger from s2sphere import LatLngRect -from monitoring.monitorlib.delay import sleep from monitoring.uss_qualifier.scenarios.astm.netrid.injected_flight_collection import ( InjectedFlightCollection, ) @@ -37,17 +36,22 @@ class indicates their behavior by computing the query rectangle at each _last_rect: LatLngRect | None = None """The most recent query rectangle""" + _sleep: Callable[[float | timedelta, str], None] + """Means by which to cause a delay.""" + def __init__( self, injected_flights: InjectedFlightCollection, repeat_query_rect_period: int, min_query_diagonal_m: float, relevant_past_data_period: timedelta, + sleep: Callable[[float | timedelta, str], None], ): self._injected_flights = injected_flights self._repeat_query_rect_period = repeat_query_rect_period self._min_query_diagonal_m = min_query_diagonal_m self._relevant_past_data_period = relevant_past_data_period + self._sleep = sleep def get_query_rect(self, diagonal_m: float = None) -> LatLngRect: if not diagonal_m or diagonal_m < self._min_query_diagonal_m: @@ -114,6 +118,6 @@ def start_polling( break delay = t_next - arrow.utcnow() if delay.total_seconds() > 0: - sleep( + self._sleep( delay, "RID sytem doesn't need to be polled again until this time" ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/aggregate_checks.md b/monitoring/uss_qualifier/scenarios/astm/utm/aggregate_checks.md index 388796db90..b09105dc7d 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/aggregate_checks.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/aggregate_checks.md @@ -45,7 +45,7 @@ When certain circumstances occur, USSs must notify UAS personnel or the operator If 95% of the USS's notifications regarding conflicts caused by the operator's flight do not appear in the USS's list of user notifications within `ConflictingOIMaxUserNotificationTime` (5 seconds) of completing the planning operation, this check will fail per **[astm.f3548.v21.SCD0090](../../../requirements/astm/f3548/v21.md)**. -To find the notifications considered, review the reports for "Nominal planning: conflict with higher priority" scenarios. +To find the notifications considered, review the reports for "Nominal planning: conflict with higher priority" scenarios -- this is where the raw data evaluated by this scenario is gathered. ### Notifications for observing conflicts test step @@ -53,4 +53,4 @@ To find the notifications considered, review the reports for "Nominal planning: If 95% of the USS's notifications regarding conflicts affecting the operator's flight do not appear in the USS's list of user notifications within `ConflictingOIMaxUserNotificationTime` (5 seconds) of completing the planning operation, this check will fail per **[astm.f3548.v21.SCD0095](../../../requirements/astm/f3548/v21.md)**. -To find the notifications considered, review the reports for "Nominal planning: conflict with higher priority" scenarios. +To find the notifications considered, review the reports for "Nominal planning: conflict with higher priority" scenarios -- this is where the raw data evaluated by this scenario is gathered. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.md index af527cdd8e..c9bcf3acbc 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.md @@ -98,6 +98,7 @@ direct retrieval by tested_uss from mock_uss. Teardown ## Cleanup + ### ⚠️ Successful flight deletion check This cleanup is for both - after testcase ends and after test scenario ends **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../requirements/interuss/automated_testing/flight_planning.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py index 34f6de05af..74eda03715 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py @@ -17,8 +17,7 @@ from monitoring.monitorlib.clients.mock_uss.mock_uss_scd_injection_api import ( MockUssFlightBehavior, ) -from monitoring.monitorlib.delay import sleep -from monitoring.monitorlib.temporal import Time, TimeDuringTest +from monitoring.monitorlib.temporal import Time 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 @@ -128,10 +127,6 @@ def __init__( def run(self, context: ExecutionContext): self.op_intent_ids = set() - times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } self.begin_test_scenario(context) self.record_note( @@ -140,18 +135,17 @@ def run(self, context: ExecutionContext): ) self.begin_test_case("Successfully plan flight near an existing flight") - self._plan_successfully_test_case(times) + self._plan_successfully_test_case() self.end_test_case() self.begin_test_case("Flight planning prevented due to invalid data sharing") - self._plan_unsuccessfully_test_case(times) + self._plan_unsuccessfully_test_case() self.end_test_case() self.end_test_scenario() - def _plan_successfully_test_case(self, times: dict[TimeDuringTest, Time]): - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - flight_2 = self.flight_2.resolve(times) + def _plan_successfully_test_case(self): + flight_2 = self.flight_2.resolve(self.time_context.evaluate_now()) self.begin_test_step("mock_uss plans flight 2") with OpIntentValidator( @@ -161,18 +155,19 @@ def _plan_successfully_test_case(self, times: dict[TimeDuringTest, Time]): flight_2.basic_information.area.bounding_volume.to_f3548v21(), ) as validator: flight_2_planning_time = Time(arrow.utcnow().datetime) - _, self.flight_2_id = plan_flight( + _, self.flight_2_id, as_planned = plan_flight( self, self.mock_uss_client, flight_2, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_2 = as_planned flight_2_oi_ref = validator.expect_shared(flight_2) self.op_intent_ids.add(flight_2_oi_ref.id) self.end_test_step() - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - flight_1 = self.flight_1.resolve(times) + flight_1 = self.flight_1.resolve(self.time_context.evaluate_now()) self.begin_test_step("tested_uss plans flight 1") with OpIntentValidator( @@ -182,17 +177,19 @@ def _plan_successfully_test_case(self, times: dict[TimeDuringTest, Time]): flight_1.basic_information.area.bounding_volume.to_f3548v21(), ) as validator: flight_1_planning_time = Time(arrow.utcnow().datetime) - plan_res, self.flight_1_id = plan_flight( + plan_res, self.flight_1_id, as_planned = plan_flight( self, self.tested_uss_client, flight_1, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_1 = as_planned flight_1_oi_ref = validator.expect_shared(flight_1) self.op_intent_ids.add(flight_1_oi_ref.id) self.end_test_step() self.begin_test_step("Validate that tested_uss obtained flight2 details") - sleep( + self.sleep( max_wait_time, "we have to wait the longest it may take a USS to send a notification before we can establish another USS has obtained operational intent details", ) @@ -224,9 +221,8 @@ def _plan_successfully_test_case(self, times: dict[TimeDuringTest, Time]): delete_flight(self, self.mock_uss_client, self.flight_2_id) self.end_test_step() - def _plan_unsuccessfully_test_case(self, times: dict[TimeDuringTest, Time]): - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - flight_info = self.flight_2.resolve(times) + def _plan_unsuccessfully_test_case(self): + flight_info = self.flight_2.resolve(self.time_context.evaluate_now()) modify_field1 = "state" modify_field2 = "priority" @@ -250,12 +246,14 @@ def _plan_unsuccessfully_test_case(self, times: dict[TimeDuringTest, Time]): flight_info.basic_information.area.bounding_volume.to_f3548v21(), ) as validator: flight_2_planning_time = Time(arrow.utcnow().datetime) - _, self.flight_2_id = plan_flight( + _, self.flight_2_id, as_planned = plan_flight( self, self.mock_uss_client, flight_info, additional_fields, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_info = as_planned flight_2_oi_ref = validator.expect_shared_with_invalid_data( flight_info, validation_failure_type=OpIntentValidationFailureType.DataFormat, @@ -264,8 +262,7 @@ def _plan_unsuccessfully_test_case(self, times: dict[TimeDuringTest, Time]): self.op_intent_ids.add(flight_2_oi_ref.id) self.end_test_step() - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - flight_1 = self.flight_1.resolve(times) + flight_1 = self.flight_1.resolve(self.time_context.evaluate_now()) self.begin_test_step("tested_uss attempts to plan flight 1, expect failure") with OpIntentValidator( self, @@ -274,7 +271,7 @@ def _plan_unsuccessfully_test_case(self, times: dict[TimeDuringTest, Time]): flight_1.basic_information.area.bounding_volume.to_f3548v21(), ) as validator: flight_1_planning_time = Time(arrow.utcnow().datetime) - _, self.flight_1_id = submit_flight( + _, self.flight_1_id, _ = submit_flight( self, "Plan should fail", { @@ -289,7 +286,7 @@ def _plan_unsuccessfully_test_case(self, times: dict[TimeDuringTest, Time]): self.end_test_step() self.begin_test_step("Validate that tested_uss obtained flight2 details") - sleep( + self.sleep( max_wait_time, "we have to wait the longest it may take a USS to send a notification before we can establish another USS has obtained operational intent details", ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/expected_interactions_test_steps.py b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/expected_interactions_test_steps.py index a44317b541..97df44639c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/expected_interactions_test_steps.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/expected_interactions_test_steps.py @@ -2,11 +2,10 @@ from datetime import datetime -from implicitdict import ImplicitDict, StringBasedDateTime +from implicitdict import StringBasedDateTime from uas_standards.astm.f3548.v21.api import ( EntityID, OperationID, - PutOperationalIntentDetailsParameters, ) from monitoring.monitorlib.clients.mock_uss.interactions import QueryDirection @@ -43,7 +42,7 @@ def expect_mock_uss_receives_op_intent_notification( """ # Check for 'notification found' will be done periodically by waiting for a duration till max_wait_time - found, query = wait_in_intervals(get_mock_uss_interactions)( + found, query = wait_in_intervals(get_mock_uss_interactions, scenario)( scenario, mock_uss, st, @@ -91,26 +90,20 @@ def expect_no_interuss_post_interactions( with scenario.check( "Mock USS interaction can be parsed", [mock_uss.participant_id] ) as check: - try: - req = PutOperationalIntentDetailsParameters( - ImplicitDict.parse( - interaction.query.request.json, - PutOperationalIntentDetailsParameters, - ) - ) - except (ValueError, TypeError, KeyError) as e: + req = interaction.query.request.json + if not req or "operational_intent_id" not in req: check.record_failed( - summary="Failed to parse request of a 'NotifyOperationalIntentDetailsChanged' interaction with mock_uss as a PutOperationalIntentDetailsParameters", - details=f"{str(e)}\nRequest: {interaction.query.request.json}\n\nStack trace:\n{e.stacktrace}", + summary="Failed to find an operational intent ID within a 'NotifyOperationalIntentDetailsChanged' interaction with mock_uss", + details=f"Request: {interaction.query.request.json}", query_timestamps=[query.request.timestamp], ) continue # low priority failure: continue checking interactions if one cannot be parsed - op_intent_id = EntityID(req.operational_intent_id) + op_intent_id = EntityID(req.get("operational_intent_id")) if op_intent_id not in shared_op_intent_ids: no_notification_check.record_failed( - summary=f"Observed unexpected notification for operational intent ID {req.operational_intent_id}.", - details=f"Notification for operational intent ID {req.operational_intent_id} triggered by subscriptions {', '.join([sub.subscription_id for sub in req.subscriptions])} with timestamp {interaction.query.request.timestamp}.", + summary=f"Observed unexpected notification for operational intent ID {op_intent_id}.", + details=f"Notification for operational intent ID {op_intent_id} triggered by subscriptions {req.get('subscriptions', None)}.", query_timestamps=[query.request.timestamp], ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/validate_no_notification_operational_intent.md b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/validate_no_notification_operational_intent.md index 9cb4fc0fd6..08d07c4ccb 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/validate_no_notification_operational_intent.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/validate_no_notification_operational_intent.md @@ -4,7 +4,7 @@ This step verifies when a flight is not created, it is also not notified by chec ## [Get Mock USS interactions logs](../../../../interuss/mock_uss/get_mock_uss_interactions.md) -## 🛑 Mock USS interaction can be parsed check +## ℹ️ Mock USS interaction can be parsed check **[interuss.mock_uss.hosted_instance.ExposeInterface](../../../../../requirements/interuss/mock_uss/hosted_instance.md)**. ## 🛑 Expect Notification not sent check diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/wait.py b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/wait.py index 5a71a966cb..66c8650d06 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/wait.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/data_exchange_validation/test_steps/wait.py @@ -4,7 +4,7 @@ import arrow from monitoring.monitorlib.clients.mock_uss.interactions import Interaction, Query -from monitoring.monitorlib.delay import sleep +from monitoring.uss_qualifier.scenarios.scenario import TestScenario MaxTimeToWaitForSubscriptionNotificationSeconds = 7 """ @@ -18,29 +18,32 @@ """Time interval to wait between two calls to get interactions from Mock USS""" -def wait_in_intervals(func) -> Callable[..., tuple[list[Interaction], Query]]: +def wait_in_intervals( + func, scenario: TestScenario +) -> Callable[..., tuple[list[Interaction], Query]]: """ This wrapper calls the given function in intervals till desired interactions (of notifications) are returned, or till the max wait time is reached. Args: func: Given function func must also return Tuple[List[Interaction], Query]. - + scenario: Test scenario providing capability to delay. """ def wrapper(*args, **kwargs) -> tuple[list[Interaction], Query]: wait_until = arrow.utcnow().datetime + timedelta( seconds=MaxTimeToWaitForSubscriptionNotificationSeconds ) + interactions, query = func(*args, **kwargs) while arrow.utcnow().datetime < wait_until: - interactions, query = func(*args, **kwargs) if interactions: break dt = (wait_until - arrow.utcnow().datetime).total_seconds() if dt > 0: - sleep( + scenario.sleep( min(dt, WaitIntervalSeconds), "the expected notification was not found yet", ) + interactions, query = func(*args, **kwargs) return interactions, query return wrapper diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py index bf618b0100..8541fb481c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py @@ -20,3 +20,6 @@ ) from .subscription_simple import SubscriptionSimple as SubscriptionSimple from .subscription_validation import SubscriptionValidation as SubscriptionValidation +from .uss_availability_mutation import ( + UssAvailabilityMutation as UssAvailabilityMutation, +) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md index 76e24fe375..fe5da2af15 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md @@ -550,14 +550,14 @@ Check response format of a search. ## Cleanup -### [Clean any existing OIRs with known test IDs](../clean_workspace_op_intents.md) +### [Clean any existing OIRs with known test IDs](../clean_workspace_op_intents_during_cleanup.md) -### [Clean any existing subscriptions with known test IDs](../clean_workspace_subs.md) +### [Clean any existing subscriptions with known test IDs](../clean_workspace_subs_during_cleanup.md) -### [Clean any existing constraint references with known test IDs](../clean_workspace_constraints.md) +### [Clean any existing constraint references with known test IDs](../clean_workspace_constraints_during_cleanup.md) -### [Availability can be requested](../fragments/availability/read.md) +### [Availability can be requested](../fragments/availability/read_during_cleanup.md) -### [Availability can be set](../fragments/availability/update.md) +### [Availability can be set](../fragments/availability/update_during_cleanup.md) The cleanup phase of this test scenario removes the subscription with the known test ID if it has not been removed before. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py index 5f0f37e7e9..52df92d2a2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py @@ -3,8 +3,9 @@ from monitoring.monitorlib.auth import InvalidTokenSignatureAuth from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.geotemporal import Volume4D -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import ( + utm_client_session_factory, +) from monitoring.monitorlib.inspection import fullname from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( @@ -167,21 +168,21 @@ def __init__( self._pid = [self._scd_dss.participant_id] self._test_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._planning_area = planning_area.specification + self._planning_area = planning_area # Build a ready-to-use 4D volume with no specified time for searching # the currently active subscriptions - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) # Session that won't provide a token at all - self._no_auth_session = UTMClientSession( + self._no_auth_session = utm_client_session_factory.get_session( self._scd_dss.base_url, auth_adapter=None ) # Session that should provide a well-formed token with a wrong signature - self._invalid_token_session = UTMClientSession( + self._invalid_token_session = utm_client_session_factory.get_session( self._scd_dss.base_url, auth_adapter=InvalidTokenSignatureAuth() ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/cr_api_validator.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/cr_api_validator.py index 6876dccc65..9ecef2b1f1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/cr_api_validator.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/cr_api_validator.py @@ -15,8 +15,8 @@ from monitoring.monitorlib.fetch import QueryError, QueryType from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.generic import ( GenericAuthValidator, ) @@ -32,7 +32,7 @@ def __init__( generic_validator: GenericAuthValidator, dss: DSSInstance, test_id: str, - planning_area: PlanningAreaSpecification, + planning_area: PlanningAreaResource, planning_area_volume4d: Volume4D, no_auth_session: UTMClientSession, invalid_token_session: UTMClientSession, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py index 228619f562..7a39afa1f2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py @@ -2,7 +2,9 @@ from monitoring.monitorlib import fetch, schema_validation from monitoring.monitorlib.auth import InvalidTokenSignatureAuth -from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.infrastructure import ( + utm_client_session_factory, +) from monitoring.monitorlib.schema_validation import F3548_21 from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance from monitoring.uss_qualifier.scenarios.scenario import TestScenario @@ -22,10 +24,12 @@ def __init__( self._pid = dss.participant_id self._scenario = scenario self._authenticated_session = dss.client - self._invalid_token_session = UTMClientSession( + self._invalid_token_session = utm_client_session_factory.get_session( dss.base_url, auth_adapter=InvalidTokenSignatureAuth() ) - self._no_auth_session = UTMClientSession(dss.base_url, auth_adapter=None) + self._no_auth_session = utm_client_session_factory.get_session( + dss.base_url, auth_adapter=None + ) self._valid_scope = valid_scope def query_no_auth(self, **query_kwargs) -> fetch.Query: diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/oir_api_validator.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/oir_api_validator.py index 74e35260cc..d5d6883818 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/oir_api_validator.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/oir_api_validator.py @@ -15,8 +15,8 @@ from monitoring.monitorlib.fetch import QueryError, QueryType from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.generic import ( GenericAuthValidator, ) @@ -32,7 +32,7 @@ def __init__( generic_validator: GenericAuthValidator, dss: DSSInstance, test_id: str, - planning_area: PlanningAreaSpecification, + planning_area: PlanningAreaResource, planning_area_volume4d: Volume4D, no_auth_session: UTMClientSession, invalid_token_session: UTMClientSession, @@ -67,7 +67,7 @@ def __init__( self._oir_params = planning_area.get_new_operational_intent_ref_params( key=[], # we expect the area to have been emptied state=OperationalIntentState.Accepted, - uss_base_url=planning_area.get_base_url(), + uss_base_url=planning_area.specification.get_base_url(), time_start=time_start, time_end=time_end, subscription_id=None, # We provide no subscription for this intent diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py index 87543818af..daa46e5544 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py @@ -12,8 +12,8 @@ from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.mutate import scd as mutate from monitoring.monitorlib.mutate.scd import MutatedSubscription +from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.generic import ( GenericAuthValidator, ) @@ -29,7 +29,7 @@ def __init__( generic_validator: GenericAuthValidator, dss: DSSInstance, test_id: str, - planning_area: PlanningAreaSpecification, + planning_area: PlanningAreaResource, planning_area_volume4d: Volume4D, no_auth_session: UTMClientSession, invalid_token_session: UTMClientSession, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_constraints_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_constraints_during_cleanup.md new file mode 100644 index 0000000000..9443c3eba8 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_constraints_during_cleanup.md @@ -0,0 +1,18 @@ +# Ensure clean workspace test step fragment + +Ensure a clean workspace for testing interactions with a DSS by removing any constraint references from the DSS that may have been left behind from testing efforts. + +## ⚠️ Constraint references can be queried by ID check + +If an existing constraint reference cannot directly be queried by its ID, or if for a non-existing one the DSS replies with a status code different than 404, +the DSS implementation is in violation of **[astm.f3548.v21.DSS0005,3](../../../../requirements/astm/f3548/v21.md)**. + +## ⚠️ Constraint references can be searched for check + +A client with valid credentials should be allowed to search for constraint references in a given area. +Otherwise, the DSS is not in compliance with **[astm.f3548.v21.DSS0005,4](../../../../requirements/astm/f3548/v21.md)**. + +## ⚠️ Constraint reference removed check + +If an existing constraint cannot be deleted by its manager when providing the proper ID and OVN, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005,3](../../../../requirements/astm/f3548/v21.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_op_intents_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_op_intents_during_cleanup.md new file mode 100644 index 0000000000..e7adb334e0 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_op_intents_during_cleanup.md @@ -0,0 +1,18 @@ +# Ensure clean workspace test step fragment + +Ensure a clean workspace for testing interactions with a DSS by removing any operational intent references from the DSS that may have been left behind from testing efforts. + +## ⚠️ Operational intent references can be queried by ID check + +If an existing operational intent reference cannot directly be queried by its ID, or if for a non-existing one the DSS replies with a status code different than 404, +the DSS implementation is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**. + +## ⚠️ Operational intent references can be searched for check + +A client with valid credentials should be allowed to search for operational intents in a given area. +Otherwise, the DSS is not in compliance with **[astm.f3548.v21.DSS0005,2](../../../../requirements/astm/f3548/v21.md)**. + +## ⚠️ Operational intent reference removed check + +If an existing operational intent cannot be deleted when providing the proper ID and OVN, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_subs_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_subs_during_cleanup.md new file mode 100644 index 0000000000..45d300500b --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/clean_workspace_subs_during_cleanup.md @@ -0,0 +1,15 @@ +# Ensure clean workspace test step fragment + +Ensure a clean workspace for testing interactions with a DSS by removing any subscriptions from the DSS that may have been left behind from testing efforts. + +## ⚠️ Successful subscription search query check + +**[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)** requires the implementation of the DSS endpoint to allow callers to retrieve the subscriptions they created. + +## ⚠️ Subscription can be queried by ID check + +If the DSS cannot be queried for the existing test ID, the DSS is likely not implementing **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)** correctly. + +## ⚠️ Subscription can be deleted check + +**[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)** requires the implementation of the DSS endpoint to allow callers to delete subscriptions they created. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.md index 704a23091e..613405c845 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.md @@ -76,4 +76,4 @@ This step verifies that an existing CR cannot be mutated with an incorrect OVN. If the DSS under test allows the qualifier to mutate an existing CR with a request that provided an incorrect OVN, it is in violation of **[astm.f3548.v21.DSS0005,3](../../../../requirements/astm/f3548/v21.md)** -## [Cleanup](./clean_workspace_constraints.md) +## [Cleanup](./clean_workspace_constraints_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.py index c302dec7fa..1def2a27f0 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/constraint_ref_simple.py @@ -4,7 +4,6 @@ from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.geotemporal import Volume4D from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( @@ -13,7 +12,6 @@ ) from monitoring.uss_qualifier.resources.communications import ClientIdentityResource from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments from monitoring.uss_qualifier.scenarios.scenario import TestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext @@ -29,14 +27,15 @@ class CRSimple(TestScenario): """ CR_TYPE = register_resource_type(397, "Constraint Reference") + CR_DURATION = timedelta(minutes=20) _dss: DSSInstance _cr_id: EntityID + _cr_start_time: datetime _expected_manager: str - _planning_area: PlanningAreaSpecification - _planning_area_volume4d: Volume4D + _planning_area: PlanningAreaResource def __init__( self, @@ -60,17 +59,17 @@ def __init__( self._pid = [self._dss.participant_id] self._cr_id = id_generator.id_factory.make_id(self.CR_TYPE) + self._cr_start_time = datetime.now() self._expected_manager = client_identity.subject() - self._planning_area = planning_area.specification - - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, - ) + self._planning_area = planning_area def run(self, context: ExecutionContext): self.begin_test_scenario(context) + + self._cr_start_time = datetime.now() - timedelta(seconds=10) + self._setup_case() self.begin_test_case("Deletion requires correct OVN") @@ -87,8 +86,8 @@ def run(self, context: ExecutionContext): def _step_create_cr(self): cr_params = self._planning_area.get_new_constraint_ref_params( - time_start=datetime.now() - timedelta(seconds=10), - time_end=datetime.now() + timedelta(minutes=20), + time_start=self._cr_start_time, + time_end=self._cr_start_time + self.CR_DURATION, ) self.begin_test_step("Create a constraint reference") @@ -100,7 +99,7 @@ def _step_create_cr(self): new_cr, subs, query = self._dss.put_constraint_ref( cr_id=self._cr_id, extents=cr_params.extents, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), ) self.record_query(query) except QueryError as qe: @@ -124,19 +123,17 @@ def _step_attempt_delete_missing_ovn(self): # We don't expect the reach this point: check.record_failed( summary="CR Deletion with empty OVN was not expected to succeed", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {q.status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 404, 409]: - # An empty OVN can be seen as: - # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) - pass - else: + # An empty OVN can be seen as: + # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) + if qe.cause_status_code not in [400, 404, 409]: check.record_failed( summary="CR Deletion with empty OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) @@ -156,18 +153,16 @@ def _step_attempt_delete_incorrect_ovn(self): # We don't expect the reach this point: check.record_failed( summary="CR Deletion with incorrect OVN was not expected to succeed", - details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {q.status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code == 409: - # The spec explicitly requests a 409 response code for incorrect OVNs. - pass - else: + # The spec explicitly requests a 409 response code for incorrect OVNs. + if qe.cause_status_code != 409: check.record_failed( summary="CR Deletion with incorrect OVN failed for unexpected reason", - details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) @@ -187,26 +182,24 @@ def _step_attempt_mutation_missing_ovn(self): _, _, q = self._dss.put_constraint_ref( cr_id=self._cr_id, extents=cr_params.extents, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), ovn="", ) self.record_query(q) # We don't expect the reach this point: check.record_failed( summary="CR mutation with empty OVN was not expected to succeed", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {q.status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 404, 409]: - # An empty OVN can be seen as: - # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) - pass - else: + # An empty OVN can be seen as: + # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) + if qe.cause_status_code not in [400, 404, 409]: check.record_failed( summary="CR mutation with empty OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) @@ -226,26 +219,23 @@ def _step_attempt_mutation_incorrect_ovn(self): _, _, q = self._dss.put_constraint_ref( cr_id=self._cr_id, extents=cr_params.extents, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), ovn="ThisIsAnIncorrectOVN", ) self.record_query(q) # We don't expect the reach this point: check.record_failed( summary="CR mutation with incorrect OVN was not expected to succeed", - details=f"Was expecting an HTTP 400 or 409 response because of an incorrect OVN, but got {q.status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 409]: - # An empty OVN cen be seen as both an incorrect parameter as well as a conflict - # because the value is incorrect: we accept both a 400 and 409 return code here. - pass - else: + # The spec explicitly requests a 409 response code for incorrect OVNs. + if qe.cause_status_code != 409: check.record_failed( summary="CR mutation with incorrect OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400 or 409 response because of an incorrect OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) self.end_test_step() @@ -265,7 +255,10 @@ def _ensure_clean_workspace_step(self): test_step_fragments.cleanup_active_constraint_refs( self, self._dss, - self._planning_area_volume4d.to_f3548v21(), + self._planning_area.resolved_volume4d_with_times( + self._cr_start_time, + self._cr_start_time + self.CR_DURATION, + ).to_f3548v21(), self._expected_manager, ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/datastore_access.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/datastore_access.md index d6a3bedcc1..3de5b9c75f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/datastore_access.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/datastore_access.md @@ -3,7 +3,9 @@ ## Overview Attempt to directly access the datastore (CockroachDB or Yugabyte) nodes intercommunicating to form the DSS Airspace Representation for the DSS instances under test, for the purpose of determining compliance to certain DSS interoperability requirements. -The psycopg library is used to attempt standard PostgreSQL connections to the nodes as it is the most straightforward way of connecting directly to the datastore nodes while controlling the connection parameters (such as for encryption). + +For CockroachDB, the psycopg library is used to attempt standard PostgreSQL connections to the nodes as it is the most straightforward way of connecting directly to the datastore nodes while controlling the connection parameters (such as for encryption). +For YugabyteDB, RPC messages are sent to the nodes directly. This scenario aims at validating the following requirements: - **[astm.f3548.v21.DSS0200](../../../../requirements/astm/f3548/v21.md)** @@ -15,12 +17,10 @@ DatastoreDBClusterResource that provides access to a set of CockroachDB or Yugab ## Setup test case ### Validate nodes are reachable test step -Attempt connection with nodes of the cluster to validate that they are reachable. -To do so, this step attempts a connection to each node without strictly requiring an encrypted connection and forcing the use of a password authentication with a dummy password. -As such we expect the node to respond with a failed password authentication. +Attempt connection with nodes of the cluster to validate that they are reachable, by opening a TCP connection to the node. #### 🛑 Node is reachable check -This check succeeds if the node can be reached and that the password authentication attempted by the USS qualifier is rejected. +This check succeeds if the node is reachable. It fails in all other cases. ## Verify security interoperability test case @@ -28,12 +28,13 @@ It fails in all other cases. Attempt connection with nodes of the cluster in insecure mode. It is expected that the connection attempts are rejected due to the fact that all nodes are running in secure mode. -#### ⚠️ Node runs in secure mode check +#### ⚠️ Node enforces encryption of its communications check This check succeeds if the node rejects the insecure connection attempt by the USS qualifier because it is in secure mode. -It fails in all other cases. +If it is in insecure mode, it means that the node does not encrypt its communications as it should per **[astm.f3548.v21.DSS0205](../../../../requirements/astm/f3548/v21.md)**. +#### ⚠️ Node enforces authentication of its communications check +This check succeeds if the node rejects the insecure connection attempt by the USS qualifier because it is in secure mode. If it is in insecure mode, it means that the node does not authenticate incoming connection attempt as it should per **[astm.f3548.v21.DSS0200](../../../../requirements/astm/f3548/v21.md)**. -If it is in insecure mode, it means that the node does not encrypt its communications as it should per **[astm.f3548.v21.DSS0205](../../../../requirements/astm/f3548/v21.md)**. ### Attempt to connect with legacy encryption protocol test step Attempt connection with nodes of the cluster forcing the use of legacy encryption protocols, namely between TLSv1 and TLSv1.1. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.md index 368ef7c85d..af9c6e39be 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.md @@ -29,12 +29,12 @@ This resource is optional. ### Test environment requirements test step -#### 🛑 DSS instance is publicly addressable check +#### ⚠️ DSS instance is publicly addressable check As per **[astm.f3548.v21.DSS0300](../../../../requirements/astm/f3548/v21.md)** the DSS instance should be publicly addressable. As such, this check will fail if the resolved IP of the DSS host is a private IP address. This check is skipped if the test exclusion `allow_private_addresses` is set to `True`. -#### 🛑 DSS instance is reachable check +#### ⚠️ DSS instance is reachable check As per **[astm.f3548.v21.DSS0300](../../../../requirements/astm/f3548/v21.md)** the DSS instance should be publicly addressable. As such, this check will fail if the DSS is not reachable with a dummy query. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.py index d8848e734b..70b2d3c6ce 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/dss_interoperability.py @@ -44,7 +44,9 @@ def __init__( if not dss.is_same_as(primary_dss_instance) ] - self._valid_search_area = Volume4D(volume=planning_area.specification.volume) + self._valid_search_area = planning_area.resolved_volume4d_with_times( + None, None + ).to_f3548v21() if test_exclusions is not None: self._allow_private_addresses = test_exclusions.allow_private_addresses diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read.md index 6cea883d08..96d349ead0 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read.md @@ -1,6 +1,6 @@ # USS Availability Read test step fragment -This fragment contains the steps for the USS Availability synchronization scenario +This fragment contains the steps for the USS Availability scenario where we confirm that a USS availability can be correctly read from a DSS instance ## 🛑 USS Availability can be requested check diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read_during_cleanup.md new file mode 100644 index 0000000000..e29ce1b065 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/read_during_cleanup.md @@ -0,0 +1,9 @@ +# USS Availability Read test step fragment + +This fragment contains the steps for the USS Availability scenario +where we confirm that a USS availability can be correctly read from a DSS instance + +## ⚠️ USS Availability can be requested check + +If, when queried for the availability of a USS using valid credentials, the DSS does not return a valid 200 response, +it is in violation of the OpenAPI specification referenced by **[astm.f3548.v21.DSS0100,1](../../../../../../requirements/astm/f3548/v21.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update.md index 84892e8db3..f10fac4c77 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update.md @@ -1,6 +1,6 @@ -# USS Availability Read test step fragment +# USS Availability Update test step fragment -This fragment contains the steps for the USS Availability synchronization scenario +This fragment contains the steps for the USS Availability scenario where we confirm that a USS availability can be correctly read from a DSS instance ## 🛑 USS Availability can be updated check diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update_during_cleanup.md new file mode 100644 index 0000000000..9188fba66e --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/fragments/availability/update_during_cleanup.md @@ -0,0 +1,9 @@ +# USS Availability Update test step fragment + +This fragment contains the steps for the USS Availability scenario +where we confirm that a USS availability can be correctly read from a DSS instance + +## ⚠️ USS Availability can be updated check + +If, when presented with a valid query to update the availability state of a USS, a DSS +responds with anything else than a 200 OK response, it is in violation of the OpenAPI specification referenced by **[astm.f3548.v21.DSS0100,1](../../../../../../requirements/astm/f3548/v21.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.md index 514e0977b5..afbe2157d9 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.md @@ -137,6 +137,6 @@ Checks that an OIR in the ACCEPTED state that is attached to an explicit subscri ## Cleanup -### [Cleanup OIRs test step](./clean_workspace_op_intents.md) +### [Cleanup OIRs test step](./clean_workspace_op_intents_during_cleanup.md) -### [Cleanup Subscriptions test step](./clean_workspace_subs.md) +### [Cleanup Subscriptions test step](./clean_workspace_subs_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py index 2c8d95ea18..328c427efa 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_explicit_sub_handling.py @@ -21,7 +21,6 @@ ) from monitoring.uss_qualifier.resources.communications import ClientIdentityResource from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.oir import ( crud as oir_fragments, @@ -49,7 +48,7 @@ class OIRExplicitSubHandling(TestScenario): # Keep track of the current OIR state _current_oir: OperationalIntentReference | None _expected_manager: str - _planning_area: PlanningAreaSpecification + _planning_area: PlanningAreaResource _planning_area_volume4d: Volume4D # Keep track of the current subscription @@ -87,10 +86,10 @@ def __init__( self._expected_manager = client_identity.subject() - self._planning_area = planning_area.specification + self._planning_area = planning_area - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) def run(self, context: ExecutionContext): @@ -129,7 +128,7 @@ def run(self, context: ExecutionContext): oir_params=self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=datetime.now() + timedelta(minutes=20), subscription_id=None, @@ -161,7 +160,7 @@ def _step_remove_subscription_from_oir(self): oir_update_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=self._current_oir.time_start.value.datetime, time_end=self._current_oir.time_end.value.datetime, subscription_id=None, @@ -214,7 +213,7 @@ def _step_create_oir_insufficient_subscription(self): oir_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=self._sub_params.end_time + timedelta(seconds=1), # OIR ends 1 sec after subscription @@ -262,7 +261,7 @@ def _step_create_oir_sufficient_subscription(self): oir_params=self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=self._sub_params.end_time - timedelta(seconds=60), # OIR ends at the same time as subscription @@ -297,7 +296,7 @@ def _steps_update_oir_with_insufficient_explicit_sub(self, is_replacement: bool) oir_update_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=self._current_oir.time_start.value.datetime, time_end=self._current_oir.time_end.value.datetime, subscription_id=self._extra_sub_id, @@ -362,7 +361,7 @@ def _step_update_oir_with_sufficient_explicit_sub(self, is_replacement: bool): oir_params=self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=self._current_extra_sub.time_start.value.datetime, time_end=self._current_extra_sub.time_end.value.datetime, subscription_id=self._extra_sub_id, @@ -445,7 +444,7 @@ def _default_oir_params( return self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=datetime.now() + timedelta(minutes=20), subscription_id=subscription_id, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md index 20fa327dbe..1efb092482 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.md @@ -360,7 +360,7 @@ it is in violation of **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm ## Cleanup -### [Remove OIRs created during this test](clean_workspace_op_intents.md) +### [Remove OIRs created during this test](clean_workspace_op_intents_during_cleanup.md) -### [Remove subscriptions created during this test](clean_workspace_subs.md) +### [Remove subscriptions created during this test](clean_workspace_subs_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py index 9175182a0c..2792db62df 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/oir_implicit_sub_handling.py @@ -13,7 +13,6 @@ from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import Query, QueryError -from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.testing import make_fake_url from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource @@ -53,6 +52,8 @@ class OIRImplicitSubHandling(TestScenario): A scenario that tests that a DSS properly handles the creation and mutation of implicit subscriptions """ + _planning_area: PlanningAreaResource + # Identifiers for the test OIRs _oir_a_id: str _oir_b_id: str @@ -97,7 +98,7 @@ def __init__( } self._dss = dss.get_instance(scopes) self._pid = [self._dss.participant_id] - self._planning_area = planning_area.specification + self._planning_area = planning_area self._oir_a_id = id_generator.id_factory.make_id(OIR_A_TYPE) self._oir_b_id = id_generator.id_factory.make_id(OIR_B_TYPE) @@ -284,7 +285,7 @@ def _case_2_step_mutate_oir_with_implicit_sub_specify_implicit_params(self): try: oir, subs, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_0, self._time_1 ).to_f3548v21() ], @@ -418,7 +419,7 @@ def _create_oir( try: oir, subs, oir_q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( time_start, time_end ).to_f3548v21() ], @@ -587,7 +588,7 @@ def _case_3_update_oir_with_explicit_sub(self): try: oir, subs, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_0, self._time_1 ).to_f3548v21() ], @@ -632,7 +633,7 @@ def _case_3_update_oir_with_no_sub(self): try: oir, subs, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_2, self._time_3 ).to_f3548v21() ], @@ -684,7 +685,7 @@ def _case_4_expand_oir_same_implicit_sub(self): try: oir, subs, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_0, self._time_2 ).to_f3548v21() ], @@ -786,7 +787,7 @@ def _case_5_replace_explicit_sub_with_implicit(self): try: oir, subs, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._explicit_sub.time_start.value.datetime, self._explicit_sub.time_end.value.datetime, ).to_f3548v21() @@ -864,7 +865,7 @@ def _case_6_attach_implicit_sub_to_oir_without_subscription(self): try: oir_updated, subs, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( oir_no_sub.time_start.value.datetime, oir_no_sub.time_end.value.datetime, ).to_f3548v21() @@ -952,7 +953,7 @@ def _case_7_mutate_oir_without_subscription(self): try: oir_mutated_no_sub, _, q = self._dss.put_op_intent( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_0, self._time_1 ).to_f3548v21() ], @@ -1019,7 +1020,7 @@ def _case_8_mutate_oir_explicit_request_implicit(self): ovn=self._oir_a_ovn, oir_params=PutOperationalIntentReferenceParameters( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_2, self._time_3 ).to_f3548v21() ], @@ -1102,7 +1103,7 @@ def _case_9_mutate_oir_nosub_request_implicit(self): ovn=self._oir_a_ovn, oir_params=PutOperationalIntentReferenceParameters( extents=[ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( self._time_2, self._time_3 ).to_f3548v21() ], @@ -1188,7 +1189,7 @@ def _ensure_clean_workspace_step(self): self.end_test_step() def _clean_workspace(self): - extents = Volume4D(volume=self._planning_area.volume) + extents = self._planning_area.resolved_volume4d_with_times(None, None) test_step_fragments.cleanup_active_oirs( self, self._dss, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.md index 504baf0870..504fa20f44 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.md @@ -112,4 +112,4 @@ in violation of **[astm.f3548.v21.OPIN0035](../../../../requirements/astm/f3548/ If an operational intent reference can be deleted by a client which did not create it, the DSS implementation is in violation of **[astm.f3548.v21.OPIN0035](../../../../requirements/astm/f3548/v21.md)**. -## [Cleanup](clean_workspace_op_intents.md) +## [Cleanup](clean_workspace_op_intents_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.py index a8c027f3ee..8eb489ec30 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_access_control.py @@ -1,4 +1,3 @@ -import arrow from uas_standards.astm.f3548.v21 import api as f3548v21 from uas_standards.astm.f3548.v21.constants import Scope @@ -11,7 +10,6 @@ ) from monitoring.monitorlib.fetch import QueryError from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection -from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.monitorlib.testing import make_fake_url from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource @@ -108,24 +106,18 @@ def __init__( self._flight_2 = templates["flight_2"] def run(self, context: ExecutionContext): - times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } self.begin_test_scenario(context) self.begin_test_case("Setup") self.begin_test_step("Ensure clean workspace") - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - ws_is_clean = self._ensure_clean_workspace(times) + ws_is_clean = self._ensure_clean_workspace() self.end_test_step() if ws_is_clean: self.begin_test_step( "Create operational intent references with different credentials" ) - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow()) - self._create_op_intents(times) + self._create_op_intents() self._ensure_credentials_are_different() self.end_test_step() @@ -138,7 +130,7 @@ def run(self, context: ExecutionContext): "Attempt unauthorized operational intent reference modification" ) - self._check_mutation_on_non_owned_intent_fails(times) + self._check_mutation_on_non_owned_intent_fails() self.end_test_step() self.end_test_case() @@ -147,12 +139,17 @@ def run(self, context: ExecutionContext): "clean_workspace", "Could not clean up workspace, skipping scenario", ) + self.end_test_case() self.end_test_scenario() - def _get_extents(self, times: dict[TimeDuringTest, Time]) -> Volume4D: + def _get_extents(self) -> Volume4D: extents = Volume4DCollection() - for info in (self._flight_1.resolve(times), self._flight_2.resolve(times)): + self.time_context.evaluate_now() + for info in ( + self._flight_1.resolve(self.time_context), + self._flight_2.resolve(self.time_context), + ): extents.extend(info.basic_information.area) return extents.bounding_volume @@ -229,12 +226,9 @@ def _clean_known_op_intents_ids(self): query_timestamps=[dq.request.timestamp], ) - def _attempt_to_delete_remaining_op_intents( - self, times: dict[TimeDuringTest, Time] - ): + def _attempt_to_delete_remaining_op_intents(self, extent: Volume4D): """Search for op intents and attempt to delete them using the main credentials""" - extent = self._get_extents(times) with self.check( "Operational intent references can be searched for", self._pid, @@ -315,14 +309,14 @@ def _attempt_to_delete_remaining_op_intents( query_timestamps=[dq.request.timestamp], ) - def _ensure_clean_workspace(self, times: dict[TimeDuringTest, Time]) -> bool: + def _ensure_clean_workspace(self) -> bool: """ Tries to provide a clean workspace. If it fails to do so and the underlying check has a severity below HIGH, this function will return false. It will only return true if the workspace is clean. """ - extent = self._get_extents(times) + extent = self._get_extents() # Record the subscription to help with troubleshooting in case of failures to clean-up self.record_note("main_credentials", self._dss.client.auth_adapter.get_sub()) @@ -333,7 +327,7 @@ def _ensure_clean_workspace(self, times: dict[TimeDuringTest, Time]) -> bool: # Delete what we know about self._clean_known_op_intents_ids() # Search and attempt deleting what may be found through search - self._attempt_to_delete_remaining_op_intents(times) + self._attempt_to_delete_remaining_op_intents(extent) with self.check( "Operational intent references can be searched for", @@ -365,8 +359,8 @@ def _ensure_clean_workspace(self, times: dict[TimeDuringTest, Time]) -> bool: return True - def _create_op_intents(self, times: dict[TimeDuringTest, Time]): - flight_1 = self._flight_1.resolve(times) + def _create_op_intents(self): + flight_1 = self._flight_1.resolve(self.time_context.evaluate_now()) with self.check( "Can create an operational intent with valid credentials", self._pid ) as check: @@ -388,7 +382,7 @@ def _create_op_intents(self, times: dict[TimeDuringTest, Time]): query_timestamps=[q1.request.timestamp], ) - flight_2 = self._flight_2.resolve(times) + flight_2 = self._flight_2.resolve(self.time_context.evaluate_now()) with self.check( "Can create an operational intent with valid credentials", self._pid ) as check: @@ -431,10 +425,8 @@ def _ensure_credentials_are_different(self): f" resources ({self._dss.client.auth_adapter.get_sub()}),", ) - def _check_mutation_on_non_owned_intent_fails( - self, times: dict[TimeDuringTest, Time] - ): - flight_1 = self._flight_1.resolve(times) + def _check_mutation_on_non_owned_intent_fails(self): + flight_1 = self._flight_1.resolve(self.time_context.evaluate_now()) with self.check( "Non-owning credentials cannot modify operational intent", self._pid + self._uids, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md index 3d561b6ced..135667b05f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.md @@ -141,4 +141,4 @@ The expectation is that the DSS will require the missing OVN. #### [Non de-conflicted mutation request fails](fragments/oir/crud/update_conflict.md) -## [Cleanup](./clean_workspace_op_intents.md) +## [Cleanup](./clean_workspace_op_intents_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py index 364fb36514..79ba67316e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_key_validation.py @@ -10,7 +10,6 @@ from monitoring.monitorlib import fetch, schema_validation from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.schema_validation import F3548_21 from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource @@ -51,6 +50,7 @@ class OIRKeyValidation(TestScenario): # Keep track of the current OIR state _current_oirs: dict[EntityID, OperationalIntentReference] + _planning_area: PlanningAreaResource def __init__( self, @@ -79,10 +79,10 @@ def __init__( self._expected_manager = client_identity.subject() - self._planning_area = planning_area.specification + self._planning_area = planning_area - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) self._current_oirs = {} @@ -109,7 +109,7 @@ def _steps_create_non_overlapping_oirs(self): first_oir_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=datetime.now() + timedelta(minutes=20), subscription_id=None, @@ -119,7 +119,7 @@ def _steps_create_non_overlapping_oirs(self): second_oir_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() + timedelta(hours=1, minutes=20), time_end=datetime.now() + timedelta(hours=1, minutes=40), subscription_id=None, @@ -189,13 +189,13 @@ def _attempt_creation_expect_conflict( self.record_query(q) check.record_failed( summary="Operational intent reference with OVN missing in key was created", - details=f"Was expecting an HTTP 409 response because of a conflict with OIR {conflicting_ids}, but got a successful response ({q.status_code}) instead", + details=f"Was expecting an HTTP 409 response because of a conflict with OIR {conflicting_ids}, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) return except QueryError as qe: self.record_queries(qe.queries) - _expect_conflict_code(check, conflicting_ids, qe.cause) + _expect_conflict_code(check, conflicting_ids, qe) conflicting_query = qe.cause self._validate_conflict_response(conflicting_ids, conflicting_query) @@ -222,13 +222,13 @@ def _attempt_update_expect_conflict( self.record_query(q) check.record_failed( summary="Operational intent reference with OVN missing in key was mutated", - details=f"Was expecting an HTTP 409 response because of a conflict with OIR {conflicting_ids}, but got a successful response ({q.status_code}) instead", + details=f"Was expecting an HTTP 409 response because of a conflict with OIR {conflicting_ids}, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) return except QueryError as qe: self.record_queries(qe.queries) - _expect_conflict_code(check, conflicting_ids, qe.cause) + _expect_conflict_code(check, conflicting_ids, qe) conflicting_query = qe.cause self._validate_conflict_response(conflicting_ids, conflicting_query) @@ -243,7 +243,7 @@ def _steps_attempt_create_overlapping_oir(self): conflict_first = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=first_oir.time_start.value.datetime, time_end=first_oir.time_end.value.datetime, subscription_id=None, @@ -258,7 +258,7 @@ def _steps_attempt_create_overlapping_oir(self): conflict_second = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=second_oir.time_start.value.datetime, time_end=second_oir.time_end.value.datetime, subscription_id=None, @@ -273,7 +273,7 @@ def _steps_attempt_create_overlapping_oir(self): conflict_both = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=first_oir.time_start.value.datetime, time_end=second_oir.time_end.value.datetime, subscription_id=None, @@ -431,11 +431,11 @@ def cleanup(self): def _expect_conflict_code( - check: PendingCheck, conflicting_ids: list[EntityID], query: fetch.Query + check: PendingCheck, conflicting_ids: list[EntityID], qe: fetch.QueryError ): - if query.status_code != 409: + if qe.cause_status_code != 409: check.record_failed( summary="OIR Creation failed for the unexpected reason", - details=f"Was expecting an HTTP 409 response because of a conflict with OIR {conflicting_ids}, but got a {query.status_code} instead", - query_timestamps=[query.request.timestamp], + details=f"Was expecting an HTTP 409 response because of a conflict with OIR {conflicting_ids}, but got {qe.cause_status_code}: {qe.msg}", + query_timestamps=qe.query_timestamps, ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md index d8d033a405..7179d65fb6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.md @@ -20,7 +20,8 @@ Verifies the behavior of a DSS for simple interactions pertaining to operational ### planning_area -[`PlanningAreaResource`](../../../../resources/planning_area.py) describes the 3D volume in which operational intent references will be created. +[`PlanningAreaResource`](../../../../resources/planning_area.py) describes the 3D volume in which operational intent references will be created. Note that any start or end times specified in the underlying volume template +will be ignored. ## Setup test case @@ -105,6 +106,6 @@ Confirm that an OIR can be mutated when the correct OVN is provided. ## Cleanup -### [Cleanup OIRs test step](./clean_workspace_op_intents.md) +### [Cleanup OIRs test step](./clean_workspace_op_intents_during_cleanup.md) Remove any lingering OIRs left by this scenario. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py index 0092bc3cac..24d690b815 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_simple.py @@ -10,7 +10,6 @@ from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.geotemporal import Volume4D from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( @@ -19,7 +18,6 @@ ) from monitoring.uss_qualifier.resources.communications import ClientIdentityResource from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource -from monitoring.uss_qualifier.resources.planning_area import PlanningAreaSpecification from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments from monitoring.uss_qualifier.scenarios.astm.utm.dss.fragments.oir import ( crud as oir_fragments, @@ -50,8 +48,7 @@ class OIRSimple(TestScenario): _current_oir: OperationalIntentReference | None _current_oir_params: PutOperationalIntentReferenceParameters | None _expected_manager: str - _planning_area: PlanningAreaSpecification - _planning_area_volume4d: Volume4D + _planning_area: PlanningAreaResource def __init__( self, @@ -80,11 +77,7 @@ def __init__( self._expected_manager = client_identity.subject() - self._planning_area = planning_area.specification - - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, - ) + self._planning_area = planning_area def run(self, context: ExecutionContext): self.begin_test_scenario(context) @@ -127,19 +120,17 @@ def _step_attempt_delete_missing_ovn(self): # We don't expect to reach this point: check.record_failed( summary="OIR Deletion with empty OVN was not expected to succeed", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {q.status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 404, 409]: - # An empty OVN can be seen as: - # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) - pass - else: + # An empty OVN can be seen as: + # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) + if qe.cause_status_code not in [400, 404, 409]: check.record_failed( summary="OIR Deletion with empty OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an empty OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) @@ -159,18 +150,16 @@ def _step_attempt_delete_incorrect_ovn(self): # We don't expect to reach this point: check.record_failed( summary="OIR Deletion with incorrect OVN was not expected to succeed", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an incorrect OVN, but got {q.status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {q.status_code}", query_timestamps=[q.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 404, 409]: - # The spec explicitly requests a 409 response code for incorrect OVNs. - pass - else: + # The spec explicitly requests a 409 response code for incorrect OVNs. + if qe.cause_status_code != 409: check.record_failed( summary="OIR Deletion with incorrect OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an incorrect OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) @@ -196,19 +185,17 @@ def _step_attempt_mutation_missing_ovn(self): # We don't expect to reach this point: check.record_failed( summary="OIR Mutation with missing OVN was not expected to succeed", - details=f"Was expecting an HTTP 400, 404 or 409 response because of a missing OVN, but got {query.status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of a missing OVN, but got {query.status_code}", query_timestamps=[query.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 404, 409]: - # An empty OVN can be seen as: - # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) - pass - else: + # An empty OVN can be seen as: + # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) + if qe.cause_status_code not in [400, 404, 409]: check.record_failed( summary="OIR Mutation with missing OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400, 404 or 409 response because of a missing OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 400, 404 or 409 response because of a missing OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) self.end_test_step() @@ -233,17 +220,16 @@ def _step_attempt_mutation_incorrect_ovn(self): # We don't expect to reach this point: check.record_failed( summary="OIR Mutation with incorrect OVN was not expected to succeed", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an incorrect OVN, but got {query.status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {query.status_code}", query_timestamps=[query.request.timestamp], ) except QueryError as qe: self.record_queries(qe.queries) - if qe.cause_status_code in [400, 404, 409]: - pass - else: + # The spec explicitly requests a 409 response code for incorrect OVNs. + if qe.cause_status_code != 409: check.record_failed( summary="OIR Mutation with incorrect OVN failed for unexpected reason", - details=f"Was expecting an HTTP 400, 404 or 409 response because of an incorrect OVN, but got {qe.cause_status_code} instead", + details=f"Was expecting an HTTP 409 response because of an incorrect OVN, but got {qe.cause_status_code}: {qe.msg}", query_timestamps=qe.query_timestamps, ) @@ -320,10 +306,11 @@ def _ensure_clean_workspace(self): def _clean_all_oirs(self): # Delete any active OIR we might own + vol = self._planning_area.resolved_volume4d_with_times(None, None) test_step_fragments.cleanup_active_oirs( self, self._dss, - self._planning_area_volume4d.to_f3548v21(), + vol.to_f3548v21(), self._expected_manager, ) @@ -341,7 +328,7 @@ def _test_params_for_current_time(self): return self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=datetime.now() + timedelta(minutes=20), subscription_id=None, @@ -353,7 +340,7 @@ def _default_oir_params( return self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=datetime.now() + timedelta(minutes=20), subscription_id=subscription_id, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.md index 6096f508b7..f39c4f9258 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.md @@ -124,4 +124,4 @@ If the DSS allows a client with the `utm.strategic_coordination` scope to transi it is in violation of **[astm.f3548.v21.SCD0100](../../../../requirements/astm/f3548/v21.md)** and **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**. -## [Cleanup](clean_workspace_op_intents.md) +## [Cleanup](clean_workspace_op_intents_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.py index 1c664cdc14..5d7c98c2a8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/op_intent_ref_state_transitions.py @@ -1,4 +1,3 @@ -import arrow from uas_standards.astm.f3548.v21 import api as f3548v21 from uas_standards.astm.f3548.v21.api import OperationalIntentState from uas_standards.astm.f3548.v21.constants import Scope @@ -8,7 +7,6 @@ ) from monitoring.monitorlib.fetch import QueryError from monitoring.monitorlib.geotemporal import Volume4D -from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.monitorlib.testing import make_fake_url from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource @@ -82,16 +80,11 @@ def __init__( self._flight = templates["flight_1"] def run(self, context: ExecutionContext): - times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } self.begin_test_scenario(context) self.begin_test_case("Setup") self.begin_test_step("Ensure clean workspace") - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - ws_is_clean = self._ensure_clean_workspace(times) + ws_is_clean = self._ensure_clean_workspace() self.end_test_step() self.end_test_case() @@ -99,13 +92,13 @@ def run(self, context: ExecutionContext): self.begin_test_case("Attempt unauthorized state creation") self.begin_test_step("Attempt direct creation with unauthorized state") - self._check_unauthorized_state_creation(times) + self._check_unauthorized_state_creation() self.end_test_step() self.end_test_case() self.begin_test_case("Attempt unauthorized state transitions") - self._steps_check_unauthorized_state_transitions(times) + self._steps_check_unauthorized_state_transitions() self.end_test_case() else: @@ -116,8 +109,10 @@ def run(self, context: ExecutionContext): self.end_test_scenario() - def _get_extents(self, times: dict[TimeDuringTest, Time]) -> Volume4D: - return self._flight.resolve(times).basic_information.area.bounding_volume + def _get_extents(self) -> Volume4D: + return self._flight.resolve( + self.time_context.evaluate_now() + ).basic_information.area.bounding_volume def _clean_known_op_intents_ids(self): with self.check( @@ -153,12 +148,9 @@ def _clean_known_op_intents_ids(self): query_timestamps=e.query_timestamps, ) - def _attempt_to_delete_remaining_op_intents( - self, times: dict[TimeDuringTest, Time] - ): + def _attempt_to_delete_remaining_op_intents(self, extent: Volume4D): """Search for op intents and attempt to delete them""" - extent = self._get_extents(times) with self.check( "Operational intent references can be searched for", self._pid, @@ -196,19 +188,19 @@ def _attempt_to_delete_remaining_op_intents( query_timestamps=e.query_timestamps, ) - def _ensure_clean_workspace(self, times: dict[TimeDuringTest, Time]) -> bool: + def _ensure_clean_workspace(self) -> bool: """ Tries to provide a clean workspace. If it fails to do so and the underlying check has a severity below HIGH, this function will return false. It will only return true if the workspace is clean. """ - extent = self._get_extents(times) + extent = self._get_extents() # Delete what we know about self._clean_known_op_intents_ids() # Search and attempt deleting what may be found through search - self._attempt_to_delete_remaining_op_intents(times) + self._attempt_to_delete_remaining_op_intents(extent) with self.check( "Operational intent references can be searched for", @@ -240,10 +232,9 @@ def _ensure_clean_workspace(self, times: dict[TimeDuringTest, Time]) -> bool: return True - def _check_unauthorized_state_creation(self, times: dict[TimeDuringTest, Time]): - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) + def _check_unauthorized_state_creation(self): # Reuse info from flight 1 for the third Operational Intent Ref - flight_3 = self._flight.resolve(times) + flight_3 = self._flight.resolve(self.time_context.evaluate_now()) with self.check( "Direct Nonconforming state creation is forbidden", self._pid + self._uids ) as check: @@ -290,7 +281,7 @@ def _check_unauthorized_state_creation(self, times: dict[TimeDuringTest, Time]): # If we reach this point, we should fail: check.record_failed( "Could create operational intent using main credentials", - details=f"DSS responded with {q.response.status_code} to attempt to create OI {self._oid_1}", + details=f"DSS responded with {q.response.status_code} to attempt to create OI {self._oid}", query_timestamps=[q.request.timestamp], ) except QueryError as e: @@ -302,18 +293,15 @@ def _check_unauthorized_state_creation(self, times: dict[TimeDuringTest, Time]): query_timestamps=e.query_timestamps, ) - def _steps_check_unauthorized_state_transitions( - self, times: dict[TimeDuringTest, Time] - ): + def _steps_check_unauthorized_state_transitions(self): """This checks for UNAUTHORIZED state transitions, that is, transitions that require the correct scope, but are not otherwise disallowed by the standard.""" self.begin_test_step("Create an Accepted OIR") - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) # Reuse info from flight 1 for the third Operational Intent Ref flight_extents = self._flight.resolve( - times + self.time_context.evaluate_now() ).basic_information.area.to_f3548v21() with self.check("Creation of an Accepted OIR is allowed", self._pid) as check: diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/pool_info.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/pool_info.md new file mode 100644 index 0000000000..e267f5d208 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/pool_info.md @@ -0,0 +1,75 @@ +# DSS pool information test scenario + +This test scenario obtains and validates information about any InterUSS ASTM F3548-21 DSS instances in a pool of interest. + +## Resources + +### dss_instances + +A [`DSSInstancesResource`](../../../../resources/astm/f3548/v21/dss.py) containing all instances of interest within a single pool. Not all instances must be InterUSS implementations, but only instances which are sufficiently-recent InterUSS implementations are likely to produce useful information and validation. + +The auth adapter for each DSS instance must support uss_qualifier's acquisition of access tokens with the `interuss.pool_status.read` scope. + +## aux information test case + +Information available via the InterUSS DSS implementation's aux interface is queried and noted and/or evaluated when obtained. + +### Examine versions test step + +Each DSS instance is queried for its version at `/aux/v1/version` and the versions are noted for the report. + +#### ⚠️ Version obtained successfully check + +If uss_qualifier cannot successfully obtain version information, the USS hosting the queried DSS instance does not comply with **[interuss.dss.hosting.ExposeAux](../../../../requirements/interuss/dss/hosting.md)**. + +TODO: Implement + +### Examine pool test step + +Each DSS instance is queried for its understanding of the pool at `/aux/v1/pool`. + +#### ⚠️ Pool information obtained successfully check + +If uss_qualifier cannot successfully obtain pool information, the USS hosting the queried DSS instance does not comply with **[interuss.dss.hosting.ExposeAux](../../../../requirements/interuss/dss/hosting.md)**. + +TODO: Implement + +#### ⚠️ DAR ID matches check + +If any two DSS instances who successfully reported their DAR ID in the pool information have differeing DAR IDs, those DSS instances are not part of the same pool and have therefore failed to comply with **[astm.f3548.v21.DSS0020](../../../../requirements/astm/f3548/v21.md)**. + +TODO: Implement + +### Examine DSS instances test step + +Each DSS instance is queried for its understanding of the other instances in the pool at `/aux/v1/pool/dss_instances`. + +#### ⚠️ DSS instances obtained successfully check + +If uss_qualifier cannot successfully obtain the pool's DSS instance information from the queried DSS, the USS hosting the DSS instance does not comply with **[interuss.dss.hosting.ExposeAux](../../../../requirements/interuss/dss/hosting.md)**. + +TODO: Implement + +#### ⚠️ DSS instances have same understanding of other instances check + +If any two DSS instances differ in what DSS instances they believe are part of the pool, then those two instances are not successfully reading a shared repository of pool information. In the InterUSS implementation, this means they have failed to comply with **[astm.f3548.v21.DSS0020](../../../../requirements/astm/f3548/v21.md)** because reading from a shared repository of information is what achieves DSS0020 compliance in InterUSS DSS implementations. + +A difference in any element of the DSSInstances information (e.g., instance ID, public endpoint) may cause this check to fail, but uss_qualifier attempts to avoid false positives when a new heartbeat arrives in between instance polling. + +TODO: Implement + +### Examine CA certificates test step + +Each DSS instance is queried for the CA certificates used to sign its node certificates (via `/aux/v1/configuration/ca_certs`) and also the CA certificates it accepts (via `/aux/v1/configuration/accepted_ca_certs`). + +#### ⚠️ CA certs obtained successfully check + +If uss_qualifier cannot successfully obtain CA certificate information, the USS hosting the queried DSS instance does not comply with **[interuss.dss.hosting.ExposeAux](../../../../requirements/interuss/dss/hosting.md)**. + +TODO: Implement + +#### ⚠️ DSS instance accepts all pool CA certs check + +If a DSS instance does not accept any of the CA certificates used to sign the node certificates of any other DSS instance in the pool, the USS hosting that DSS instance will not comply with **[astm.f3548.v21.DSS0020](../../../../requirements/astm/f3548/v21.md)** because the InterUSS DSS implementation relies on every DSS instance accepting the certificates of every other DSS instance in the pool to comply with this requirement. + +TODO: Implement diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/pool_info.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/pool_info.py new file mode 100644 index 0000000000..e8a40add8f --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/pool_info.py @@ -0,0 +1,13 @@ +from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstancesResource +from monitoring.uss_qualifier.scenarios.astm.dss.pool_info import ( + PoolInfo as CommonPoolInfo, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario + + +class PoolInfo(TestScenario, CommonPoolInfo): + def __init__( + self, + dss_instances: DSSInstancesResource, + ): + super().__init__() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/report.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/report.py index 22e8ec7330..96e1487444 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/report.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/report.py @@ -39,7 +39,9 @@ def _dss_report_case(self): def gen_record() -> ExchangeRecord: op = OPERATIONS[OperationID.GetOperationalIntentReference] query = query_and_describe( - infrastructure.UTMClientSession(make_fake_url("dss")), + infrastructure.utm_client_session_factory.get_session( + make_fake_url("dss") + ), op.verb, op.path.format(entityid="dummy_op_intent_id"), QueryType.F3548v21DSSGetOperationalIntentReference, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.md index 918733bfcf..d97201b84e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.md @@ -173,6 +173,6 @@ If the subscription still exists on one of the other DSS instances, one of the i ## Cleanup -### [Clean any straggling OIRs with known test IDs](clean_workspace_op_intents.md) +### [Clean any straggling OIRs with known test IDs](clean_workspace_op_intents_during_cleanup.md) -### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs.md) +### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.py index 9e044ce3be..77f80af828 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions.py @@ -10,10 +10,7 @@ ) from uas_standards.astm.f3548.v21.constants import Scope -from monitoring.monitorlib.delay import sleep from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.geotemporal import Volume4D -from monitoring.monitorlib.temporal import Time from monitoring.monitorlib.testing import make_fake_url from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.configurations.configuration import ParticipantID @@ -58,6 +55,8 @@ class SubscriptionInteractions(TestScenario): _manager: str + _planning_area: PlanningAreaResource + def __init__( self, dss: DSSInstanceResource, @@ -80,7 +79,7 @@ def __init__( } self._dss = dss.get_instance(scopes) self._pid = [self._dss.participant_id] - self._planning_area = planning_area.specification + self._planning_area = planning_area self._secondary_instances = [ dss.get_instance(scopes) for dss in other_instances.dss_instances @@ -374,7 +373,7 @@ def _steps_expire_subs_at_each_dss(self): ) self._current_subs.pop(sub_id) - sleep( + self.sleep( timedelta(seconds=WAIT_FOR_EXPIRY_SEC), "waiting for subscriptions to expire", ) @@ -383,10 +382,8 @@ def _steps_expire_subs_at_each_dss(self): sub_id = self._sub_ids[i] for other_dss in {self._dss, *self._secondary_instances} - {dss}: other_dss_subs = other_dss.query_subscriptions( - Volume4D( - volume=self._planning_area.volume, - time_start=Time(self._time_start), - time_end=Time(self._time_end), + self._planning_area.resolved_volume4d_with_times( + self._time_start, self._time_end ).to_f3548v21() ) self.record_query(other_dss_subs) @@ -451,7 +448,7 @@ def _verify_clean_secondaries_step(self): self.end_test_step() def _clean_workspace(self): - extents = Volume4D(volume=self._planning_area.volume) + extents = self._planning_area.resolved_volume4d_with_times(None, None) test_step_fragments.cleanup_active_oirs( self, self._dss, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.md index 826c75801f..a79216f5db 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.md @@ -125,6 +125,6 @@ If the DSS includes a deleted subscription, it fails to implement **[astm.f3548. ## Cleanup -### [Clean any straggling OIRs with known test IDs](clean_workspace_op_intents.md) +### [Clean any straggling OIRs with known test IDs](clean_workspace_op_intents_during_cleanup.md) -### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs.md) +### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.py index 41fc5a77b3..75e96cb3fa 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_interactions_deletion.py @@ -10,7 +10,6 @@ from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.testing import make_fake_url from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( @@ -44,6 +43,8 @@ class SubscriptionInteractionsDeletion(TestScenario): _manager: str + _planning_area: PlanningAreaResource + def __init__( self, dss: DSSInstanceResource, @@ -57,7 +58,7 @@ def __init__( Scope.StrategicCoordination: "create and delete subscriptions and operational intents" } self._dss = dss.get_instance(scopes) - self._planning_area = planning_area.specification + self._planning_area = planning_area self._secondary_instances = [ dss.get_instance(scopes) for dss in other_instances.dss_instances @@ -291,7 +292,7 @@ def _verify_clean_secondaries_step(self): self.end_test_step() def _clean_workspace(self): - extents = Volume4D(volume=self._planning_area.volume) + extents = self._planning_area.resolved_volume4d_with_times(None, None) test_step_fragments.cleanup_active_oirs( self, self._dss, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.md index 836591ea9c..328a39ae21 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.md @@ -185,4 +185,4 @@ If the DSS returns the deleted subscription in a search that covers the area it ## Cleanup -### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs.md) +### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.py index f6591fd95b..dad9e940ab 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_simple.py @@ -60,6 +60,8 @@ class SubscriptionSimple(TestScenario): # An area designed to be too big to be allowed to search by the DSS _problematically_big_area_vol: Polygon + _planning_area: PlanningAreaResource + def __init__( self, dss: DSSInstanceResource, @@ -81,12 +83,12 @@ def __init__( self._dss = dss.get_instance(scopes) self._pid = [self._dss.participant_id] self._base_sub_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._planning_area = planning_area.specification + self._planning_area = planning_area # Build a ready-to-use 4D volume with no specified time for searching # the currently active subscriptions - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) # Prepare 4 different subscription ids: @@ -656,10 +658,13 @@ def _validate_subscription( with self.check( "Returned USS base URL has correct base URL", self._pid ) as check: - if sub_under_test.uss_base_url != self._planning_area.get_base_url(): + if ( + sub_under_test.uss_base_url + != self._planning_area.specification.get_base_url() + ): check.record_failed( "Returned USS Base URL does not match provided one", - details=f"Provided: {self._planning_area.get_base_url()}, Returned: {sub_under_test.uss_base_url}", + details=f"Provided: {self._planning_area.specification.get_base_url()}, Returned: {sub_under_test.uss_base_url}", query_timestamps=query_timestamps, ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.md index 95fc9fc391..870a2becf0 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.md @@ -52,4 +52,4 @@ or fails to truncate the duration of the subscription to this duration, it is in ## Cleanup -### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs.md) +### [Clean any straggling subscriptions with known test IDs](clean_workspace_subs_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py index 16ad6574cc..ead8c75b55 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py @@ -13,7 +13,6 @@ from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource from monitoring.uss_qualifier.resources.planning_area import ( PlanningAreaResource, - PlanningAreaSpecification, ) from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments from monitoring.uss_qualifier.scenarios.scenario import PendingCheck, TestScenario @@ -39,7 +38,7 @@ class SubscriptionValidation(TestScenario): _sub_id: str """Base identifier for the subscriptions that will be created""" - _planning_area: PlanningAreaSpecification + _planning_area: PlanningAreaResource _planning_area_volume4d: Volume4D @@ -71,12 +70,12 @@ def __init__( self._dss = dss.get_instance(scopes) self._pid = [self._dss.participant_id] self._sub_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._planning_area = planning_area.specification + self._planning_area = planning_area # Build a ready-to-use 4D volume with no specified time for searching # the currently active subscriptions - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) self._sub_generation_kwargs = dict( diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.md index eff16b367c..9897cd6543 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.md @@ -21,7 +21,8 @@ are properly propagated to every other DSS instance participating in the deploym ### planning_area -[`PlanningAreaResource`](../../../../../resources/planning_area.py) describes the 3D volume in which constraint reference will be created. +[`PlanningAreaResource`](../../../../../resources/planning_area.py) describes the 3D volume in which constraint reference will be created. Note that any start or end times specified in the underlying volume template +will be ignored. ### client_identity @@ -158,7 +159,7 @@ Attempt to query and search for the deleted constraint reference in various ways #### 🛑 Deleted CR cannot be retrieved from all DSS instances check If a DSS returns an constraint reference that was previously successfully deleted from the primary DSS, -either one of the primary DSS or the DSS that returned the constraint reference is in violation of **[astm.f3548.v21.DSS0210,2a](../../../../../requirements/astm/f3548/v21.md)**, **[astm.f3548.v21.DSS0210,A2-7-2,3b](../../../../../requirements/astm/f3548/v21.md)**, +either one of the primary DSS or the DSS that returned the constraint reference is in violation of **[astm.f3548.v21.DSS0210,2a](../../../../../requirements/astm/f3548/v21.md)**, **[astm.f3548.v21.DSS0210,A2-7-2,3d](../../../../../requirements/astm/f3548/v21.md)**, **[astm.f3548.v21.DSS0215](../../../../../requirements/astm/f3548/v21.md)** and **[astm.f3548.v21.DSS0020](../../../../../requirements/astm/f3548/v21.md)**. #### [CR can be searched for](../fragments/cr/crud/search_query.md) @@ -166,7 +167,7 @@ either one of the primary DSS or the DSS that returned the constraint reference #### 🛑 Deleted CR cannot be searched for from all DSS instances check If a DSS returns an constraint reference that was previously successfully deleted from the primary DSS, -either one of the primary DSS or the DSS that returned the constraint reference is in violation of **[astm.f3548.v21.DSS0210,2a](../../../../../requirements/astm/f3548/v21.md)**, **[astm.f3548.v21.DSS0210,A2-7-2,3a](../../../../../requirements/astm/f3548/v21.md)**, +either one of the primary DSS or the DSS that returned the constraint reference is in violation of **[astm.f3548.v21.DSS0210,2a](../../../../../requirements/astm/f3548/v21.md)**, **[astm.f3548.v21.DSS0210,A2-7-2,3c](../../../../../requirements/astm/f3548/v21.md)**, **[astm.f3548.v21.DSS0215](../../../../../requirements/astm/f3548/v21.md)** and **[astm.f3548.v21.DSS0020](../../../../../requirements/astm/f3548/v21.md)**. -## [Cleanup](../clean_workspace_constraints.md) +## [Cleanup](../clean_workspace_constraints_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py index 2718a63661..120e58ce5a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/constraint_ref_synchronization.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from typing import Any from uas_standards.astm.f3548.v21.api import ( ConstraintReference, @@ -8,7 +9,7 @@ from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import Query, QueryError -from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection +from monitoring.monitorlib.geotemporal import Volume4DCollection from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( @@ -63,6 +64,8 @@ class CRSynchronization(TestScenario): _expected_manager: str + _planning_area: PlanningAreaResource + def __init__( self, dss: DSSInstanceResource, @@ -99,12 +102,12 @@ def __init__( self._cr_id = id_generator.id_factory.make_id(self.CR_TYPE) self._expected_manager = client_identity.subject() - self._planning_area = planning_area.specification + self._planning_area = planning_area # Build a ready-to-use 4D volume with no specified time for searching # the currently active CRs - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) self._current_cr = None @@ -341,6 +344,7 @@ def _validate_cr_from_secondary( involved_participants: list[str], from_search: bool = False, ): + check_args: dict[str, Any] = {} with self.check(main_check_name, involved_participants) as main_check: with self.check( "Propagated constraint reference contains the correct manager", diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.md index f67c129759..527b991c2a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.md @@ -134,4 +134,4 @@ If a DSS returns an operational intent reference that was previously successfull either one of the primary DSS or the DSS that returned the operational intent reference is in violation of **[astm.f3548.v21.DSS0210,2a](../../../../../requirements/astm/f3548/v21.md)** and **[astm.f3548.v21.DSS0210,A2-7-2,3a](../../../../../requirements/astm/f3548/v21.md)**. -## [Cleanup](../clean_workspace_op_intents.md) +## [Cleanup](../clean_workspace_op_intents_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py index bdcf4b797d..7232a12920 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/op_intent_ref_synchronization.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from typing import Any from uas_standards.astm.f3548.v21.api import ( EntityID, @@ -9,7 +10,7 @@ from uas_standards.astm.f3548.v21.constants import Scope from monitoring.monitorlib.fetch import Query, QueryError -from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection +from monitoring.monitorlib.geotemporal import Volume4DCollection from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( @@ -96,12 +97,12 @@ def __init__( self._oir_id = id_generator.id_factory.make_id(self.SUB_TYPE) self._expected_manager = client_identity.subject() - self._planning_area = planning_area.specification + self._planning_area = planning_area # Build a ready-to-use 4D volume with no specified time for searching # the currently active OIRs - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) self._current_oir = None @@ -110,7 +111,7 @@ def run(self, context: ExecutionContext): self._oir_params = self._planning_area.get_new_operational_intent_ref_params( key=[], state=OperationalIntentState.Accepted, - uss_base_url=self._planning_area.get_base_url(), + uss_base_url=self._planning_area.specification.get_base_url(), time_start=datetime.now() - timedelta(seconds=10), time_end=datetime.now() + timedelta(minutes=45), subscription_id=None, @@ -351,7 +352,7 @@ def _validate_oir_from_secondary( involved_participants: list[str], ): # TODO: this main check mechanism may be removed if we are able to specify requirements to be validated in test step fragments - + check_args: dict[str, Any] = {} with self.check(main_check_name, involved_participants) as main_check: with self.check( "Propagated operational intent reference contains the correct manager", diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.md index ce5f3926f8..fce8e29846 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.md @@ -192,7 +192,7 @@ As a result, the DSS pool under test is failing to meet **[astm.f3548.v21.DSS002 ## Cleanup -### [Ensure that no subscriptions with the known test IDs remain in the DSS](../clean_workspace_subs.md) +### [Ensure that no subscriptions with the known test IDs remain in the DSS](../clean_workspace_subs_during_cleanup.md) This includes the main test subscription used in this test, as well as the extra subscription used for testing the `manager` field sync, if the test is configured to test for it. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.py index 651c7153e3..c6ca1a1638 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/subscription_synchronization.py @@ -117,12 +117,12 @@ def __init__( ] self._acl_sub_id = id_generator.id_factory.make_id(self.ACL_SUB_TYPE) - self._planning_area = planning_area.specification + self._planning_area = planning_area # Build a ready-to-use 4D volume with no specified time for searching # the currently active subscriptions - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, + self._planning_area_volume4d = self._planning_area.resolved_volume4d_with_times( + None, None ) # Get a list of vertices enclosing the area diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/uss_availability_synchronization.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/uss_availability_synchronization.py index 6559d63473..d87c15a2c7 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/uss_availability_synchronization.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/synchronization/uss_availability_synchronization.py @@ -115,6 +115,8 @@ def run(self, context: ExecutionContext): self.end_test_case() + self.end_test_scenario() + def _step_unknown_uss_reported_as_unknown(self): unknown_uss_id = "ThisIdShouldNotExistOnTheDSS" with self.check( diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/test_step_fragments.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/test_step_fragments.py index 996018ab61..103e5509b5 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/test_step_fragments.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/test_step_fragments.py @@ -350,8 +350,6 @@ def get_uss_availability( Returns: The state and version of the USS availability. """ - availability = UssAvailabilityState.Unknown - version = "" with scenario.check( "USS Availability can be requested", dss.participant_id ) as check: @@ -361,6 +359,7 @@ def get_uss_availability( availability = avail_response.status.availability version = avail_response.version + return availability, version except QueryError as e: scenario.record_queries(e.queries) avail_query = e.queries[0] @@ -369,7 +368,7 @@ def get_uss_availability( details=f"DSS responded code {avail_query.status_code}; {e}", query_timestamps=[avail_query.request.timestamp], ) - return availability, version + raise ScenarioDidNotStopError(check) def set_uss_availability( @@ -387,7 +386,6 @@ def set_uss_availability( Returns: The new version of the USS availability. """ - version = "" with scenario.check( "USS Availability can be updated", [dss.participant_id] ) as check: @@ -398,6 +396,7 @@ def set_uss_availability( version, ) scenario.record_query(avail_query) + return version except QueryError as e: scenario.record_queries(e.queries) avail_query = e.queries[0] @@ -406,7 +405,7 @@ def set_uss_availability( details=f"DSS responded code {avail_query.status_code}; {e}", query_timestamps=[avail_query.request.timestamp], ) - return version + raise ScenarioDidNotStopError(check) def make_dss_report( diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/uss_availability_mutation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/uss_availability_mutation.md new file mode 100644 index 0000000000..32c01c1487 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/uss_availability_mutation.md @@ -0,0 +1,44 @@ +# ASTM Availability DSS: USS Availability Mutation test scenario + +## Overview + +Verifies the behavior of a DSS for simple interactions pertaining to USS availability status. + +## Resources + +### dss + +[`DSSInstanceResource`](../../../../resources/astm/f3548/v21/dss.py) the DSS instance through which entities are created, modified and deleted. + +### client_identity + +[`ClientIdentityResource`](../../../../resources/communications/client_identity.py) the client identity with the `utm.availability_arbitration` scope that will be used to report the availability status. + +## Update USS availability state test case + +### Declare USS as available at DSS test step + +#### [Availability can be read](./fragments/availability/read.md) + +#### [Availability can be updated](./fragments/availability/update.md) + +## Update requires correct version test case + +Test DSS behavior when update requests are not providing the required version. + +### Attempt update with missing version test step + +This step verifies that an existing USS availability status cannot be mutated with a missing version. + +#### 🛑 Request to update USS availability status with empty version fails check + +If the DSS under test allows the qualifier to update the USS availability status with a request that provided an empty version, it is in violation of **[astm.f3548.v21.DSS0100,1](../../../../requirements/astm/f3548/v21.md)** + +### Attempt update with incorrect version test step + +This step verifies that an existing OIR cannot be mutated with an incorrect version. + +#### 🛑 Request to update USS availability status with incorrect version fails check + +If the DSS under test allows the qualifier to update the USS availability status with a request that provided an incorrect version, +it is in violation of **[astm.f3548.v21.DSS0100,1](../../../../requirements/astm/f3548/v21.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/uss_availability_mutation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/uss_availability_mutation.py new file mode 100644 index 0000000000..190b7fddd3 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/uss_availability_mutation.py @@ -0,0 +1,132 @@ +from uas_standards.astm.f3548.v21.api import UssAvailabilityState +from uas_standards.astm.f3548.v21.constants import Scope + +from monitoring.monitorlib.fetch import QueryError +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( + DSSInstance, + DSSInstanceResource, +) +from monitoring.uss_qualifier.resources.communications import ClientIdentityResource +from monitoring.uss_qualifier.scenarios.astm.utm.dss.test_step_fragments import ( + get_uss_availability, + set_uss_availability, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.suites.suite import ExecutionContext + + +class UssAvailabilityMutation(TestScenario): + """ + A scenario that verifies that USS availability status cannot be updated with the incorrect version. + """ + + _dss: DSSInstance + _pid: list[str] + + _uss_id: str + + def __init__( + self, + dss: DSSInstanceResource, + client_identity: ClientIdentityResource, + ): + """ + Args: + dss: dss to test + client_identity: tells us the identity we should expect as an entity's manager + """ + super().__init__() + scopes: dict[str, str] = { + Scope.AvailabilityArbitration: "read and set availability for a USS" + } + + self._dss = dss.get_instance(scopes) + self._pid = [self._dss.participant_id] + + self._uss_id = client_identity.subject() + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + + self.begin_test_case("Update USS availability state") + self._step_declare_uss_available() + self.end_test_case() + + self.begin_test_case("Update requires correct version") + self._step_attempt_update_missing_version() + self._step_attempt_update_incorrect_version() + self.end_test_case() + + def _step_declare_uss_available(self): + self.begin_test_step("Declare USS as available at DSS") + _, version = get_uss_availability( + self, + self._dss, + self._uss_id, + Scope.AvailabilityArbitration, + ) + set_uss_availability( + self, self._dss, self._uss_id, UssAvailabilityState.Normal, version + ) + self.end_test_step() + + def _step_attempt_update_missing_version(self): + self.begin_test_step("Attempt update with missing version") + with self.check( + "Request to update USS availability status with empty version fails", + self._pid, + ) as check: + try: + _, q = self._dss.set_uss_availability( + self._uss_id, + UssAvailabilityState.Down, + "", + ) + self.record_query(q) + # We don't expect the reach this point: + check.record_failed( + summary="Set USS availability with missing version was not expected to succeed", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an missing version, but got {q.status_code}", + query_timestamps=[q.request.timestamp], + ) + except QueryError as qe: + self.record_queries(qe.queries) + # An empty version can be seen as: + # an incorrect parameter (400), a reference to a non-existing entity (404) as well as a conflict (409) + if qe.cause_status_code not in [400, 404, 409]: + check.record_failed( + summary="Set USS availability with missing version failed for unexpected reason", + details=f"Was expecting an HTTP 400, 404 or 409 response because of an missing version, but got {qe.cause_status_code}: {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + self.end_test_step() + + def _step_attempt_update_incorrect_version(self): + self.begin_test_step("Attempt update with incorrect version") + with self.check( + "Request to update USS availability status with incorrect version fails", + self._pid, + ) as check: + try: + _, q = self._dss.set_uss_availability( + self._uss_id, + UssAvailabilityState.Down, + "ThisIsAnIncorrectVersion", + ) + self.record_query(q) + # We don't expect the reach this point: + check.record_failed( + summary="Set USS availability with incorrect version was not expected to succeed", + details=f"Was expecting an HTTP 409 response because of an incorrect version, but got {q.status_code}", + query_timestamps=[q.request.timestamp], + ) + except QueryError as qe: + self.record_queries(qe.queries) + # The spec explicitly requests a 409 response code for incorrect version. + if qe.cause_status_code != 409: + check.record_failed( + summary="Set USS availability with incorrect version failed for unexpected reason", + details=f"Was expecting an HTTP 409 response because of an incorrect version, but got {qe.cause_status_code}: {qe.msg}", + query_timestamps=qe.query_timestamps, + ) + self.end_test_step() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py b/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py index 3c1b9fc219..2aa249c235 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py @@ -1,6 +1,13 @@ 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 ( + OperationalIntent, + OperationalIntentDetails, + OperationalIntentReference, + UssAvailabilityState, + Volume4D, +) from monitoring.monitorlib.geotemporal import Volume4DCollection from monitoring.monitorlib.scd import priority_of @@ -8,6 +15,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, @@ -56,3 +122,58 @@ def validate_op_intent_details( ) return "; ".join(errors_text) if len(errors_text) > 0 else None + + +def errors_for_nonequivalent_op_intent_details( + old_oi: OperationalIntent, + new_oi: OperationalIntent, +) -> str | None: + errors_text: list[str] = [] + old_ref = old_oi.reference + new_ref = new_oi.reference + old_details = old_oi.details + new_details = new_oi.details + + def append_ovn_err(): + errors_text.append( + f"The OVN {new_ref.ovn} reported by USS for operational intent {new_ref.id} at version {new_ref.version} does not match the value {old_ref.ovn} previously indicated by the USS for the same operational intent with the same version" + ) + + def append_err(field_name: str): + errors_text.append( + f"The value {getattr(new_details, field_name)} for `{field_name}` reported by USS for details of operational intent {new_ref.id} at version {new_ref.version} (OVN {new_ref.ovn if 'ovn' in new_ref else 'empty'}) does not match the value {getattr(old_details, field_name)} previously indicated by the USS for the same operational intent with the same version" + ) + + if "ovn" in old_ref and "ovn" in new_ref: + if old_ref.ovn != new_ref.ovn: + append_ovn_err() + elif "ovn" in old_ref or "ovn" in new_ref: + append_ovn_err() + + if "priority" in old_details and "priority" in new_details: + if old_details.priority != new_details.priority: + append_err("priority") + elif "priority" in old_details or "priority" in new_details: + append_err("priority") + + if (old_details.volumes is None) != (new_details.volumes is None): + append_err("volumes") + elif old_details.volumes and new_details.volumes: + if not Volume4DCollection.from_f3548v21(old_details.volumes).is_equivalent( + Volume4DCollection.from_f3548v21(new_details.volumes) + ): + append_err("volumes") + + if (old_details.off_nominal_volumes is None) != ( + new_details.off_nominal_volumes is None + ): + append_err("off_nominal_volumes") + elif old_details.off_nominal_volumes and new_details.off_nominal_volumes: + if not Volume4DCollection.from_f3548v21( + old_details.off_nominal_volumes + ).is_equivalent( + Volume4DCollection.from_f3548v21(new_details.off_nominal_volumes) + ): + append_err("off_nominal_volumes") + + return "; ".join(errors_text) if errors_text else None diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md index d451c0f2e7..bfba22bcaf 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md @@ -141,5 +141,6 @@ to reject or accept the flight. If the USS indicates that the attempt failed, th #### [Validate Tiny Overlap Conflict Flight not planned](../validate_not_shared_operational_intent.md) ## Cleanup + ### ⚠️ Successful flight deletion check **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../requirements/interuss/automated_testing/flight_planning.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py index fb07fcf437..f17615dc28 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py @@ -1,6 +1,5 @@ from datetime import timedelta -import arrow from implicitdict import StringBasedTimeDelta from uas_standards.astm.f3548.v21.constants import ( OiMaxPlanHorizonDays, @@ -21,7 +20,6 @@ FlightPlanStatus, PlanningActivityResult, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest 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 @@ -49,8 +47,6 @@ class FlightIntentValidation(TestScenario): ) PLAN_VALID_FLIGHT_STEP = "Plan Valid Flight" - times: dict[TimeDuringTest, Time] - valid_flight: FlightInfoTemplate valid_activated: FlightInfoTemplate @@ -131,15 +127,9 @@ def __init__( setattr(self, efi.intent_id, templates[efi.intent_id]) def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - self.times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - return flight_template.resolve(self.times) + return flight_template.resolve(self.time_context.evaluate_now()) def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - self.begin_test_scenario(context) self.record_note( "Tested USS", @@ -218,11 +208,14 @@ def _validate_ended_cancellation(self): self.dss, valid_flight, ) as planned_validator: - _, flight_id = plan_flight( + _, flight_id, as_planned = plan_flight( self, self.tested_uss, valid_flight, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + valid_flight = as_planned oi_ref = planned_validator.expect_shared(valid_flight) self.end_test_step() @@ -241,11 +234,12 @@ def _validate_precision_intersection(self): self.begin_test_step(self.PLAN_VALID_FLIGHT_STEP) valid_flight = self.resolve_flight(self.valid_flight) - plan_flight( + _, _, as_planned = plan_flight( self, self.tested_uss, valid_flight, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed self.end_test_step() self.begin_test_step("Attempt to plan Tiny Overlap Conflict Flight") diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/make_uss_report.py b/monitoring/uss_qualifier/scenarios/astm/utm/make_uss_report.py index 01238fa167..aeef827c6a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/make_uss_report.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/make_uss_report.py @@ -12,7 +12,11 @@ ) from monitoring.monitorlib.fetch import Query, QueryError, QueryType, query_and_describe -from monitoring.monitorlib.infrastructure import AuthAdapter, UTMClientSession +from monitoring.monitorlib.infrastructure import ( + AuthAdapter, + UTMClientSession, + utm_client_session_factory, +) from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.communications import AuthAdapterResource from monitoring.uss_qualifier.scenarios.astm.utm import FlightIntentValidation @@ -112,7 +116,7 @@ def _get_uss_base_urls(self, context: ExecutionContext) -> dict[str, Participant def _call_make_uss_report(self, base_urls: dict[str, ParticipantID]) -> None: for base_url, participant_id in base_urls.items(): - client = UTMClientSession(base_url, self._auth) + client = utm_client_session_factory.get_session(base_url, self._auth) url = base_url + OPERATIONS[OperationID.MakeUssReport].path t = StringBasedDateTime(arrow.utcnow()) exchange = ExchangeRecord( diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.md b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.md index dec3c6e53e..c2d4159b05 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.md @@ -83,6 +83,7 @@ Otherwise, the FlightIntentsResource must provide the following flight intents: + Because the scenario involves activation of intents, the start times of all activated intents must be during the time the test scenario is executed (not before). Additionally, their end times must leave sufficient time for the execution of the test scenario. @@ -97,7 +98,6 @@ CMSA role in order to transition to the `Nonconforming` state in order to create ### 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) @@ -189,10 +189,15 @@ directly activate the flight without planning it beforehand. The test driver attempts to enlarge Flight 1c so that it conflicts with Flight 2. Both flights are activated at the point where that change is requested. However, because the conflict did not exist when the modification was initiated, it should be rejected per **[astm.f3548.v21.SCD0050](../../../../../requirements/astm/f3548/v21.md)**. +In addition, Flight 1c should not have been removed, because doing so would leave an aircraft in flight without any flight plan. -#### [Validate Flight 1c not modified](../../validate_shared_operational_intent.md) -Because the modification attempt was invalid, either Flight 1c should not have been modified (because the USS kept the -original accepted request), or it should have been removed (because the USS rejected the replacement plan provided). +#### [Validate Flight 1c not modified](../../validate_not_shared_operational_intent.md) +Because the modification attempt was invalid, Flight 1c should not have been modified. + +### [Delete Flight 1c if USS did not support its modification test step](../../../../flight_planning/delete_flight_intent.md) +If, during the previous step, the USS indicated that it does not support modifications, then it will not be able to +modify Flight 1c into Flight 1 during the next test case. As such, the test driver deletes Flight 1c from the system so +that Flight 1 can be created directly activated during the next test case. ### [Delete Flight 2 test step](../../../../flight_planning/delete_flight_intent.md) To prepare for the next test case, Flight 2 must be removed from the system. @@ -229,6 +234,10 @@ per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../ If the USS rejects the transition, this check will fail. If the USS indicates that the operation is not supported, the USS does not support the CMSA role, and as such the scenario execution will stop without failing. +#### 🛑 Injection fidelity check + +The requested flight should have been updated essentially as requested. The system may adapt requested parameters as necessary, but may not change the test-critical attributes of the flight when fulfilling the planning request per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. + #### 🛑 Failure check All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per @@ -240,27 +249,34 @@ to reject or accept the flight. If the USS indicates that the injection attempt Before execution of this step, Flight 1 is activated (onto time range A) and Flight 2 is non-conforming (onto time range A), and both are in conflict. The test driver modifies Flight 1 in a way that still conflicts with Flight 2 by extending its time range A. -This modification results in a conflict between the two equal priority flight that already existed before the +This modification results in a conflict between the two equal priority flights that already existed before the modification was initiated. While this modification is expected to be accepted by the tested USS in general, the rejection of the modification does not constitute a violation of a requirement. However, the modification request must not result in a failure per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. +Nor should Flight 1 have been removed, because doing so would leave an aircraft in flight without any flight plan. -#### 🛑 Successful modification or rejection check -All flight intent data provided is correct and the USS should have either successfully modified the flight or rejected -properly the modification per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. -If the USS indicates that the injection attempt failed, this check will fail. +#### 🛑 Successful flight intent handling check +All flight intent data provided is correct and the USS should have either: +- successfully modified the flight; or +- properly rejected the modification; or +- indicated that it does not support modification +per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. +In any other case, this check will fail. #### 🛑 Failure check All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. -#### [Validate Flight 1 sharing](../../validate_shared_operational_intent.md) -This step validates that the response of the USS is consistent with the flight shared, i.e. either it was properly -modified, or the USS considered the attempt invalid. In the latter case, because the modification attempt was invalid, -either Flight 1 should not have been modified (because the USS kept the original accepted request), or it should have -been removed (because the USS rejected the replacement plan provided). +#### [Validate Flight 1 sharing if USS accepted its modification](../../validate_shared_operational_intent.md) +This step validates that the response of the USS is consistent with the flight shared if the USS accepted the +modification, i.e. whether Flight 1 was properly modified. + +#### [Validate Flight 1 not modified if USS rejected its modification](../../validate_not_shared_operational_intent.md) +This step validates that the response of the USS is consistent with the flight shared if the USS rejected the +modification, i.e. whether Flight 1 was not modified. ## Cleanup + ### ⚠️ Successful flight deletion check **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../../requirements/interuss/automated_testing/flight_planning.md)** 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 8ec91a1ab9..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 @@ -1,15 +1,10 @@ -import arrow from uas_standards.astm.f3548.v21.api import OperationalIntentReference 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 ( AirspaceUsageState, - FlightInfo, UasState, ) from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( @@ -19,19 +14,16 @@ FlightPlanStatus, PlanningActivityResult, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest -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_validation import ( ExpectedFlightIntent, - 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 ( @@ -42,21 +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): - times: dict[TimeDuringTest, Time] - +class ConflictEqualPriorityNotPermitted(PlanningSequenceScenario): flight1_id: str | None = None flight1_planned: FlightInfoTemplate flight1_activated: FlightInfoTemplate @@ -65,26 +53,18 @@ class ConflictEqualPriorityNotPermitted(TestScenario): flight1c_activated: FlightInfoTemplate flight2_id: str | None = None - flight2m_planned: FlightInfoTemplate - flight2_planned: FlightInfoTemplate - flight2_activated: FlightInfoTemplate - flight2_nonconforming: FlightInfoTemplate - - tested_uss: FlightPlannerClient - control_uss: FlightPlannerClient - dss: DSSInstance + equal_prio_flight2m_planned: FlightInfoTemplate + equal_prio_flight2_planned: FlightInfoTemplate + equal_prio_flight2_activated: FlightInfoTemplate + equal_prio_flight2_nonconforming: 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" } @@ -93,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", @@ -163,53 +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. ] - templates = flight_intents.get_flight_intents() - try: - self._intents_extent = validate_flight_intent_templates( - 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_", ""), templates[efi.intent_id] - ) - - def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - self.times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - return flight_template.resolve(self.times) - - def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - - 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}", + 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.begin_test_case("Prerequisites check") - self.begin_test_step("Verify area is clear") - validate_clear_area( - self, - self.dss, - [self._intents_extent], - ignore_self=True, - ) - 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() @@ -232,11 +173,9 @@ 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) + flight2_planned = self.resolve_flight(self.equal_prio_flight2_planned) with OpIntentValidator( self, @@ -244,16 +183,18 @@ def _attempt_plan_flight_conflict(self): self.dss, flight2_planned, ) as validator: - _, self.flight2_id = plan_flight( + _, self.flight2_id, as_planned = plan_flight( self, self.control_uss, flight2_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2_planned = as_planned flight_2_oi_ref = validator.expect_shared(flight2_planned) 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, @@ -262,12 +203,14 @@ def _attempt_plan_flight_conflict(self): flight2_activated, flight_2_oi_ref, ) as validator: - activate_flight( + _, _, as_planned = activate_flight( self, self.control_uss, flight2_activated, self.flight2_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2_activated = as_planned validator.expect_shared(flight2_activated) self.end_test_step() @@ -319,12 +262,15 @@ def _attempt_modify_planned_flight_conflict( self.dss, flight1c_planned, ) as validator: - _, self.flight1_id = plan_flight( + _, self.flight1_id, as_planned = plan_flight( self, self.tested_uss, flight1c_planned, nearby_potential_conflict=True, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight1c_planned = as_planned flight_1_oi_ref = validator.expect_shared(flight1c_planned) self.end_test_step() @@ -364,12 +310,15 @@ def _attempt_modify_activated_flight_conflict( flight1c_activated, flight_1_oi_ref, ) as validator: - activate_flight( + _, _, as_planned = activate_flight( self, self.tested_uss, flight1c_activated, self.flight1_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight1c_activated = as_planned flight_1_oi_ref = validator.expect_shared(flight1c_activated) self.end_test_step() @@ -383,17 +332,25 @@ def _attempt_modify_activated_flight_conflict( [flight1c_activated, flight1_activated], flight_1_oi_ref, ) as validator: - modify_activated_conflict_flight( + modify_resp = modify_activated_conflict_flight( self, self.tested_uss, flight1_activated, self.flight1_id, ) - flight_1_oi_ref = validator.expect_shared( - flight1c_activated, skip_if_not_found=True - ) + validator.expect_not_shared() self.end_test_step() + if modify_resp.activity_result == PlanningActivityResult.NotSupported: + self.begin_test_step( + "Delete Flight 1c if USS did not support its modification" + ) + if self.flight1_id is None: + raise ValueError("flight1_id is None") + delete_flight(self, self.tested_uss, self.flight1_id) + self.flight1_id = None + self.end_test_step() + self.begin_test_step("Delete Flight 2") delete_flight(self, self.control_uss, self.flight2_id) self.flight2_id = None @@ -415,17 +372,20 @@ def _modify_activated_flight_preexisting_conflict( flight1_activated, flight_1_oi_ref, ) as validator: - activate_flight( + _, self.flight1_id, as_planned = activate_flight( self, self.tested_uss, flight1_activated, self.flight1_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight1_activated = as_planned flight_1_oi_ref = validator.expect_shared(flight1_activated) 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, @@ -433,16 +393,21 @@ def _modify_activated_flight_preexisting_conflict( self.dss, flight2m_planned, ) as validator: - _, self.flight2_id = plan_flight( + _, self.flight2_id, as_planned = plan_flight( self, self.control_uss, flight2m_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight2m_planned = as_planned flight_2_oi_ref = validator.expect_shared(flight2m_planned) 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, @@ -451,7 +416,7 @@ def _modify_activated_flight_preexisting_conflict( [flight2m_planned, flight2_nonconforming], flight_2_oi_ref, ) as validator: - resp_flight_2, _ = submit_flight( + resp_flight_2, _, as_planned = submit_flight( scenario=self, success_check="Successful transition to non-conforming state", expected_results={ @@ -463,6 +428,8 @@ def _modify_activated_flight_preexisting_conflict( flight_info=flight2_nonconforming, flight_id=self.flight2_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2_nonconforming = as_planned if resp_flight_2.activity_result == PlanningActivityResult.NotSupported: msg = f"{self.control_uss.participant_id} does not support the transition to a Nonconforming state; execution of the scenario was stopped without failure" @@ -484,27 +451,27 @@ def _modify_activated_flight_preexisting_conflict( [flight1_activated, flight1m_activated], flight_1_oi_ref, ) as validator: - resp_flight_1, _ = submit_flight( + resp_flight_1, _, as_planned = submit_flight( scenario=self, - success_check="Successful modification or rejection", + success_check="Successful flight intent handling", expected_results={ (PlanningActivityResult.Completed, FlightPlanStatus.OkToFly), (PlanningActivityResult.Rejected, FlightPlanStatus.OkToFly), - (PlanningActivityResult.Rejected, FlightPlanStatus.Closed), + (PlanningActivityResult.NotSupported, FlightPlanStatus.OkToFly), }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=self.tested_uss, flight_info=flight1m_activated, flight_id=self.flight1_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight1m_activated = as_planned if resp_flight_1.activity_result == PlanningActivityResult.Completed: validator.expect_shared(flight1m_activated) - elif resp_flight_1.activity_result == PlanningActivityResult.Rejected: - validator.expect_shared(flight1_activated, skip_if_not_found=True) + elif resp_flight_1.activity_result in { + PlanningActivityResult.Rejected, + PlanningActivityResult.NotSupported, + }: + 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.md b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md index 7f6776c1e7..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 @@ -42,7 +42,7 @@ FlightIntentsResource that provides the following flight intents: Flight 1m Accepted Flight 2 - N/A + Flight 2m flight1m_activated @@ -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) @@ -108,7 +114,6 @@ higher priority. As such it should be rejected per **[astm.f3548.v21.SCD0015](.. ### [Delete Flight 2 test step](../../../../flight_planning/delete_flight_intent.md) - ## Attempt to modify planned flight in conflict test case ![Test case summary illustration](assets/attempt_to_modify_planned_flight_into_conflict.svg) @@ -121,6 +126,10 @@ The first flight should be successfully planned by the tested USS. ### Plan Flight 2 test step +#### ℹ️ Retrieve pre-existing notifications check + +Just before directing the planning activity, the test director observes pre-existing notifications which cannot relate to the planning activity that has not yet happened. If these notifications cannot be retrieved, the USS has not fully implemented **[interuss.automated_testing.flight_planning.ImplementAPI](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. + #### [Plan Flight 2](../../../../flight_planning/plan_flight_intent.md) The second flight should be successfully planned by the control USS. It conflicts with Flight 1, but it has higher priority. @@ -129,13 +138,17 @@ It conflicts with Flight 1, but it has higher priority. ### Check for conflict notifications test step -The test director checks whether a notification reporting the creation of a conflict by Flight 2 was sent to UAS personnel for the control_uss. +The test director checks whether a notification reporting the creation of a conflict by Flight 2 was sent to UAS personnel for the control_uss as is required by SCD0090. + +The test director also checks whether a notification reporting the creation of a new conflict with Flight 1 was sent to the UAS personnel for the tested_uss managing Flight 1 as is required by SCD0095. + +The timeliness and completeness of these notifications will be checked for compliance with SCD0090 and SCD0095 in aggregate in a later test scenario -- only the raw data is gathered in this test scenario. When eligible notifications are found, they are reported as [notes](../../notifications_to_operator/notification_checker.py) in the test scenario; if a USS does not have a `scd0090_notification` or `scd0095_notification` note, then uss_qualifier did not detect a required notification. -The test director also checks whether a notification reporting the creation of a new conflict with Flight 1 was sent to the UAS personnel for the tested_uss managing Flight 1. +Note that these actions will not be performed if the test director was unable to retrieve the notifications before the planning activity. #### ℹ️ Retrieve notifications check -We fetch the list of notifications. If the USS doesn't return a valid answer, we cannot proceed with notification tests. +We fetch the list of notifications. If the USS doesn't return a valid answer, the USS has not fully implemented **[interuss.automated_testing.flight_planning.ImplementAPI](../../../../../requirements/interuss/automated_testing/flight_planning.md)** and we cannot proceed with notification checks. ### Attempt to modify planned Flight 1 in conflict test step @@ -185,6 +198,10 @@ The second flight should be successfully planned by the control USS. ### Activate Flight 2 test step +#### ℹ️ Retrieve pre-existing notifications check + +Just before directing the planning activity, the test director observes pre-existing notifications which cannot relate to the planning activity that has not yet happened. This list of pre-existing notifications will be used later to determine if a new notification resulted from the planning activity. If these notifications cannot be retrieved, the USS has not fully implemented **[interuss.automated_testing.flight_planning.ImplementAPI](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. + #### [Activate Flight 2](../../../../flight_planning/activate_flight_intent.md) The test driver activates Flight 2, which should be done successfully given that it is the highest-priority flight. @@ -192,66 +209,89 @@ The test driver activates Flight 2, which should be done successfully given that ### Check for conflict notifications test step -The test director checks whether a notification reporting the modification of Flight 2 while in conflict was sent to UAS personnel for the control_uss. +The test director checks whether a notification reporting the modification of Flight 2 while in conflict was sent to UAS personnel for the control_uss as required by SCD0090. + +The test director also checks whether a notification reporting the modification of a flight conflicting with Flight 1 was sent to the UAS personnel for the tested_uss managing Flight 1 as required by SCD0095. -The test director also checks whether a notification reporting the modification of a flight conflicting with Flight 1 was sent to the UAS personnel for the tested_uss managing Flight 1. +The timeliness and completeness of these notifications will be checked for compliance with SCD0090 and SCD0095 in aggregate in a later test scenario -- only the raw data is gathered in this test scenario. When eligible notifications are found, they are reported as [notes](../../notifications_to_operator/notification_checker.py) in the test scenario; if a USS does not have a `scd0090_notification` or `scd0095_notification` note, then uss_qualifier did not detect a required notification. + +Note that these actions will not be performed if the test director was unable to retrieve the notifications before the planning activity. #### ℹ️ Retrieve notifications check -We fetch the list of notifications. If the USS doesn't return a valid answer, we cannot proceed with notification tests. +We fetch the list of notifications. If the USS doesn't return a valid answer, the USS has not fully implemented **[interuss.automated_testing.flight_planning.ImplementAPI](../../../../../requirements/interuss/automated_testing/flight_planning.md)** and we cannot proceed with notification checks. ### Modify activated Flight 1 in conflict with activated Flight 2 test step -#### [Modify Flight 1](../../../../flight_planning/modify_activated_flight_intent.md) Before execution of this step, flights 1 and 2 are activated and in conflict. Flight 2 is the highest-priority flight. -The test driver attempts to modify Flight 1 in a way that still conflicts with Flight 2. +The virtual user attempts to modify Flight 1 in a way that still conflicts with Flight 2, though the conflict is likely +reduced. + +A practical usage of this workflow might consist of the following sequence of events: +* Operator 1 plans and begins low-priority Flight 1 +* Operator 2 plans high-priority Flight 2 +* Operator 1 is notified of Flight 2 +* Operator 1 cancels their current mission and makes a new plan to exit Flight 2's volumes +* Operator 1 informs USS 1 of their plan + +This last action is the virtual user flight planning interaction the USS under test will encounter in this test step. + +#### [Modify Flight 1](../../../../flight_planning/modify_activated_flight_intent.md) +The virtual user attempts to modify Flight 1 to reduce/mitigate the conflict. The successful outcomes of the modification attempts: 1. Even though Flight 2 is the highest-priority flight, because the conflict existed before the modification was initiated, an accepted modification is considered a success per **[astm.f3548.v21.SCD0030](../../../../../requirements/astm/f3548/v21.md)**. -2. Due to the conflict, the USS may decide to be more conservative and to not support the modification. This is - considered a success as there is no positive requirement for the USS to accept the modification. - -A rejected modification will indicate a low severity failure. Indeed, in some situations a rejection may not be strictly -speaking a failure to meet a requirement. This could be the case for example if the USS does not support directly update -of intents and instead delete the previous one and create a new one. Since we cannot distinguish between an actual -failure to meet the requirement and a reasonable behavior due to implementation limitations, we indicate a low severity -failure which won't actually fail the test. +2. If the USS does not support flight updates of this type (perhaps because their operators have indicated that they + would not use such a capability), the USS may indicate that the action the virtual user is trying to take is not + supported. + +If the USS rejects the modification, the USS is indicating the modification is "not allowed". This is not correct when +judging solely on the rules of F3548-21. However, USSs may (generally) apply any number of additional rules to +determine whether or not a flight planning action is accepted by the USS. Because of this, a rejection is not +conclusive evidence of SCD0030 non-compliance since the rejection may be due to a different reason. However, rejection +in this case would not seem to be justified on the basis of conservatism since rejection is effectively a refusal to +communicate relevant information to other USSs and operators in the ecosystem. Therefore, a rejected modification will +indicate a low severity failure, producing a finding since we cannot distinguish between an actual failure to meet the +requirement and a reasonable behavior due non-F3548-21 constraints. Since this finding is a low severity failure, it +does not indicate a failure to comply with a requirement. In any case, whatever is the outcome of this step, there should not be any impact on the rest of the execution of the -scenario. An intent should exist (this is checked in the next step) and it should be either the previous or the modified -intent, both of which make no difference in the next steps. +scenario. An intent should exist (this is checked in the next step) and it should be either Flight 1 or Flight 1m, both +of which can be used in the next step since neither conflicts with Flight 2m. #### [Validate Flight 1 sharing](../../validate_shared_operational_intent.md) If the modification was accepted, Flight 1 should have been modified. -If the modification was not supported, Flight 1 should not have been modified. -If the modification was rejected, Flight 1 should not have been modified and should still exist. If it does not exist, -it means that there is an active flight without an operational intent, which is a failure to meet **[interuss.automated_testing.flight_planning.FlightCoveredByOperationalIntent](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. +If the modification was not supported or the modification was rejected, Flight 1 should not have been modified and +should still exist. If it does not exist, it means that there is an active flight without an operational intent, which +is a failure to meet **[interuss.automated_testing.flight_planning.FlightCoveredByOperationalIntent](../../../../../requirements/interuss/automated_testing/flight_planning.md)**. - -## Attempt to modify activated flight in conflict test case +## Attempt to modify activated flight into conflict test case ![Test case summary illustration](assets/attempt_to_modify_activated_flight_into_conflict.svg) ### Modify activated Flight 2 to not conflict with activated Flight 1 test step #### [Modify Flight 2](../../../../flight_planning/modify_planned_flight_intent.md) -The test driver modifies (activated) Flight 2 with the control USS so that it is not anymore in conflict with (activated) -flight of test USS. -As Flight 2 is of higher priority, this should succeed and leave Flight 1 clear of conflict. +The test driver modifies (activated) Flight 2 with the control USS so that it is not anymore in conflict with +(activated) flight of test USS. This should succeed and leave Flight 1 clear of conflict. + +If flight modification is not supported by the USS, the next test step cannot be performed and the test case will end +here. #### [Validate Flight 2 sharing](../../validate_shared_operational_intent.md) -### Attempt to modify activated Flight 1 in conflict test step +### Attempt to modify activated Flight 1 into conflict test step #### [Attempt to modify Flight 1](../../../../flight_planning/modify_activated_priority_conflict_flight_intent.md) The test driver attempts to modify Flight 1 so that it becomes in conflict with Flight 2. Both flights are activated at that point. However, because the conflict did not exist when the modification was initiated, it should be rejected per **[astm.f3548.v21.SCD0030](../../../../../requirements/astm/f3548/v21.md)**. +In addition, Flight 1 should not have been removed, because doing so would leave an aircraft in flight without any flight plan. -#### [Validate Flight 1 not modified](../../validate_shared_operational_intent.md) -Because the modification attempt was invalid, either Flight 1 should not have been modified (because the USS kept the -original accepted request), or it should have been removed (because the USS rejected the replacement plan provided). +#### [Validate Flight 1 not modified](../../validate_not_shared_operational_intent.md) +Because the modification attempt was invalid, Flight 1 should not have been modified. ## Cleanup + ### ⚠️ Successful flight deletion check **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../../requirements/interuss/automated_testing/flight_planning.md)** 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 acfbfff6be..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,17 +15,17 @@ from monitoring.monitorlib.clients.flight_planning.planning import ( PlanningActivityResult, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest -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_validation import ( ExpectedFlightIntent, - validate_flight_intent_templates, ) from monitoring.uss_qualifier.resources.flight_planning.flight_planners import ( FlightPlannerResource, ) +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, ) @@ -41,18 +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): - times: dict[TimeDuringTest, Time] - +class ConflictHigherPriority(PlanningSequenceScenario, NotificationChecker): flight1_id: str | None = None flight1_planned: FlightInfoTemplate flight1m_planned: FlightInfoTemplate @@ -65,10 +58,6 @@ class ConflictHigherPriority(TestScenario, NotificationChecker): flight2_activated: FlightInfoTemplate flight2m_activated: FlightInfoTemplate - tested_uss: FlightPlannerClient - control_uss: FlightPlannerClient - dss: DSSInstance - def __init__( self, flight_intents: FlightIntentsResource, @@ -76,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", @@ -106,6 +89,7 @@ def __init__( "flight1m_planned", "Flight 1m", must_conflict_with=["Flight 2"], + must_not_conflict_with=["Flight 2m"], usage_state=AirspaceUsageState.Planned, uas_state=UasState.Nominal, ), @@ -113,6 +97,7 @@ def __init__( "flight1m_activated", "Flight 1m", must_conflict_with=["Flight 2"], + must_not_conflict_with=["Flight 2m"], usage_state=AirspaceUsageState.InUse, uas_state=UasState.Nominal, ), @@ -150,38 +135,16 @@ def __init__( ), ] - templates = flight_intents.get_flight_intents() - try: - validate_flight_intent_templates(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]) - - def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - self.times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - return flight_template.resolve(self.times) - - def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - - 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}", + super().__init__( + flight_intents=flight_intents, + expected_flight_intents=expected_flight_intents, + tested_uss=tested_uss, + control_uss=control_uss, + dss=dss, + scopes=scopes, ) + 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() @@ -207,14 +170,12 @@ def run(self, context: ExecutionContext): ) = self._modify_activated_flight_conflict_preexisting(flight_1_oi_ref) self.end_test_case() - self.begin_test_case("Attempt to modify activated flight in conflict") + self.begin_test_case("Attempt to modify activated flight into conflict") self._attempt_modify_activated_flight_conflict( flight_1_intent, flight_1_oi_ref, flight_2_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) @@ -225,11 +186,13 @@ def _attempt_plan_flight_conflict(self): self.dss, flight2_planned, ) as validator: - _, self.flight2_id = plan_flight( + _, self.flight2_id, as_planned = plan_flight( self, self.control_uss, flight2_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2_planned = as_planned validator.expect_shared(flight2_planned) self.end_test_step() @@ -267,15 +230,21 @@ def _attempt_modify_planned_flight_conflict( self.dss, flight1_planned, ) as validator: - _, self.flight1_id = plan_flight( + _, self.flight1_id, as_planned = plan_flight( self, self.tested_uss, flight1_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight1_planned = as_planned flight_1_oi_ref = validator.expect_shared(flight1_planned) self.end_test_step() self.begin_test_step("Plan Flight 2") + preexisting_notifications = self._get_preexisting_notifications( + [self.control_uss, self.tested_uss] + ) flight2_planned = self.resolve_flight(self.flight2_planned) with OpIntentValidator( @@ -285,21 +254,25 @@ def _attempt_modify_planned_flight_conflict( flight2_planned, ) as validator: earliest_creation_time = arrow.utcnow().datetime - _, self.flight2_id = plan_flight( + _, self.flight2_id, as_planned = plan_flight( self, self.control_uss, flight2_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight2_planned = as_planned latest_creation_time = arrow.utcnow().datetime validator.expect_shared(flight2_planned) self.end_test_step() self.begin_test_step("Check for conflict notifications") self._check_for_user_notifications( - self.control_uss, - self.tested_uss, - earliest_creation_time, - latest_creation_time, + causing_conflict=self.control_uss, + observing_conflict=self.tested_uss, + preexisting_notifications=preexisting_notifications, + earliest_action_time=earliest_creation_time, + latest_action_time=latest_creation_time, ) self.end_test_step() @@ -372,12 +345,15 @@ def _modify_activated_flight_conflict_preexisting( flight1_activated, flight_1_oi_ref, ) as validator: - activate_flight( + _, _, as_planned = activate_flight( self, self.tested_uss, flight1_activated, self.flight1_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + assert as_planned is not None + flight1_activated = as_planned flight_1_oi_ref = validator.expect_shared(flight1_activated) self.end_test_step() @@ -390,15 +366,20 @@ def _modify_activated_flight_conflict_preexisting( self.dss, flight2_planned, ) as validator: - _, self.flight2_id = plan_flight( + _, self.flight2_id, as_planned = plan_flight( self, self.control_uss, flight2_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2_planned = as_planned flight_2_oi_ref = validator.expect_shared(flight2_planned) self.end_test_step() self.begin_test_step("Activate Flight 2") + preexisting_notifications = self._get_preexisting_notifications( + [self.control_uss, self.tested_uss] + ) flight2_activated = self.resolve_flight(self.flight2_activated) with OpIntentValidator( @@ -409,22 +390,25 @@ def _modify_activated_flight_conflict_preexisting( flight_2_oi_ref, ) as validator: earliest_activation_time = arrow.utcnow().datetime - activate_flight( + _, _, as_planned = activate_flight( self, self.control_uss, flight2_activated, self.flight2_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2_activated = as_planned latest_activation_time = arrow.utcnow().datetime flight_2_oi_ref = validator.expect_shared(flight2_activated) self.end_test_step() self.begin_test_step("Check for conflict notifications") self._check_for_user_notifications( - self.control_uss, - self.tested_uss, - earliest_activation_time, - latest_activation_time, + causing_conflict=self.control_uss, + observing_conflict=self.tested_uss, + preexisting_notifications=preexisting_notifications, + earliest_action_time=earliest_activation_time, + latest_action_time=latest_activation_time, ) self.end_test_step() @@ -440,13 +424,15 @@ def _modify_activated_flight_conflict_preexisting( [flight1_activated, flight1m_activated], flight_1_oi_ref, ) as validator: - resp = modify_activated_flight( + resp, as_planned = modify_activated_flight( self, self.tested_uss, flight1m_activated, self.flight1_id, preexisting_conflict=True, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight1m_activated = as_planned if resp.activity_result == PlanningActivityResult.Completed: flight_1_oi_ref = validator.expect_shared(flight1m_activated) @@ -475,16 +461,26 @@ def _attempt_modify_activated_flight_conflict( flight2m_activated, flight_2_oi_ref, ) as validator: - modify_activated_flight( + resp, as_planned = modify_activated_flight( self, self.control_uss, flight2m_activated, self.flight2_id, ) - validator.expect_shared(flight2m_activated) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight2m_activated = as_planned + if resp.activity_result == PlanningActivityResult.Completed: + validator.expect_shared(flight2m_activated) self.end_test_step() - self.begin_test_step("Attempt to modify activated Flight 1 in conflict") + if resp.activity_result == PlanningActivityResult.NotSupported: + self.record_note( + "conflict_higher_priority_skip_step", + f"Skip next step since USS {self.control_uss} did not modify flight 2.", + ) + return + + self.begin_test_step("Attempt to modify activated Flight 1 into conflict") flight1c_activated = self.resolve_flight(self.flight1c_activated) with OpIntentValidator( @@ -500,13 +496,5 @@ def _attempt_modify_activated_flight_conflict( flight1c_activated, self.flight1_id, ) - validator.expect_shared( - flight_1_intent, - skip_if_not_found=True, - ) + 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..4222170b49 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py @@ -0,0 +1,129 @@ +from abc import ABC, abstractmethod + +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): + """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, + 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({k.value: 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}" + ) + + 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.""" + 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() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.md b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.md index d4631e6c04..52bf798414 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.md @@ -1,72 +1,7 @@ # Solo happy path test scenario ## Description -This scenario performs a simple flight planning sequence with a single USS and no interactions with other USSs. It -verifies that basic planning functionality for the USS works properly. -It assumes that the area used in the scenario is already clear of any pre-existing flights (using, for instance, -PrepareFlightPlanners scenario). +This test scenario is deprecated and removed since monitoring v0.26.0. ## Resources -### flight_intents -The FlightIntentsResource must provide the following flight intents: - - - - - - - - - - - - - - - - - - - - -
    Flight intent IDFlight namePriorityAirspace usage stateUAS state
    flight1_plannedFlight 1Any (but all the same)PlannedNominal
    flight1_activatedInUse
    - -Because the scenario involves activation of intents, the start times of all activated intents must be during the time -the test scenario is executed (not before). Additionally, their end times must leave sufficient time for the execution -of the test scenario. - -### tested_uss -FlightPlannerResource that is under test and will manage Flight 1 and its variants. - -### 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. - -## Solo happy path test case - -### Plan Flight 1 test step - -#### [Plan Flight 1](../../../flight_planning/plan_flight_intent.md) -Flight 1 should be successfully planned by the tested USS. - -#### [Validate Flight 1 sharing](../validate_shared_operational_intent.md) - -### Activate Flight 1 test step - -#### [Activate Flight 1](../../../flight_planning/activate_flight_intent.md) -Flight 1 should be successfully activated by the tested USS. - -#### [Validate Flight 1 sharing](../validate_shared_operational_intent.md) - -### [Delete Flight 1 test step](../../../flight_planning/delete_flight_intent.md) -Flight 1 should be sucessfully removed from the system by the tested USS. - -## Cleanup -### ⚠️ Successful flight deletion check -**[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../requirements/interuss/automated_testing/flight_planning.md)** diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.py index 2f53b4ae5e..eca03de94c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/solo_happy_path.py @@ -1,98 +1,10 @@ -import arrow -from uas_standards.astm.f3548.v21.constants import Scope +from deprecation import deprecated -from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient -from monitoring.monitorlib.clients.flight_planning.flight_info import ( - AirspaceUsageState, - UasState, -) -from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( - FlightInfoTemplate, -) -from monitoring.monitorlib.temporal import Time, TimeDuringTest -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_validation import ( - ExpectedFlightIntent, - validate_flight_intent_templates, -) -from monitoring.uss_qualifier.resources.flight_planning.flight_planners import ( - FlightPlannerResource, -) -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 SoloHappyPath(TestScenario): - times: dict[TimeDuringTest, Time] - - flight1_id: str | None = None - flight1_planned: FlightInfoTemplate - flight1_activated: FlightInfoTemplate - - tested_uss: FlightPlannerClient - dss: DSSInstance - - def __init__( - self, - tested_uss: FlightPlannerResource, - dss: DSSInstanceResource, - flight_intents: FlightIntentsResource | None = None, - ): - super().__init__() - self.tested_uss = tested_uss.client - - scopes = { - Scope.StrategicCoordination: "search for operational intent references to verify outcomes of planning activities and retrieve operational intent details" - } - - self.dss = dss.get_instance(scopes) - - expected_flight_intents = [ - ExpectedFlightIntent( - "flight1_planned", - "Flight", - usage_state=AirspaceUsageState.Planned, - uas_state=UasState.Nominal, - ), - ExpectedFlightIntent( - "flight1_activated", - "Flight", - usage_state=AirspaceUsageState.InUse, - uas_state=UasState.Nominal, - ), - ] - - templates = flight_intents.get_flight_intents() - try: - self._intents_extent = validate_flight_intent_templates( - 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]) - - def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - + @deprecated(deprecated_in="0.26.0") + def run(self, context): self.begin_test_scenario(context) - - # TODO(#751): Implement scenario - self.end_test_scenario() - - def cleanup(self): - self.begin_cleanup() - cleanup_flights(self, (self.tested_uss,)) - self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/notifications_to_operator/notification_checker.py b/monitoring/uss_qualifier/scenarios/astm/utm/notifications_to_operator/notification_checker.py index c8058a4eab..e657873d5e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/notifications_to_operator/notification_checker.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/notifications_to_operator/notification_checker.py @@ -5,6 +5,7 @@ import arrow from uas_standards.astm.f3548.v21.constants import ( ConflictingOIMaxUserNotificationTimeSeconds, + TimeSyncMaxDifferentialSeconds, ) from monitoring.monitorlib.clients.flight_planning.client import ( @@ -14,11 +15,11 @@ Conflict, UserNotification, ) -from monitoring.monitorlib.delay import sleep from monitoring.monitorlib.fetch import Query from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario +NOTIFICATIONS_MAX_CLOCK_SKEW = timedelta(seconds=TimeSyncMaxDifferentialSeconds) SCD0090_NOTE_PREFIX = "scd0090_notification" SCD0095_NOTE_PREFIX = "scd0095_notification" NOTIFICATION_NOTE_FORMAT = "{participant_id} notification latency: >{latency}s" @@ -35,12 +36,58 @@ class Notifications: class NotificationChecker(GenericTestScenario, ABC): """Helper class to do notification checks""" + def _get_preexisting_notifications( + self, clients: list[FlightPlannerClient] + ) -> dict[ParticipantID, Notifications]: + notifications = {} + start_point = arrow.utcnow().datetime - NOTIFICATIONS_MAX_CLOCK_SKEW + for client in clients: + resp, query = client.get_user_notifications(after=start_point) + self.record_query(query) + with self.check( + "Retrieve pre-existing notifications", client.participant_id + ) as check: + if not resp or "user_notifications" not in resp: + notifications[client.participant_id] = Notifications( + notifications=None, query=query + ) + check.record_failed( + summary="No notifications returned", + details=f"{client.participant_id} didn't return a list of notifications when querying for pre-existing notifications", + query_timestamps=[query.request.timestamp], + ) + continue + + if any( + [ + notification.observed_at.datetime + > arrow.now() + NOTIFICATIONS_MAX_CLOCK_SKEW + for notification in resp.user_notifications + ] + ): + notifications[client.participant_id] = Notifications( + notifications=None, query=query + ) + check.record_failed( + summary="Error while trying to retrieve notifications", + details=f"Response from {client.participant_id} returned notifications in the future.", + query_timestamps=[query.request.timestamp], + ) + continue + + notifications[client.participant_id] = Notifications( + notifications=resp.user_notifications, query=query + ) + return notifications + def _get_notifications( self, clients: list[FlightPlannerClient], start_point: datetime, deadline: datetime, + preexisting_notifications: dict[ParticipantID, Notifications], ) -> dict[ParticipantID, Notifications]: + start_point -= NOTIFICATIONS_MAX_CLOCK_SKEW notifications = {} most_recent_check = arrow.utcnow().datetime while most_recent_check < deadline: @@ -48,6 +95,14 @@ def _get_notifications( if client.participant_id in notifications: # We've already found notifications for this participant continue + if ( + client.participant_id not in preexisting_notifications + or preexisting_notifications[client.participant_id].notifications + is None + ): + # We weren't able to retrieve the "before" notifications, so don't attempt to retrieve the "after" notifications + notifications[client.participant_id] = None + continue resp, query = client.get_user_notifications(after=start_point) self.record_query(query) most_recent_check = query.response.reported.datetime @@ -67,11 +122,36 @@ def _get_notifications( ) continue + if any( + [ + notification.observed_at.datetime + > arrow.now() + NOTIFICATIONS_MAX_CLOCK_SKEW + for notification in resp.user_notifications + ] + ): + notifications[client.participant_id] = Notifications( + notifications=None, query=query + ) + check.record_failed( + summary="Error while trying to retrieve notifications", + details=f"Response from {client.participant_id} returned notifications in the future.", + query_timestamps=[query.request.timestamp], + ) + continue + # If there was at least one qualifying notification, use the response obtained for this participant + previously_observed = { + n.observed_at + for n in preexisting_notifications[ + client.participant_id + ].notifications + or [] + } qualifying_notifications = [ - notification - for notification in resp.user_notifications - if notification.conflicts in _ACCEPTABLE_CONFLICTS + n + for n in resp.user_notifications + if n.conflicts in _ACCEPTABLE_CONFLICTS + and n.observed_at not in previously_observed ] if qualifying_notifications: notifications[client.participant_id] = Notifications( @@ -84,27 +164,29 @@ def _get_notifications( if client.participant_id not in notifications ] if len(remaining_participants) > 0: - sleep( + self.sleep( 2, f"user notifications have not yet appeared in {', '.join(remaining_participants)}", ) else: break - return notifications + return {k: v for k, v in notifications.items() if v is not None} def _check_for_user_notifications( self, causing_conflict: FlightPlannerClient, observing_conflict: FlightPlannerClient, + preexisting_notifications: dict[ParticipantID, Notifications], earliest_action_time: datetime, latest_action_time: datetime, ): new_notifications = self._get_notifications( - [causing_conflict, observing_conflict], - earliest_action_time, - latest_action_time + clients=[causing_conflict, observing_conflict], + start_point=earliest_action_time, + deadline=latest_action_time + timedelta(seconds=ConflictingOIMaxUserNotificationTimeSeconds), + preexisting_notifications=preexisting_notifications, ) def _latency_of(notifications: list[UserNotification]) -> str: @@ -130,7 +212,10 @@ def _note_for(client: FlightPlannerClient) -> str: ) def _maybe_record_note(prefix: str, client: FlightPlannerClient) -> None: - if new_notifications[client.participant_id].notifications is not None: + if ( + client.participant_id in new_notifications + and new_notifications[client.participant_id].notifications is not None + ): self.record_note(prefix, _note_for(client)) _maybe_record_note(SCD0090_NOTE_PREFIX, causing_conflict) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md index 005f50a9cf..ab6752a77f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.md @@ -71,24 +71,37 @@ However, since: - the virtual USS is declared as down at the DSS, - it does not respond for operational intent details, and - the conflicting operational intent is in the 'Accepted' state, -The tested USS should evaluate the conflicting operational intent as having the lowest bound priority status, i.e. a priority strictly lower than the lowest priority allowed by the local regulation. + +the tested USS should evaluate the conflicting operational intent as having the lowest bound priority status, i.e. a priority strictly lower than the lowest priority allowed by the local regulation. As such, the tested USS may either: - Successfully plan Flight 1 over the conflicting operational intent, or - Decide to be more conservative and reject the planning of Flight 1. #### 🛑 Successful planning check -All flight intent data provided is correct and the USS should have either successfully planned Flight 1 per **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)**, -or rejected properly the planning if it decided to be more conservative with such conflicts. -If the USS indicates that the injection attempt failed, this check will fail. +All flight intent data provided is correct and **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)** allows the USS to plan an operational +intent in conflict with the pre-existing operational intent managed by a down USS by applying the lowest-bound priority +to that operational intent. If the USS indicates that the injection attempt failed, this check will fail. -Do take note that if the USS rejects the planning, this check will not fail, but the following *Rejected planning check* -will. Refer to this check for more information. +If the USS rejects the requested flight, this check will not fail, but the following *Rejected planning check* will. +Refer to this check for more information. #### ℹ️ Rejected planning check -All flight intent data provided is correct and the USS should have either successfully planned Flight 1 or rejected -properly the planning if it decided to be more conservative with such conflicts. -If the USS rejects the planning, this check will produce a low severity finding per **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)**. +ASTM F3548-21 does not require USSs to accept flight requests in all circumstances not prohibited by F3548-21. USSs may +(generally) apply any number of additional rules to determine whether or not a flight planning action is accepted by the +USS. For instance, a USS may say, "even though we're allowed to plan over unactivated op intents managed by down USSs, +we choose not to in order to avoid the small risk the USS may not realize it is down and start that flight any way." + +For this reason, a USS may reject the planning request above while still complying with SCD0005 by applying the correct +priority to the pre-existing operational intent. Therefore, a rejection of the planning request does not indicate +non-compliance with SCD0005. However, because InterUSS uses successful planning as a means to measure whether a USS is +in compliance with SCD0005, rejecting the planning attempt means that the primary mechanism for inferring compliance +with **[astm.f3548.v21.SCD0005](../../../../requirements/astm/f3548/v21.md)** is not available. In this case, this check will produce a low-severity finding +to note InterUSS's inability to use this primary mechanism for SCD0005 compliance verification. + +#### 🛑 Injection fidelity check + +The requested flight should have been planned essentially as requested. The system may adapt requested parameters as necessary, but may not change the test-critical attributes of the flight when fulfilling the planning request per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../../requirements/interuss/automated_testing/flight_planning.md)**. #### 🛑 Failure check All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS @@ -102,13 +115,12 @@ If the planning was accepted, Flight 1 should have been shared. If the planning was rejected, Flight 1 should not have been shared, thus should not exist. ## Cleanup -### 🛑 Availability of virtual USS restored check -**[astm.f3548.v21.DSS0100,1](../../../../requirements/astm/f3548/v21.md)** +### [Restore virtual USS availability test step](../set_uss_available_during_cleanup.md) -### 🛑 Successful flight deletion check +### ⚠️ Successful flight deletion check Delete flights injected at USS through the flight planning interface. **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../requirements/interuss/automated_testing/flight_planning.md)** -### 🛑 Successful operational intents cleanup check +### ⚠️ Successful operational intents cleanup check Delete operational intents created at DSS by virtual USS. If the search for operational intent references or their deletion fail, this check fails per **[astm.f3548.v21.DSS0005,2](../../../../requirements/astm/f3548/v21.md)** or **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**, respectively. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py index 23f03c8907..91bbe8fda3 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss.py @@ -1,8 +1,6 @@ -import arrow from uas_standards.astm.f3548.v21.api import ( OperationalIntentReference, OperationalIntentState, - UssAvailabilityState, ) from uas_standards.astm.f3548.v21.constants import Scope @@ -20,13 +18,17 @@ PlanningActivityResult, ) from monitoring.monitorlib.fetch import QueryError -from monitoring.monitorlib.temporal import Time, TimeDuringTest +from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection from monitoring.monitorlib.testing import make_fake_url 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 ( @@ -44,20 +46,24 @@ cleanup_flights, submit_flight, ) -from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.scenarios.scenario import ScenarioLogicError, TestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext class DownUSS(TestScenario): - times: dict[TimeDuringTest, Time] - flight1_planned: FlightInfoTemplate uss_qualifier_sub: str + scenario_execution_max_extents: Volume4D | None = None + """Actual bounding extent of the flight intents created by the USS qualifier acting as a virtual USS during the scenario execution.""" + + estimated_max_extents: Volume4D | None = None + """Estimated bounding extent of the flight intents created by the USS qualifier acting as a virtual USS during the scenario execution.""" tested_uss: FlightPlannerClient dss_resource: DSSInstanceResource dss: DSSInstance + flight_intents_templates: dict[FlightIntentID, FlightInfoTemplate] def __init__( self, @@ -70,10 +76,10 @@ def __init__( self.tested_uss = tested_uss.client self.dss = dss.get_instance(self._dss_req_scopes) - templates = flight_intents.get_flight_intents() + self.flight_intents_templates = flight_intents.get_flight_intents() try: - self._intents_extent = validate_flight_intent_templates( - templates, self._expected_flight_intents + validate_flight_intent_templates( + self.flight_intents_templates, self._expected_flight_intents ) except ValueError as e: raise ValueError( @@ -81,7 +87,7 @@ def __init__( ) for efi in self._expected_flight_intents: - setattr(self, efi.intent_id, templates[efi.intent_id]) + setattr(self, efi.intent_id, self.flight_intents_templates[efi.intent_id]) @property def _dss_req_scopes(self) -> dict[str, str]: @@ -102,15 +108,17 @@ def _expected_flight_intents(self) -> list[ExpectedFlightIntent]: ] def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - self.times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - return flight_template.resolve(self.times) + flight = flight_template.resolve(self.time_context.evaluate_now()) - def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } + extents = Volume4DCollection([]) + if self.scenario_execution_max_extents is not None: + extents.append(self.scenario_execution_max_extents) + extents.extend(flight.basic_information.area) + self.scenario_execution_max_extents = extents.bounding_volume + + return flight + def run(self, context: ExecutionContext): self.begin_test_scenario(context) self.record_note( @@ -131,11 +139,15 @@ def run(self, context: ExecutionContext): self.end_test_scenario() def _setup(self): + estimated_max_extents = estimate_scenario_execution_max_extents( + self.time_context, self.flight_intents_templates + ) + self.begin_test_step("Resolve USS ID of virtual USS") with self.check("Successful dummy query", [self.dss.participant_id]) as check: try: _, dummy_query = self.dss.find_op_intent( - self._intents_extent.to_f3548v21() + estimated_max_extents.to_f3548v21() ) self.record_query(dummy_query) except QueryError as e: @@ -158,15 +170,15 @@ def _setup(self): self.end_test_step() self.begin_test_step("Clear operational intents created by virtual USS") - self._clear_op_intents() + self._clear_op_intents(estimated_max_extents) self.end_test_step() self.begin_test_step("Verify area is clear") validate_clear_area( self, self.dss, - [self._intents_extent], - ignore_self=True, + [estimated_max_extents], + ignore_self=False, ) self.end_test_step() @@ -246,7 +258,7 @@ def _plan_flight_conflict_planned(self): self.dss, flight1_planned, ) as validator: - resp, flight_id = submit_flight( + resp, flight_id, as_planned = submit_flight( scenario=self, success_check="Successful planning", expected_results={ @@ -258,49 +270,56 @@ def _plan_flight_conflict_planned(self): flight_planner=self.tested_uss, flight_info=flight1_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight1_planned = as_planned - if resp.activity_result == PlanningActivityResult.Completed: - validator.expect_shared(flight1_planned) - elif resp.activity_result == PlanningActivityResult.Rejected: - with self.check( - "Rejected planning", [self.tested_uss.participant_id] - ) as check: + with self.check( + "Rejected planning", [self.tested_uss.participant_id] + ) as check: + if resp.activity_result == PlanningActivityResult.Rejected: msg = f"{self.tested_uss.participant_id} indicated {resp.activity_result}" if "notes" in resp and resp.notes: msg += f' with notes "{resp.notes}"' else: msg += " with no notes" check.record_failed( - summary="Warning (not a failure): planning got rejected, USS may have been more conservative", + summary="Warning (not a failure): flight was rejected, USS may have been more conservative", details=msg, ) + + if resp.activity_result == PlanningActivityResult.Completed: + validator.expect_shared(flight1_planned) + elif resp.activity_result == PlanningActivityResult.Rejected: validator.expect_not_shared() + else: + raise ScenarioLogicError( + f"OpIntentValidator should have ensured that resp.activity_result was Completed or Rejected, but instead it was {resp.activity_result}" + ) + self.end_test_step() - def _clear_op_intents(self): + def _clear_op_intents(self, area: Volume4D): with self.check( "Successful operational intents cleanup", [self.dss.participant_id] ) as check: + oi_refs = [] + try: - oi_refs, find_query = self.dss.find_op_intent( - self._intents_extent.to_f3548v21() - ) + oi_refs, find_query = self.dss.find_op_intent(area.to_f3548v21()) self.record_query(find_query) except QueryError as e: self.record_queries(e.queries) find_query = e.queries[0] check.record_failed( - summary=f"Failed to query operational intent references from DSS in {self._intents_extent} for cleanup", + summary=f"Failed to query operational intent references from DSS in {area} for cleanup", details=f"DSS responded code {find_query.status_code}; {e}", query_timestamps=[find_query.request.timestamp], ) for oi_ref in oi_refs: - if oi_ref.manager == self.uss_qualifier_sub: + if oi_ref.manager == self.uss_qualifier_sub and (ovn := oi_ref.ovn): try: - del_oi, _, del_query = self.dss.delete_op_intent( - oi_ref.id, oi_ref.ovn - ) + _, _, del_query = self.dss.delete_op_intent(oi_ref.id, ovn) self.record_query(del_query) except QueryError as e: self.record_queries(e.queries) @@ -313,26 +332,9 @@ def _clear_op_intents(self): def cleanup(self): self.begin_cleanup() - - with self.check( - "Availability of virtual USS restored", [self.dss.participant_id] - ) as check: - try: - availability_version, avail_query = self.dss.set_uss_availability( - self.uss_qualifier_sub, - UssAvailabilityState.Normal, - ) - self.record_query(avail_query) - except QueryError as e: - self.record_queries(e.queries) - avail_query = e.queries[0] - check.record_failed( - summary=f"Availability of USS {self.uss_qualifier_sub} could not be set to available", - details=f"DSS responded code {avail_query.status_code}; {e}", - query_timestamps=[avail_query.request.timestamp], - ) - + set_uss_available(self, self.dss, self.uss_qualifier_sub) cleanup_flights(self, [self.tested_uss]) - self._clear_op_intents() + if self.scenario_execution_max_extents: + self._clear_op_intents(self.scenario_execution_max_extents) self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.md b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.md index e5b76d377e..2170c11996 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.md @@ -173,13 +173,12 @@ to reject or accept Flight 2. If the USS indicates that the injection attempt fa ## Cleanup -### 🛑 Availability of virtual USS restored check -**[astm.f3548.v21.DSS0100,1](../../../../requirements/astm/f3548/v21.md)** +### [Restore virtual USS availability test step](../set_uss_available_during_cleanup.md) -### 🛑 Successful flight deletion check +### ⚠️ Successful flight deletion check Delete flights injected at USS through the flight planning interface. **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../../requirements/interuss/automated_testing/flight_planning.md)** -### 🛑 Successful operational intents cleanup check +### ⚠️ Successful operational intents cleanup check Delete operational intents created at DSS by virtual USS. If the search for operational intent references or their deletion fail, this check fails per **[astm.f3548.v21.DSS0005,2](../../../../requirements/astm/f3548/v21.md)** or **[astm.f3548.v21.DSS0005,1](../../../../requirements/astm/f3548/v21.md)**, respectively. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py index 3c81c5c66b..2aa36f1a79 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py @@ -1,4 +1,3 @@ -import arrow from uas_standards.astm.f3548.v21.api import ( OperationalIntentReference, OperationalIntentState, @@ -16,7 +15,6 @@ FlightPlanStatus, PlanningActivityResult, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.uss_qualifier.resources.flight_planning.flight_intent_validation import ( ExpectedFlightIntent, ) @@ -63,11 +61,6 @@ def _expected_flight_intents(self) -> list[ExpectedFlightIntent]: ] def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - self.begin_test_scenario(context) self.record_note( diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_available_during_cleanup.md b/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_available_during_cleanup.md new file mode 100644 index 0000000000..01035984f1 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/set_uss_available_during_cleanup.md @@ -0,0 +1,9 @@ +# Set USS availability to 'Available' test step fragment + +This step sets the USS availability to 'Available' at the DSS. + +See `set_uss_available` in [test_steps.py](test_steps.py). + +## [Availability can be read](./dss/fragments/availability/read_during_cleanup.md) + +## [Availability can be updated](./dss/fragments/availability/update_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py b/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py index 1bd7a2ce04..9ce0ac0ca6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py @@ -11,7 +11,6 @@ from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( FlightInfoTemplate, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest 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 @@ -116,10 +115,6 @@ def __init__( setattr(self, efi.intent_id, templates[efi.intent_id]) def run(self, context: ExecutionContext): - times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } self.begin_test_scenario(context) self.record_note( "Tested USS", @@ -132,25 +127,23 @@ def run(self, context: ExecutionContext): self.begin_test_case( "Activated operational intent receives notification of relevant intent" ) - self._receive_notification_successfully_when_activated_test_case(times) + self._receive_notification_successfully_when_activated_test_case() self.end_test_case() self.begin_test_case( "Modify Activated operational intent area and receive notification of relevant intent" ) - self._receive_notification_successfully_when_activated_modified_test_case(times) + self._receive_notification_successfully_when_activated_modified_test_case() self.end_test_case() self.end_test_scenario() - def _receive_notification_successfully_when_activated_test_case( - self, times: dict[TimeDuringTest, Time] - ): - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) + def _receive_notification_successfully_when_activated_test_case(self): + self.time_context.evaluate_now() - flight_1_planned = self.flight_1_planned.resolve(times) - flight_1_activated = self.flight_1_activated.resolve(times) - flight_2_planned = self.flight_2_planned.resolve(times) + flight_1_planned = self.flight_1_planned.resolve(self.time_context) + flight_1_activated = self.flight_1_activated.resolve(self.time_context) + flight_2_planned = self.flight_2_planned.resolve(self.time_context) resolved_extents = flight_info.extents_of( [flight_1_planned, flight_1_activated, flight_2_planned] @@ -163,11 +156,13 @@ def _receive_notification_successfully_when_activated_test_case( self.dss, resolved_extents, ) as validator: - _, self.flight_1_id = plan_flight( + _, self.flight_1_id, as_planned = plan_flight( self, self.tested_uss_client, flight_1_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_1_planned = as_planned self.flight_1_oi_ref = validator.expect_shared(flight_1_planned) with OpIntentValidator( @@ -177,12 +172,14 @@ def _receive_notification_successfully_when_activated_test_case( resolved_extents, self.flight_1_oi_ref, ) as validator: - _, self.flight_1_id = activate_flight( + _, self.flight_1_id, as_planned = activate_flight( self, self.tested_uss_client, flight_1_activated, self.flight_1_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_1_activated = as_planned self.flight_1_oi_ref = validator.expect_shared(flight_1_activated) self.end_test_step() @@ -195,11 +192,13 @@ def _receive_notification_successfully_when_activated_test_case( resolved_extents, ) as validator: flight_2_planning_time = arrow.utcnow().datetime - _, self.flight_2_id = plan_flight( + _, self.flight_2_id, as_planned = plan_flight( self, self.mock_uss_client, flight_2_planned, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_2_planned = as_planned self.flight_2_oi_ref = validator.expect_shared(flight_2_planned) self.end_test_step() @@ -217,11 +216,10 @@ def _receive_notification_successfully_when_activated_test_case( ) self.end_test_step() - def _receive_notification_successfully_when_activated_modified_test_case( - self, times: dict[TimeDuringTest, Time] - ): - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - flight_2_planned_modified = self.flight_2_planned_modified.resolve(times) + def _receive_notification_successfully_when_activated_modified_test_case(self): + flight_2_planned_modified = self.flight_2_planned_modified.resolve( + self.time_context.evaluate_now() + ) self.begin_test_step("Mock_uss modifies planned Flight 2") with OpIntentValidator( @@ -232,12 +230,14 @@ def _receive_notification_successfully_when_activated_modified_test_case( self.flight_2_oi_ref, ) as validator: flight_2_modif_time = arrow.utcnow().datetime - modify_planned_flight( + _, as_planned = modify_planned_flight( self, self.mock_uss_client, flight_2_planned_modified, self.flight_2_id, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed + flight_2_planned_modified = as_planned self.flight_2_oi_ref = validator.expect_shared(flight_2_planned_modified) self.end_test_step() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/test_steps/validate_notification_received.py b/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/test_steps/validate_notification_received.py index 2d7d10bf96..6765adec9a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/test_steps/validate_notification_received.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/subscription_notifications/test_steps/validate_notification_received.py @@ -47,7 +47,7 @@ def expect_tested_uss_receives_notification_from_mock_uss( with scenario.check( "Mock USS sends valid notification", mock_uss.participant_id ) as check: - interactions, query = wait_in_intervals(get_mock_uss_interactions)( + interactions, query = wait_in_intervals(get_mock_uss_interactions, scenario)( scenario, mock_uss, StringBasedDateTime(interactions_since_time), diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py index d5eace6db2..f3be6de011 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py @@ -1,11 +1,14 @@ from __future__ import annotations -from enum import Enum +import datetime +from dataclasses import dataclass +from enum import StrEnum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional, StringBasedDateTime from uas_standards.astm.f3548.v21.api import ( EntityID, GetOperationalIntentDetailsResponse, + OperationalIntent, OperationalIntentReference, OperationalIntentState, UssAvailabilityState, @@ -28,13 +31,26 @@ set_uss_availability, ) from monitoring.uss_qualifier.scenarios.astm.utm.evaluation import ( + errors_for_nonequivalent_op_intent_details, validate_op_intent_details, + validate_op_intent_reference, ) from monitoring.uss_qualifier.scenarios.scenario import ( + ScenarioDidNotStopError, + ScenarioLogicError, TestRunCannotContinueError, TestScenarioType, ) +NUMERIC_PRECISION_TIME = datetime.timedelta(milliseconds=1) +NUMERIC_PRECISION_DISTANCE = 0.001 # meters + + +@dataclass +class CachedOpIntent: + query_timestamp: StringBasedDateTime + op_intent: OperationalIntent + class OpIntentValidator: """ @@ -55,7 +71,7 @@ class OpIntentValidator: _after_oi_refs: list[OperationalIntentReference] _after_query: fetch.Query - _new_oi_ref: OperationalIntentReference | None = None + _new_oi_ref: Optional[OperationalIntentReference] = None def __init__( self, @@ -201,7 +217,7 @@ def expect_not_shared(self) -> None: def expect_shared( self, - flight_info: FlightInfo, + flight_info: FlightInfo | None, skip_if_not_found: bool = False, ) -> OperationalIntentReference | None: """Validate that operational intent information was correctly shared for a flight intent. @@ -215,6 +231,10 @@ def expect_shared( """ self._begin_step_fragment() oi_ref = self._operational_intent_shared_check(flight_info, skip_if_not_found) + if flight_info is None: + raise ScenarioLogicError( + "Scenario should have stopped with missing flight_info during self._operational_intent_shared_check" + ) if oi_ref is None: return None @@ -234,7 +254,7 @@ def expect_shared( def expect_shared_with_invalid_data( self, - flight_info: FlightInfo, + flight_info: FlightInfo | None, validation_failure_type: OpIntentValidationFailureType, invalid_fields: list | None = None, skip_if_not_found: bool = False, @@ -244,6 +264,7 @@ def expect_shared_with_invalid_data( This function implements the test step described in validate_sharing_operational_intent_but_with_invalid_interuss_data. + :param flight_info: the flight intent that was supposed to have been shared. :param skip_if_not_found: set to True to skip the execution of the checks if the operational intent was not found while it should have been modified. :param validation_failure_type: specific type of validation failure expected :param invalid_fields: Optional list of invalid fields to expect when validation_failure_type is OI_DATA_FORMAT @@ -296,12 +317,19 @@ def expect_shared_with_invalid_data( def _operational_intent_shared_check( self, - flight_intent: FlightInfo, + flight_intent: FlightInfo | None, skip_if_not_found: bool, ) -> OperationalIntentReference | None: with self._scenario.check( "Operational intent shared correctly", [self._flight_planner.participant_id] ) as check: + if flight_intent is None: + check.record_failed( + summary="Flight not eligible to be shared as planned", + details=f"USS {self._flight_planner.participant_id} was supposed to have planned a flight that would be shared with the DSS, but instead indicated that the flight planning activity did not result in a flight plan that would be shared with the DSS", + query_timestamps=[], # TODO: Identify the flight planning query that resulted in no flight info as planned + ) + raise ScenarioDidNotStopError(check) if self._orig_oi_ref is None: # We expect a new op intent to have been created. Exception made if skip_if_not_found=True: step is # skipped. @@ -406,6 +434,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], ) + raise ScenarioDidNotStopError(check) validation_failures = self._evaluate_op_intent_validation(oi_full_query) with self._scenario.check( @@ -430,6 +459,51 @@ 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( + "Operational intent details have not changed without publishing a new version to the DSS", + [self._flight_planner.participant_id], + ) as check: + cache_key = ( + f"full_op_intent:{oi_full.reference.id}:{oi_full.reference.version}" + ) + old_oi: CachedOpIntent | None = self._scenario.cache.get(cache_key) + if not old_oi: + self._scenario.cache[cache_key] = CachedOpIntent( + op_intent=oi_full, + query_timestamp=StringBasedDateTime( + oi_full_query.request.timestamp + ), + ) + else: + error_text = errors_for_nonequivalent_op_intent_details( + old_oi.op_intent, + oi_full, + ) + if error_text: + check.record_failed( + summary="Operational intent details have changed without the change being published to the DSS", + details=error_text, + query_timestamps=[ + old_oi.query_timestamp, + oi_full_query.request.timestamp, + ], + ) + with self._scenario.check( "Correct operational intent details", [self._flight_planner.participant_id] ) as check: @@ -445,6 +519,97 @@ def _check_op_intent_details( query_timestamps=[oi_full_query.request.timestamp], ) + with self._scenario.check( + "Operational intent details extents are contained within reference extents", + [self._flight_planner.participant_id], + ) as check: + all_volumes = Volume4DCollection.from_f3548v21( + oi_full.details.get("volumes", []) + + oi_full.details.get("off_nominal_volumes", []) + ) + + # Time start check + v_time_start = all_volumes.time_start + ref_time_start = oi_ref.get("time_start") + if not v_time_start: + if ref_time_start: + check.record_failed( + summary="Details volume starts before reference", + details="A volume in the operational intent details has no start time (infinite past), but the operational intent reference specifies a start time.", + query_timestamps=[oi_full_query.request.timestamp], + ) + elif ref_time_start: + if ( + v_time_start.datetime + < ref_time_start.value.datetime - NUMERIC_PRECISION_TIME + ): + check.record_failed( + summary="Details volume starts before reference", + details=f"A volume in the operational intent details starts at {v_time_start}, which is before the operational intent reference start time {ref_time_start.value.datetime}.", + query_timestamps=[oi_full_query.request.timestamp], + ) + + # Time end check + v_time_end = all_volumes.time_end + ref_time_end = oi_ref.get("time_end") + if not v_time_end: + if ref_time_end: + check.record_failed( + summary="Details volume ends after reference", + details="A volume in the operational intent details has no end time (infinite future), but the operational intent reference specifies an end time.", + query_timestamps=[oi_full_query.request.timestamp], + ) + elif ref_time_end: + if ( + v_time_end.datetime + > ref_time_end.value.datetime + NUMERIC_PRECISION_TIME + ): + check.record_failed( + summary="Details volume ends after reference", + details=f"A volume in the operational intent details ends at {v_time_end.datetime}, which is after the operational intent reference end time {ref_time_end.value.datetime}.", + query_timestamps=[oi_full_query.request.timestamp], + ) + + # Altitude check (if reference specifies altitude, which it typically doesn't in F3548, but implemented defensively just in case) + v_altitude_lower = all_volumes.altitude_lower + ref_altitude_lower = oi_ref.get("altitude_lower") + if not v_altitude_lower: + if ref_altitude_lower: + check.record_failed( + summary="Details volume lower altitude below reference", + details="A volume in the operational intent details has no lower altitude bound (infinite downward), but the operational intent reference specifies a lower altitude.", + query_timestamps=[oi_full_query.request.timestamp], + ) + elif ref_altitude_lower: + if ( + v_altitude_lower.value + < ref_altitude_lower.value - NUMERIC_PRECISION_DISTANCE + ): + check.record_failed( + summary="Details volume lower altitude below reference", + details=f"A volume in the operational intent details has lower altitude {v_altitude_lower.value}, which is below the operational intent reference lower altitude {ref_altitude_lower.value}.", + query_timestamps=[oi_full_query.request.timestamp], + ) + v_altitude_upper = all_volumes.altitude_upper + ref_altitude_upper = oi_ref.get("altitude_upper") + if not v_altitude_upper: + if ref_altitude_upper: + check.record_failed( + summary="Details volume upper altitude above reference", + details="A volume in the operational intent details has no upper altitude bound (infinite upward), but the operational intent reference specifies an upper altitude.", + query_timestamps=[oi_full_query.request.timestamp], + ) + elif ref_altitude_upper: + if ( + v_altitude_upper.value + > ref_altitude_upper.value + NUMERIC_PRECISION_DISTANCE + ): + check.record_failed( + summary="Details volume upper altitude above reference", + details=f"A volume in the operational intent details has upper altitude {v_altitude_upper.value}, which is above the operational intent reference upper altitude {ref_altitude_upper.value}.", + query_timestamps=[oi_full_query.request.timestamp], + ) + with self._scenario.check( "Off-nominal volumes", [self._flight_planner.participant_id] ) as check: @@ -613,7 +778,7 @@ def expected_fields_in_errors( return failure_found -class OpIntentValidationFailureType(str, Enum): +class OpIntentValidationFailureType(StrEnum): DataFormat = "DataFormat" """The operational intent did not validate against the canonical JSON Schema.""" @@ -627,10 +792,10 @@ class OpIntentValidationFailureType(str, Enum): class OpIntentValidationFailure(ImplicitDict): validation_failure_type: OpIntentValidationFailureType - error_text: str | None = None + error_text: Optional[str] = None """Any error_text returned after validation check""" - errors: list[schema_validation.ValidationError] | None = None + errors: Optional[list[schema_validation.ValidationError]] = None """Any errors returned after validation check""" def __hash__(self): 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..2fd099b463 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,10 +29,24 @@ 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. + +## 🛑 Operational intent details have not changed without publishing a new version to the DSS check + +If the operational intent details reported by the USS have changed without the USS having updated the operational intent reference in the DSS, this check will fail because the USS did not make the changes to the operational intent discoverable using the DSS, per **[astm.f3548.v21.USS0005](../../../requirements/astm/f3548/v21.md)**. Per **[astm.f3548.v21.USS0105,1](../../../requirements/astm/f3548/v21.md)** (definition of `version` field), a USS must update the operational intent reference in the DSS, even when no data fields of the reference have changed, to make changes to the operational intent discoverable using the DSS. + +To be clear, this check specifically targets cases where the USS changes details of the operational intent without a version change. + ## 🛑 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)**. +## 🛑 Operational intent details extents are contained within reference extents check + +If the 4D extents (start time, end time, and altitude if specified) of any of the operational intent detail volumes are not fully contained within the 4D extents of the operational intent reference, this check will fail per **[astm.f3548.v21.USS0105,1](../../../requirements/astm/f3548/v21.md)**. + ## ⚠️ Off-nominal volumes check **[astm.f3548.v21.OPIN0015](../../../requirements/astm/f3548/v21.md)** specifies that nominal operational intents (Accepted and Activated) must not include any off-nominal 4D volumes, so this check will fail if an Accepted or Activated operational intent includes off-nominal volumes. diff --git a/monitoring/uss_qualifier/scenarios/definitions.py b/monitoring/uss_qualifier/scenarios/definitions.py index 6dc3473d1b..6860570653 100644 --- a/monitoring/uss_qualifier/scenarios/definitions.py +++ b/monitoring/uss_qualifier/scenarios/definitions.py @@ -3,7 +3,12 @@ from monitoring.uss_qualifier.resources.definitions import ResourceID TestScenarioTypeName = str -"""This plain string represents a type of test scenario, expressed as a Python class name qualified relative to the `uss_qualifier` module""" +"""This plain string represents a type of test scenario, expressed as a Python class name qualified relative to the +`uss_qualifier` module. + +Note that equality between different TestScenarioTypeNames (whether they refer to the same type of test scenario) should +be determined via are_scenario_types_equal as multiple TestScenarioTypeNames may resolve to the same test scenario type. +""" class TestScenarioDeclaration(ImplicitDict): diff --git a/monitoring/uss_qualifier/scenarios/dev/noop.py b/monitoring/uss_qualifier/scenarios/dev/noop.py index 8681a368fd..13fbeff3b5 100644 --- a/monitoring/uss_qualifier/scenarios/dev/noop.py +++ b/monitoring/uss_qualifier/scenarios/dev/noop.py @@ -1,6 +1,5 @@ from datetime import UTC, datetime -from monitoring.monitorlib.delay import sleep from monitoring.uss_qualifier.resources.dev import NoOpResource from monitoring.uss_qualifier.scenarios.scenario import TestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext @@ -21,7 +20,7 @@ def run(self, context: ExecutionContext): f"Starting at {datetime.now(UTC).isoformat()}Z, sleeping for {self.sleep_secs}s...", ) - sleep(self.sleep_secs, "the no-op scenario sleeps for the specified time") + self.sleep(self.sleep_secs, "the no-op scenario sleeps for the specified time") self.record_note("End time", f"Ending at {datetime.now(UTC).isoformat()}Z.") diff --git a/monitoring/uss_qualifier/scenarios/documentation/definitions.py b/monitoring/uss_qualifier/scenarios/documentation/definitions.py index 799f5bea9c..b40a4ecb1d 100644 --- a/monitoring/uss_qualifier/scenarios/documentation/definitions.py +++ b/monitoring/uss_qualifier/scenarios/documentation/definitions.py @@ -1,4 +1,4 @@ -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.reports.report import RequirementID @@ -6,21 +6,21 @@ class TestCheckDocumentation(ImplicitDict): name: str - url: str | None = None + url: Optional[str] = None applicable_requirements: list[RequirementID] has_todo: bool - severity: Severity | None = None + severity: Optional[Severity] = None class TestStepDocumentation(ImplicitDict): name: str - url: str | None = None + url: Optional[str] = None checks: list[TestCheckDocumentation] class TestCaseDocumentation(ImplicitDict): name: str - url: str | None = None + url: Optional[str] = None steps: list[TestStepDocumentation] def get_step_by_name(self, step_name: str) -> TestStepDocumentation | None: @@ -32,11 +32,11 @@ def get_step_by_name(self, step_name: str) -> TestStepDocumentation | None: class TestScenarioDocumentation(ImplicitDict): name: str - url: str | None = None + url: Optional[str] = None local_path: str - resources: list[str] | None + resources: Optional[list[str]] cases: list[TestCaseDocumentation] - cleanup: TestStepDocumentation | None + cleanup: Optional[TestStepDocumentation] def get_case_by_name(self, case_name: str) -> TestCaseDocumentation | None: for case in self.cases: diff --git a/monitoring/uss_qualifier/scenarios/documentation/requirements.py b/monitoring/uss_qualifier/scenarios/documentation/requirements.py index 7744777dbb..3d635b0620 100644 --- a/monitoring/uss_qualifier/scenarios/documentation/requirements.py +++ b/monitoring/uss_qualifier/scenarios/documentation/requirements.py @@ -27,7 +27,7 @@ class ParticipantRequirementPerformance(ImplicitDict): - successes: list[JSONPath] + passes: list[JSONPath] """List of passed checks involving the requirement""" failures: list[JSONPath] @@ -70,11 +70,11 @@ def _add_check( for participant_id in participants: if participant_id not in requirement.participant_performance: requirement.participant_performance[participant_id] = ( - ParticipantRequirementPerformance(successes=[], failures=[]) + ParticipantRequirementPerformance(passes=[], failures=[]) ) performance = requirement.participant_performance[participant_id] if isinstance(check, PassedCheck): - performance.successes.append(path) + performance.passes.append(path) elif isinstance(check, FailedCheck): performance.failures.append(path) else: diff --git a/monitoring/uss_qualifier/scenarios/eurocae/ed318/__init__.py b/monitoring/uss_qualifier/scenarios/eurocae/ed318/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/monitoring/uss_qualifier/scenarios/eurocae/ed318/source_data_model.md b/monitoring/uss_qualifier/scenarios/eurocae/ed318/source_data_model.md new file mode 100644 index 0000000000..89efb5fb21 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/eurocae/ed318/source_data_model.md @@ -0,0 +1,23 @@ +# EUROCAE ED-318 UAS geographical zone model test scenario + +## Overview + +This scenario verifies that a JSON document complies with the ED-318 UAS Geographical Zone Model for Geo-Awareness purpose. + +## Resources + +### source_document + +The file or url of the document to be tested. + +## ED-318 data model compliance test case + +### Valid source test step + +#### 🛑 Valid JSON check + +The JSON file is properly formatted and can be read successfully. + +#### 🛑 Valid schema and values check + +The file respects the ED-318 schema and values are valid. diff --git a/monitoring/uss_qualifier/scenarios/eurocae/ed318/source_data_model.py b/monitoring/uss_qualifier/scenarios/eurocae/ed318/source_data_model.py new file mode 100644 index 0000000000..9c597556b3 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/eurocae/ed318/source_data_model.py @@ -0,0 +1,80 @@ +import json +import pathlib + +import referencing +from jsonschema import ValidationError, validate +from referencing.exceptions import NoSuchResource + +import monitoring +from monitoring.uss_qualifier.resources.eurocae.ed318.source_document import ( + SourceDocument, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.suites.suite import ExecutionContext + + +class SourceDataModelValidation(TestScenario): + source_document: SourceDocument + schema_registry: referencing.Registry = referencing.Registry() + schema: dict + + def __init__(self, source_document: SourceDocument): + super().__init__() + self.source_document = source_document + + # create a JSON schema registry for ED318 schemas + def retrieve_schema(uri: str) -> referencing.Resource: + # the $ref in the schemas are relative paths, this function allows resolving them + repo_root = pathlib.Path(str(monitoring.__file__)).parent.parent + schema_path = repo_root / "schemas" / "ed318" / "schema" / uri + if not schema_path.exists(): + raise NoSuchResource(ref=str(schema_path)) + with open(schema_path) as schema: + return referencing.Resource.from_contents(json.load(schema)) + + self.schema_registry = referencing.Registry(retrieve=retrieve_schema) + self.schema = self.schema_registry.get_or_retrieve( + "Schema_GeoZones.json" + ).value.contents + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + + self.record_note( + "Document", + f"Ready at {self.source_document.specification.url}", + ) + + self.begin_test_case("ED-318 data model compliance") + self.begin_test_step("Valid source") + + data = None + with self.check( + "Valid JSON", + [self.source_document.specification.url], + ) as check: + try: + data = json.loads(self.source_document.raw_document) + except json.decoder.JSONDecodeError as e: + check.record_failed( + summary="Unable to deserialize the document as JSON", + details=str(e), + ) + + if data: + with self.check( + "Valid schema and values", [self.source_document.specification.url] + ) as check: + try: + validate( + instance=data, schema=self.schema, registry=self.schema_registry + ) + except ValidationError as e: + check.record_failed( + summary="Invalid format error", + details=str(e), + ) + + self.end_test_step() + self.end_test_case() + self.end_test_scenario() diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/activate_flight_intent.md b/monitoring/uss_qualifier/scenarios/flight_planning/activate_flight_intent.md index 94772e5aca..35cd1a9e06 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/activate_flight_intent.md +++ b/monitoring/uss_qualifier/scenarios/flight_planning/activate_flight_intent.md @@ -11,3 +11,7 @@ All flight intent data provided is correct and valid and free of conflict in spa All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../requirements/interuss/automated_testing/flight_planning.md)**. + +## 🛑 Injection fidelity check + +The requested flight should have been activated essentially as requested. The system may adapt requested parameters as necessary, but may not change the test-critical attributes of the flight when fulfilling the planning request per **interuss.automated_testing.flight_planning.ExpectedBehavior**. diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/injection_evaluation.py b/monitoring/uss_qualifier/scenarios/flight_planning/injection_evaluation.py new file mode 100644 index 0000000000..28f125e376 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/flight_planning/injection_evaluation.py @@ -0,0 +1,197 @@ +from collections.abc import Callable, Iterable +from numbers import Number +from types import NoneType +from typing import Any + +import arrow +import bc_jsonpath_ng +from implicitdict import StringBasedDateTime + +from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo +from monitoring.monitorlib.dicts import JSONAddress, JSONPath +from monitoring.uss_qualifier.scenarios.scenario import PendingCheck + +CompatibilityEvaluator = Callable[[Any, Any, JSONAddress, PendingCheck], None] +"""Evaluates whether an as-planned value is compatible with an as-requested value. + Arguments: + * as_requested: The value, as requested. + * as_planned: The value, as planned. + * address: Location of the value within the content structure being evalated. + * check: Pending check for whether the planned value is compatible with the requested value. +""" + + +def values_exactly_equal( + as_requested: str | Number | StringBasedDateTime | None, + as_planned: str | Number | StringBasedDateTime | None, + address: JSONAddress, + check: PendingCheck, +): + """Implements CompatibilityEvaluator pattern, requiring requested and planned values to match exactly.""" + + for dtype in (StringBasedDateTime, Number, str, NoneType): + if isinstance(as_requested, dtype): + if not isinstance(as_planned, dtype): + check.record_failed( + summary=f"Mismatched data type in {address}", + details=f"The data type in {address} was a {dtype.__name__} in the request, but {type(as_planned).__name__} ('{as_planned}') as planned", + ) + return + if dtype == StringBasedDateTime: + assert isinstance(as_planned, StringBasedDateTime) + assert isinstance(as_requested, StringBasedDateTime) + equal = as_planned.datetime == as_requested.datetime + else: + equal = as_planned == as_requested + if not equal: + check.record_failed( + summary=f"Incompatible flight info at {address}", + details=f"The value at {address} as requested was '{as_requested}', but as planned was '{as_planned}' when exact equality was expected", + ) + return + + raise NotImplementedError( + f"Means to compare data type {type(as_requested).__name__} (in field {address}) for equality has not yet been implemented" + ) + + +def times_not_later_than_specified_or_now( + as_requested: StringBasedDateTime, + as_planned: StringBasedDateTime, + address: JSONAddress, + check: PendingCheck, +): + """Implements CompatibilityEvaluator pattern for StringBasedDateTimes, requiring planned value to not be later than + requested or now, which ever is later. This allows a client to plan a time to be the current wall time when the + requested time was earlier than the wall time.""" + + if not isinstance(as_requested, StringBasedDateTime): + check.record_failed( + summary=f"Incorrect requested data type in {address}", + details=f"Expected a StringBasedDateTime in {address}, but found a {type(as_requested).__name__} ('{as_requested}') in the request instead", + ) + return + if not isinstance(as_planned, StringBasedDateTime): + check.record_failed( + summary=f"Incorrect data type in {address}", + details=f"Expected a StringBasedDateTime in {address}, but found a {type(as_planned).__name__} ('{as_planned}') as planned instead", + ) + return + now = arrow.utcnow().datetime + latest = now if as_requested.datetime < now else as_requested.datetime + if as_planned.datetime > latest: + check.record_failed( + f"Planned time {as_planned} is too late", + details=f"Requested time no later than {as_requested} (or now, at {now}) for {address}, but planned time was {as_planned} which is later than the latest time allowed of {latest}", + ) + + +def _resolve_addresses( + paths: Iterable[JSONPath] | JSONPath, content: dict[str, Any] +) -> Iterable[JSONAddress]: + if isinstance(paths, JSONPath): + paths = [paths] + for path in paths: + for match in bc_jsonpath_ng.parser.parse(path).find(content): + full_path = "$." + str(match.full_path) + full_path = full_path.replace(".[", "[") + yield JSONAddress(full_path) + + +def _require_compatible_values( + as_requested: dict | list | Any, + as_planned: dict | list | Any, + check: PendingCheck, + default_compatibility: CompatibilityEvaluator | None, + compatibility: dict[JSONAddress, CompatibilityEvaluator | None] | None, + address: JSONAddress = JSONAddress("$"), +): + # Use an explicit evaluator for this value if there is one + if compatibility and address in compatibility: + evaluator = compatibility[address] + if evaluator is not None: + evaluator(as_requested, as_planned, address, check) + + elif isinstance(as_requested, dict): + if not isinstance(as_planned, dict): + check.record_failed( + summary=f"Mismatched data in {address}", + details=f"The data type in {address} was an object/dictionary in the request, but '{as_planned}' ({type(as_planned)}) indicated as planned", + ) + return + for k, v in as_requested.items(): + if k in as_planned: + _require_compatible_values( + v, + as_planned[k], + check, + default_compatibility, + compatibility, + address + "." + k, + ) + else: + check.record_failed( + summary=f"As-planned missing {address}.{k}", + details=f"The request specified {address}.{k} as '{v}' but there was no such '{k}' key in {address} as planned", + ) + return + + elif isinstance(as_requested, list) and not isinstance(as_requested, str): + if not isinstance(as_planned, list): + check.record_failed( + summary=f"Mismatched data in {address}", + details=f"The data type in {address} was a list in the request, but '{as_planned}' ({type(as_planned)}) indicated as planned", + ) + return + if len(as_requested) != len(as_planned): + check.record_failed( + summary=f"Mismatched list at {address}", + details=f"As requested, {address} has {len(as_requested)} elements, but as planned, {address} has {len(as_planned)} elements (equality expected)", + ) + return + for i, v in enumerate(as_requested): + _require_compatible_values( + v, + as_planned[i], + check, + default_compatibility, + compatibility, + address + f"[{i}]", + ) + + else: + if default_compatibility: + default_compatibility(as_requested, as_planned, address, check) + else: + raise NotImplementedError( + f"Means to compare data type {type(as_requested)} in as_planned field {address} has not yet been implemented" + ) + + +def require_compatible_values( + as_requested: FlightInfo, + as_planned: FlightInfo, + check: PendingCheck, + compatibility: dict[JSONPath, CompatibilityEvaluator | None] | None = None, + default_compatibility: CompatibilityEvaluator | None = None, +) -> None: + """Requires values in as_planned FlightInfo to be compatible with those in as_requested. + + Arguments: + * as_requested: The flight information that was requested. + * as_planned: The flight information that was actually planned. + * check: Pending check that should fail if any planned values are incompatible with the request. + * default_compatibility: If specified, determine if a particular value pair is compatible using this method when + no other method is specified via `compatibility` + * compatibility: If specified, mapping between JSONPaths (e.g., $.basic_information.area[0].time_start) + describing element(s) of as_requested and the method to determine compatibility between each matching value + pair. + """ + compatibility_by_address = dict() + if compatibility: + for json_path, evaluator in compatibility.items(): + for address in _resolve_addresses(json_path, as_requested): + compatibility_by_address[address] = evaluator + _require_compatible_values( + as_requested, as_planned, check, default_compatibility, compatibility_by_address + ) diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/modify_activated_flight_intent.md b/monitoring/uss_qualifier/scenarios/flight_planning/modify_activated_flight_intent.md index 04bbdf2561..45b88c9ea5 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/modify_activated_flight_intent.md +++ b/monitoring/uss_qualifier/scenarios/flight_planning/modify_activated_flight_intent.md @@ -40,3 +40,7 @@ a low severity finding per **[astm.f3548.v21.SCD0030](../../requirements/astm/f3 All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../requirements/interuss/automated_testing/flight_planning.md)**. + +## 🛑 Injection fidelity check + +The requested flight should have been modified essentially as requested. The system may adapt requested parameters as necessary, but may not change the test-critical attributes of the flight when fulfilling the planning request per **interuss.automated_testing.flight_planning.ExpectedBehavior**. diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/modify_planned_flight_intent.md b/monitoring/uss_qualifier/scenarios/flight_planning/modify_planned_flight_intent.md index a0033a6f01..f7c0ba947a 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/modify_planned_flight_intent.md +++ b/monitoring/uss_qualifier/scenarios/flight_planning/modify_planned_flight_intent.md @@ -15,3 +15,7 @@ flight, this check will fail. All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../requirements/interuss/automated_testing/flight_planning.md)**. + +## 🛑 Injection fidelity check + +The requested flight should have been modified essentially as requested. The system may adapt requested parameters as necessary, but may not change the test-critical attributes of the flight when fulfilling the planning request per **interuss.automated_testing.flight_planning.ExpectedBehavior**. diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/plan_flight_intent.md b/monitoring/uss_qualifier/scenarios/flight_planning/plan_flight_intent.md index a57719e6cd..cdfd18350e 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/plan_flight_intent.md +++ b/monitoring/uss_qualifier/scenarios/flight_planning/plan_flight_intent.md @@ -11,3 +11,7 @@ All flight intent data provided is correct and valid and free of conflict in spa All flight intent data provided was complete and correct. It should have been processed successfully, allowing the USS to reject or accept the flight. If the USS indicates that the injection attempt failed, this check will fail per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../requirements/interuss/automated_testing/flight_planning.md)**. + +## 🛑 Injection fidelity check + +The requested flight should have been planned essentially as requested. The system may adapt requested parameters as necessary, but may not change the test-critical attributes of the flight when fulfilling the planning request per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../requirements/interuss/automated_testing/flight_planning.md)**. diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/prep_planners.py b/monitoring/uss_qualifier/scenarios/flight_planning/prep_planners.py index 91306c58e2..ad1423e505 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/prep_planners.py +++ b/monitoring/uss_qualifier/scenarios/flight_planning/prep_planners.py @@ -7,7 +7,7 @@ PlanningActivityError, ) from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection -from monitoring.monitorlib.temporal import Time, TimeDuringTest +from monitoring.monitorlib.temporal import TestTimeContext, Time from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.flight_planning import ( FlightIntentsResource, @@ -35,9 +35,9 @@ def __init__( ): super().__init__() now = Time(arrow.utcnow().datetime) - times_now = {t: now for t in TimeDuringTest} + times_now = TestTimeContext.all_times_are(now) later = now.offset(MAX_TEST_DURATION) - times_later = {t: later for t in TimeDuringTest} + times_later = TestTimeContext.all_times_are(later) self.areas = [] for intents in ( flight_intents, diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/prioritization_test_steps.py b/monitoring/uss_qualifier/scenarios/flight_planning/prioritization_test_steps.py index b264cf8211..d2ae866cbd 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/prioritization_test_steps.py +++ b/monitoring/uss_qualifier/scenarios/flight_planning/prioritization_test_steps.py @@ -1,6 +1,6 @@ from uas_standards.interuss.automated_testing.flight_planning.v1.api import ( - BasicFlightPlanInformationUasState, BasicFlightPlanInformationUsageState, + FunctionalState, ) from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient @@ -34,7 +34,7 @@ def plan_priority_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.Planned, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -68,7 +68,7 @@ def modify_planned_priority_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.Planned, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -81,6 +81,10 @@ def modify_planned_priority_conflict_flight( PlanningActivityResult.Rejected, FlightPlanStatus.Closed, ), # case where the USS closes the flight plan as a result of the rejected modification attempt + ( + PlanningActivityResult.NotSupported, + FlightPlanStatus.Planned, + ), # case where the USS does not support modification of flights }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=flight_planner, @@ -107,7 +111,7 @@ def activate_priority_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.InUse, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -150,7 +154,7 @@ def modify_activated_priority_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.InUse, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -160,9 +164,9 @@ def modify_activated_priority_conflict_flight( expected_results={ (PlanningActivityResult.Rejected, FlightPlanStatus.OkToFly), ( - PlanningActivityResult.Rejected, - FlightPlanStatus.Closed, - ), # case where the USS closes the flight plan as a result of the rejected modification attempt; note: is this actually desirable if the flight was activated? + PlanningActivityResult.NotSupported, + FlightPlanStatus.OkToFly, + ), # case where the USS does not support modification of flights }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=flight_planner, @@ -188,7 +192,7 @@ def plan_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.Planned, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -222,7 +226,7 @@ def modify_planned_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.Planned, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -235,6 +239,10 @@ def modify_planned_conflict_flight( PlanningActivityResult.Rejected, FlightPlanStatus.Closed, ), # case where the USS closes the flight plan as a result of the rejected modification attempt + ( + PlanningActivityResult.NotSupported, + FlightPlanStatus.Planned, + ), # case where the USS does not support modification of flights }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=flight_planner, @@ -261,7 +269,7 @@ def activate_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.InUse, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -304,7 +312,7 @@ def modify_activated_conflict_flight( expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.InUse, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) @@ -314,9 +322,9 @@ def modify_activated_conflict_flight( expected_results={ (PlanningActivityResult.Rejected, FlightPlanStatus.OkToFly), ( - PlanningActivityResult.Rejected, - FlightPlanStatus.Closed, - ), # case where the USS closes the flight plan as a result of the rejected modification attempt; note: is this actually desirable if the flight was activated? + PlanningActivityResult.NotSupported, + FlightPlanStatus.OkToFly, + ), # case where the USS does not support modification of flights }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=flight_planner, diff --git a/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py b/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py index ac82226140..3b6a7d5c49 100644 --- a/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/flight_planning/test_steps.py @@ -3,14 +3,15 @@ import arrow from uas_standards.interuss.automated_testing.flight_planning.v1.api import ( - BasicFlightPlanInformationUasState, BasicFlightPlanInformationUsageState, + FunctionalState, ) from monitoring.monitorlib.clients.flight_planning.client import PlanningActivityError from monitoring.monitorlib.clients.flight_planning.client_v1 import FlightPlannerClient from monitoring.monitorlib.clients.flight_planning.flight_info import ( ExecutionStyle, + FlightID, FlightInfo, ) from monitoring.monitorlib.clients.flight_planning.planning import ( @@ -18,16 +19,25 @@ PlanningActivityResponse, PlanningActivityResult, ) +from monitoring.monitorlib.dicts import JSONPath from monitoring.monitorlib.fetch import Query, QueryError from monitoring.monitorlib.geotemporal import end_time_of -from monitoring.uss_qualifier.scenarios.scenario import TestScenarioType +from monitoring.uss_qualifier.scenarios.flight_planning.injection_evaluation import ( + require_compatible_values, + times_not_later_than_specified_or_now, + values_exactly_equal, +) +from monitoring.uss_qualifier.scenarios.scenario import ( + ScenarioDidNotStopError, + TestScenario, +) def expect_flight_intent_state( flight_intent: FlightInfo, expected_usage_state: BasicFlightPlanInformationUsageState, - expected_uas_state: BasicFlightPlanInformationUasState, - scenario: TestScenarioType, + expected_uas_state: FunctionalState, + scenario: TestScenario, ) -> None: """Confirm that provided flight intent test data has the expected state or raise a ValueError.""" function_name = str(inspect.stack()[1][3]) @@ -43,12 +53,12 @@ def expect_flight_intent_state( def plan_flight( - scenario: TestScenarioType, + scenario: TestScenario, flight_planner: FlightPlannerClient, flight_info: FlightInfo, additional_fields: dict | None = None, nearby_potential_conflict: bool = False, -) -> tuple[PlanningActivityResponse, str | None]: +) -> tuple[PlanningActivityResponse, str | None, FlightInfo | None]: """Plan a flight intent that should result in success. This function implements the test step fragment described in @@ -60,9 +70,10 @@ def plan_flight( Returns: * The injection response. * The ID of the injected flight if it is returned, None otherwise. + * The flight info, as actually planned by the client. """ - resp, flight_id = submit_flight( + resp, flight_id, as_planned = submit_flight( scenario=scenario, success_check="Successful planning", expected_results={(PlanningActivityResult.Completed, FlightPlanStatus.Planned)}, @@ -80,66 +91,77 @@ def plan_flight( "Validate tested USS intersection algorithm", flight_planner.participant_id ).record_passed() - return resp, flight_id + return resp, flight_id, as_planned def modify_planned_flight( - scenario: TestScenarioType, + scenario: TestScenario, flight_planner: FlightPlannerClient, flight_info: FlightInfo, flight_id: str, additional_fields: dict | None = None, -) -> PlanningActivityResponse: +) -> tuple[PlanningActivityResponse, FlightInfo | None]: """Modify a planned flight intent that should result in success. This function implements the test step described in modify_planned_flight_intent.md. - Returns: The injection response. + Returns: + * The injection response. + * The flight info, as actually planned by the client. """ expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.Planned, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) - return submit_flight( + resp, _, as_planned = submit_flight( scenario=scenario, success_check="Successful modification", - expected_results={(PlanningActivityResult.Completed, FlightPlanStatus.Planned)}, + expected_results={ + (PlanningActivityResult.Completed, FlightPlanStatus.Planned), + ( + PlanningActivityResult.NotSupported, + FlightPlanStatus.Planned, + ), # case where the USS does not support modification of flights + }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=flight_planner, flight_info=flight_info, flight_id=flight_id, additional_fields=additional_fields, - )[0] + ) + return resp, as_planned def modify_activated_flight( - scenario: TestScenarioType, + scenario: TestScenario, flight_planner: FlightPlannerClient, flight_info: FlightInfo, flight_id: str, preexisting_conflict: bool = False, additional_fields: dict | None = None, -) -> PlanningActivityResponse: +) -> tuple[PlanningActivityResponse, FlightInfo | None]: """Modify an activated flight intent that should result in success. This function implements the test step described in modify_activated_flight_intent.md. - Returns: The injection response. + Returns: + * The injection response. + * The flight info, as actually planned by the client. """ expect_flight_intent_state( flight_info, BasicFlightPlanInformationUsageState.InUse, - BasicFlightPlanInformationUasState.Nominal, + FunctionalState.Nominal, scenario, ) if preexisting_conflict: - resp, _ = submit_flight( + resp, _, as_planned = submit_flight( scenario=scenario, success_check="Successful modification", expected_results={ @@ -154,6 +176,7 @@ def modify_activated_flight( flight_id=flight_id, additional_fields=additional_fields, ) + assert as_planned is not None with scenario.check( "Rejected modification", [flight_planner.participant_id] @@ -170,11 +193,15 @@ def modify_activated_flight( ) else: - resp, _ = submit_flight( + resp, _, as_planned = submit_flight( scenario=scenario, success_check="Successful modification", expected_results={ - (PlanningActivityResult.Completed, FlightPlanStatus.OkToFly) + (PlanningActivityResult.Completed, FlightPlanStatus.OkToFly), + ( + PlanningActivityResult.NotSupported, + FlightPlanStatus.OkToFly, + ), # case where the USS does not support modification of flights }, failed_checks={PlanningActivityResult.Failed: "Failure"}, flight_planner=flight_planner, @@ -182,17 +209,18 @@ def modify_activated_flight( flight_id=flight_id, additional_fields=additional_fields, ) + assert as_planned is not None - return resp + return resp, as_planned def activate_flight( - scenario: TestScenarioType, + scenario: TestScenario, flight_planner: FlightPlannerClient, flight_info: FlightInfo, flight_id: str | None = None, additional_fields: dict | None = None, -) -> tuple[PlanningActivityResponse, str | None]: +) -> tuple[PlanningActivityResponse, str | None, FlightInfo | None]: """Activate a flight intent that should result in success. This function implements the test step fragment described in @@ -201,6 +229,7 @@ def activate_flight( Returns: * The injection response. * The ID of the injected flight if it is returned, None otherwise. + * The flight info, as actually planned by the client. """ return submit_flight( scenario=scenario, @@ -215,7 +244,7 @@ def activate_flight( def submit_flight( - scenario: TestScenarioType, + scenario: TestScenario, success_check: str, expected_results: set[tuple[PlanningActivityResult, FlightPlanStatus]], failed_checks: dict[PlanningActivityResult, str], @@ -225,7 +254,7 @@ def submit_flight( additional_fields: dict | None = None, skip_if_not_supported: bool = False, may_end_in_past: bool = False, -) -> tuple[PlanningActivityResponse, str | None]: +) -> tuple[PlanningActivityResponse, str | None, FlightInfo | None]: """Submit a flight intent with an expected result. A check fail is considered by default of high severity and as such will raise an ScenarioCannotContinueError. The severity of each failed check may be overridden if needed. @@ -238,6 +267,7 @@ def submit_flight( Returns: * The injection response. * The ID of the injected flight if it is returned, None otherwise. + * The flight info, as actually planned by the client. """ if expected_results.intersection(failed_checks.keys()): raise ValueError( @@ -264,13 +294,14 @@ def submit_flight( details=f"{str(e)}\n\nStack trace:\n{e.stacktrace}", query_timestamps=[q.request.timestamp for q in e.queries], ) + raise ScenarioDidNotStopError(check) if ( skip_if_not_supported and resp.activity_result == PlanningActivityResult.NotSupported ): check.skip() - return resp, None + return resp, None, None msg = f"{flight_planner.participant_id} indicated flight planning activity {resp.activity_result} leaving flight plan {resp.flight_plan_status} rather than the expected {' or '.join([f'(Activity {expected_result[0]}, flight plan {expected_result[1]})' for expected_result in expected_results])}" if "notes" in resp and resp.notes: @@ -296,7 +327,35 @@ def submit_flight( query_timestamps=[query.request.timestamp], ) - return resp, flight_id + if resp.flight_plan_status not in { + FlightPlanStatus.Planned, + FlightPlanStatus.OkToFly, + FlightPlanStatus.OffNominal, + }: + return resp, flight_id, None + + if "as_planned" in resp and resp.as_planned: + with scenario.check( + "Injection fidelity", flight_planner.participant_id + ) as fidelity_check: + # TODO(#1326): Relax/remove this global fidelity check when each individual scenario validates that + # the flight, as injected, will satisfy the needs of the scenario. + require_compatible_values( + flight_info, + resp.as_planned, + fidelity_check, + default_compatibility=values_exactly_equal, + compatibility={ + JSONPath( + "$.basic_information.area[*].time_start" + ): times_not_later_than_specified_or_now + }, + ) + as_planned = resp.as_planned + else: + as_planned = flight_info + + return resp, flight_id, as_planned def request_flight( @@ -332,25 +391,10 @@ def request_flight( return resp, resp.queries[0], flight_id -def cleanup_flight( - flight_planner: FlightPlannerClient, flight_id: str -) -> tuple[PlanningActivityResponse, Query]: - try: - resp = flight_planner.try_end_flight(flight_id, ExecutionStyle.IfAllowed) - except PlanningActivityError as e: - raise QueryError(str(e), e.queries) - - flight_planner.created_flight_ids.discard(str(flight_id)) - return ( - resp, - resp.queries[0], - ) - - def delete_flight( - scenario: TestScenarioType, + scenario: TestScenario, flight_planner: FlightPlannerClient, - flight_id: str, + flight_id: FlightID, ) -> PlanningActivityResponse: """Delete an existing flight intent that should result in success. A check fail is considered of high severity and as such will raise an ScenarioCannotContinueError. @@ -363,57 +407,60 @@ def delete_flight( "Successful deletion", [flight_planner.participant_id] ) as check: try: - resp, query = cleanup_flight(flight_planner, flight_id) + resp = flight_planner.try_end_flight(flight_id, ExecutionStyle.IfAllowed) + except QueryError as e: - for q in e.queries: - scenario.record_query(q) + scenario.record_queries(e.queries) check.record_failed( summary=f"Error from {flight_planner.participant_id} when attempting to delete a flight intent (flight ID: {flight_id})", details=f"{str(e)}\n\nStack trace:\n{e.stacktrace}", query_timestamps=[q.request.timestamp for q in e.queries], ) - scenario.record_query(query) + raise ScenarioDidNotStopError(check) + scenario.record_queries(resp.queries) + notes_suffix = f': "{resp.notes}"' if "notes" in resp and resp.notes else "" if ( resp.activity_result == PlanningActivityResult.Completed and resp.flight_plan_status == FlightPlanStatus.Closed ): + flight_planner.created_flight_ids.discard(flight_id) return resp else: check.record_failed( summary=f"Flight deletion attempt unexpectedly {resp.activity_result} with flight plan status {resp.flight_plan_status}", details=f"{flight_planner.participant_id} indicated {resp.activity_result} with flight plan status {resp.flight_plan_status} rather than the expected Completed with flight plan status Closed{notes_suffix}", - query_timestamps=[query.request.timestamp], + query_timestamps=[resp.queries[0].request.timestamp] + if resp.queries + else [], ) - - raise RuntimeError( - "Error with deletion of flight intent, but a High Severity issue didn't interrupt execution" - ) + raise ScenarioDidNotStopError(check) def cleanup_flights( - scenario: TestScenarioType, flight_planners: Iterable[FlightPlannerClient] + scenario: TestScenario, flight_planners: Iterable[FlightPlannerClient] ) -> None: """Remove flights during a cleanup test step. This function assumes: * `scenario` is currently cleaning up (cleanup has started) * "Successful flight deletion" check declared for cleanup phase in `scenario`'s documentation + * IDs of flights created have been added to each flight_planner.created_flight_ids """ for flight_planner in flight_planners: - removed = [] to_remove = flight_planner.created_flight_ids.copy() for flight_id in to_remove: with scenario.check( "Successful flight deletion", [flight_planner.participant_id] ) as check: try: - resp, query = cleanup_flight(flight_planner, flight_id) - scenario.record_query(query) + resp = flight_planner.try_end_flight( + flight_id, ExecutionStyle.IfAllowed + ) + except QueryError as e: - for q in e.queries: - scenario.record_query(q) + scenario.record_queries(e.queries) if ( e.queries and e.queries[0].status_code == 404 @@ -429,12 +476,24 @@ def cleanup_flights( details=f"{str(e)}\n\nStack trace:\n{e.stacktrace}", query_timestamps=[q.request.timestamp for q in e.queries], ) - continue - if resp.flight_plan_status != FlightPlanStatus.Closed: - scenario.record_note( - f"Deletion of {flight_id}", - f"Deletion of flight {flight_id} returned a status of '{resp.flight_plan_status}' ({FlightPlanStatus.Closed} wanted)", + # Do not attempt to clean up again as we would expect the same error + flight_planner.created_flight_ids.discard(flight_id) + + continue + scenario.record_queries(resp.queries) + + if resp.flight_plan_status not in [ + FlightPlanStatus.Closed, + FlightPlanStatus.NotPlanned, + ]: + check.record_failed( + summary=f"Failed to clean up flight {flight_id} from {flight_planner.participant_id}", + details=f"Deletion of flight {flight_id} returned a status of '{resp.flight_plan_status}' ({FlightPlanStatus.Closed} or {FlightPlanStatus.NotPlanned} wanted)", + query_timestamps=[q.request.timestamp for q in resp.queries], ) - removed.append(flight_id) + # Remove flight_id from created flights regardless of status. + # If status was successful, the flight has been removed. If the status was unsuccessful, we would + # expect the same error status if we were to try again. + flight_planner.created_flight_ids.discard(flight_id) diff --git a/monitoring/uss_qualifier/scenarios/interuss/flight_authorization/general_flight_authorization.py b/monitoring/uss_qualifier/scenarios/interuss/flight_authorization/general_flight_authorization.py index a757f7b73a..1babc2b265 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/flight_authorization/general_flight_authorization.py +++ b/monitoring/uss_qualifier/scenarios/interuss/flight_authorization/general_flight_authorization.py @@ -1,5 +1,3 @@ -import arrow - from monitoring.monitorlib.clients.flight_planning.client import ( FlightPlannerClient, PlanningActivityError, @@ -10,8 +8,8 @@ FlightPlanStatus, PlanningActivityResult, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.uss_qualifier.configurations.configuration import ParticipantID +from monitoring.uss_qualifier.requirements.definitions import RequirementID from monitoring.uss_qualifier.resources.flight_planning import ( FlightIntentsResource, FlightPlannerResource, @@ -25,7 +23,9 @@ FlightCheckTableResource, ) from monitoring.uss_qualifier.scenarios.documentation.definitions import ( + TestCaseDocumentation, TestCheckDocumentation, + TestScenarioDocumentation, TestStepDocumentation, ) from monitoring.uss_qualifier.scenarios.scenario import TestScenario @@ -63,35 +63,24 @@ def __init__( self.participant_id = planner.participant_id self.flight_intents = flight_intents.get_flight_intents() - def run(self, context: ExecutionContext): - self.begin_test_scenario(context) - times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - - self.begin_test_case("Flight planning") - self._plan_flights(times) - self.end_test_case() - - self.end_test_scenario() + def _rewrite_documentation(self): + """The documentation in the standard, static accompanying .md file acts as a template, but the test scenario + dynamically adjusts its test procedure based on the FlightCheckTable provided in configuration.""" + original_case = self.documentation.cases[0] + original_step = original_case.steps[0] + steps = [] - def _plan_flights(self, times: dict[TimeDuringTest, Time]): for row in self.table.rows: # Collect checks applicable to this row/test step checks = [ - _get_check_by_name(self._current_case.steps[0], name) + _get_check_by_name(original_step, name) for name in (_VALID_API_RESPONSE_NAME, _SUCCESSFUL_CLOSURE_NAME) ] if row.acceptance_expectation == AcceptanceExpectation.MustBeAccepted: - acceptance_check = _get_check_by_name( - self._current_case.steps[0], _ACCEPT_CHECK_NAME - ) + acceptance_check = _get_check_by_name(original_step, _ACCEPT_CHECK_NAME) checks.append(acceptance_check) elif row.acceptance_expectation == AcceptanceExpectation.MustBeRejected: - rejection_check = _get_check_by_name( - self._current_case.steps[0], _REJECT_CHECK_NAME - ) + rejection_check = _get_check_by_name(original_step, _REJECT_CHECK_NAME) checks.append(rejection_check) elif row.acceptance_expectation == AcceptanceExpectation.Irrelevant: pass # No acceptance-related checks to perform in this case @@ -102,12 +91,12 @@ def _plan_flights(self, times: dict[TimeDuringTest, Time]): if row.conditions_expectation == ConditionsExpectation.MustBePresent: conditional_check = _get_check_by_name( - self._current_case.steps[0], _CONDITIONAL_CHECK_NAME + original_step, _CONDITIONAL_CHECK_NAME ) checks.append(conditional_check) elif row.conditions_expectation == ConditionsExpectation.MustBeAbsent: unconditional_check = _get_check_by_name( - self._current_case.steps[0], _UNCONDITIONAL_CHECK_NAME + original_step, _UNCONDITIONAL_CHECK_NAME ) checks.append(unconditional_check) elif row.conditions_expectation == ConditionsExpectation.Irrelevant: @@ -118,30 +107,62 @@ def _plan_flights(self, times: dict[TimeDuringTest, Time]): ) # Construct documentation for this test step - # Note that we are duck-typing a List[str] into a List[RequirementID] for applicable_requirements, but this - # should be ok as the requirements are only used as strings from this point. step_checks = [ TestCheckDocumentation( name=c.name, url=c.url, - applicable_requirements=row.requirement_ids, + applicable_requirements=[ + RequirementID(r) for r in row.requirement_ids + ], has_todo=c.has_todo, severity=c.severity, ) for c in checks ] - doc = TestStepDocumentation( - name=row.flight_check_id, - url=self._current_case.steps[0].url, - checks=step_checks, + steps.append( + TestStepDocumentation( + name=row.flight_check_id, + url=original_step.url, + checks=step_checks, + ) ) - # Officially begin the test step - self.begin_dynamic_test_step(doc) + case = TestCaseDocumentation( + name=original_case.name, + url=original_case.url, + steps=steps, + ) + + new_doc = TestScenarioDocumentation( + name=self.documentation.name, + url=self.documentation.url, + local_path=self.documentation.local_path, + cases=[case], + ) + if "resources" in self.documentation: + new_doc.resources = self.documentation.resources + if "cleanup" in self.documentation: + new_doc.cleanup = self.documentation.cleanup + self.documentation = new_doc + + def run(self, context: ExecutionContext): + self._rewrite_documentation() + self.begin_test_scenario(context) + + self.begin_test_case("Flight planning") + self._plan_flights() + self.end_test_case() + + self.end_test_scenario() + + def _plan_flights(self): + for row in self.table.rows: + self.begin_test_step(row.flight_check_id) # Attempt planning action - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - info = self.flight_intents[row.flight_intent].resolve(times) + info = self.flight_intents[row.flight_intent].resolve( + self.time_context.evaluate_now() + ) with self.check(_VALID_API_RESPONSE_NAME, [self.participant_id]) as check: try: resp = self.flight_planner.try_plan_flight( diff --git a/monitoring/uss_qualifier/scenarios/interuss/geospatial_map/geospatial_feature_comprehension.py b/monitoring/uss_qualifier/scenarios/interuss/geospatial_map/geospatial_feature_comprehension.py index 65509d2c7f..55e4b9a0ef 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/geospatial_map/geospatial_feature_comprehension.py +++ b/monitoring/uss_qualifier/scenarios/interuss/geospatial_map/geospatial_feature_comprehension.py @@ -1,5 +1,3 @@ -import arrow - from monitoring.monitorlib.clients.geospatial_info.client import ( GeospatialInfoClient, GeospatialInfoError, @@ -10,8 +8,8 @@ OperationalImpact, SelectionOutcome, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.uss_qualifier.configurations.configuration import ParticipantID +from monitoring.uss_qualifier.requirements.definitions import RequirementID from monitoring.uss_qualifier.resources.geospatial_info import ( GeospatialInfoProviderResource, ) @@ -23,7 +21,9 @@ FeatureCheckTable, ) from monitoring.uss_qualifier.scenarios.documentation.definitions import ( + TestCaseDocumentation, TestCheckDocumentation, + TestScenarioDocumentation, TestStepDocumentation, ) from monitoring.uss_qualifier.scenarios.scenario import TestScenario @@ -55,55 +55,81 @@ def __init__( self.geospatial_client = geospatial_info_provider.client self.table = table.table - def run(self, context: ExecutionContext): - self.begin_test_scenario(context) - times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), + def _rewrite_documentation(self): + """The documentation in the standard, static accompanying .md file acts as a template, but the test scenario + dynamically adjusts its test procedure based on the FeatureCheckTable provided in configuration.""" + original_case = self.documentation.cases[0] + original_step = original_case.steps[0] + steps = [] + + all_checks = {c.name: c for c in original_step.checks} + query_check = all_checks[_SUCCESSFUL_QUERY_CHECK_NAME] + outcome_checks = { + expected_result: all_checks[check_name] + for expected_result, check_name in _CHECK_NAMES.items() } - self.begin_test_case("Map query") - self._map_query(times) - self.end_test_case() - - self.end_test_scenario() - - def _map_query(self, times: dict[TimeDuringTest, Time]): - query_check = [ - c - for c in self._current_case.steps[0].checks - if c.name == _SUCCESSFUL_QUERY_CHECK_NAME - ][0] for row in self.table.rows: if row.expected_result not in _CHECK_NAMES: raise NotImplementedError( f"expected_result {row.expected_result} is not yet supported" ) - check_name = _CHECK_NAMES[row.expected_result] - original_check = [ - c for c in self._current_case.steps[0].checks if c.name == check_name - ][0] - # Note that we are duck-typing a List[str] into a List[RequirementID] for applicable_requirements, but this - # should be ok as the requirements are only used as strings from this point. + original_check = outcome_checks[row.expected_result] check = TestCheckDocumentation( - name=check_name, + name=original_check.name, url=original_check.url, - applicable_requirements=row.requirement_ids, + applicable_requirements=[RequirementID(r) for r in row.requirement_ids], has_todo=original_check.has_todo, severity=original_check.severity, ) - doc = TestStepDocumentation( + step = TestStepDocumentation( name=row.geospatial_check_id, - url=self._current_case.steps[0].url, + url=original_step.url, checks=[query_check, check], ) - self.begin_dynamic_test_step(doc) + steps.append(step) + + case = TestCaseDocumentation( + name=original_case.name, + url=original_case.url, + steps=steps, + ) + + new_doc = TestScenarioDocumentation( + name=self.documentation.name, + url=self.documentation.url, + local_path=self.documentation.local_path, + cases=[case], + ) + if "resources" in self.documentation: + new_doc.resources = self.documentation.resources + if "cleanup" in self.documentation: + new_doc.cleanup = self.documentation.cleanup + self.documentation = new_doc + + def run(self, context: ExecutionContext): + self._rewrite_documentation() + self.begin_test_scenario(context) + + self.begin_test_case("Map query") + self._map_query() + self.end_test_case() + + self.end_test_scenario() + + def _map_query(self): + for row in self.table.rows: + self.begin_test_step(row.geospatial_check_id) + if row.description: + self.record_note( + f"{row.geospatial_check_id}.description", row.description + ) # Populate filter set filter_set = GeospatialFeatureFilter() - times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - concrete_volumes = [v.resolve(times) for v in row.volumes] + self.time_context.evaluate_now() + concrete_volumes = [v.resolve(self.time_context) for v in row.volumes] filter_set.volumes4d = concrete_volumes if row.restriction_source: diff --git a/monitoring/uss_qualifier/scenarios/interuss/mock_uss/configure_locality.md b/monitoring/uss_qualifier/scenarios/interuss/mock_uss/configure_locality.md index 5208ed3381..9e8819a150 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/mock_uss/configure_locality.md +++ b/monitoring/uss_qualifier/scenarios/interuss/mock_uss/configure_locality.md @@ -30,6 +30,6 @@ If a mock USS instance doesn't respond properly to a request to change its local ## Cleanup -### 🛑 Restore locality check +### ⚠️ Restore locality check If uss_qualifier cannot restore a mock_uss instance's locality to its old value when rolling back incomplete locality changes, **[interuss.mock_uss.hosted_instance.ExposeInterface](../../../requirements/interuss/mock_uss/hosted_instance.md)** is not met. diff --git a/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py b/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py index b679fa4e77..39aede2757 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/interuss/mock_uss/test_steps.py @@ -9,7 +9,7 @@ Interaction, QueryDirection, ) -from monitoring.monitorlib.fetch import Query, QueryError +from monitoring.monitorlib.fetch import Query, QueryError, QueryType from monitoring.uss_qualifier.resources.interuss.mock_uss.client import MockUSSClient from monitoring.uss_qualifier.scenarios.scenario import TestScenarioType @@ -132,3 +132,16 @@ def is_applicable(interaction: Interaction) -> bool: return interaction.query.status_code == status_code return is_applicable + + +def query_type_filter(*query_type: QueryType | None) -> Callable[[Interaction], bool]: + def is_applicable(interaction: Interaction) -> bool: + if "query_type" in interaction.query and interaction.query.query_type: + if interaction.query.query_type in query_type: + return True + else: + if None in query_type: + return True + return False + + return is_applicable diff --git a/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.md b/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.md index 9e13dc2a07..204c1d5b07 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.md +++ b/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.md @@ -65,4 +65,4 @@ If the DSS accepts the OVN suffix, or fails with an unexpected error, this check Check that the DSS rejects OVN suffix that are outdated UUIDv7. If the DSS accepts the OVN suffix, or fails with an unexpected error, this check will fail. -## [Cleanup](../../astm/utm/dss/clean_workspace_op_intents.md) +## [Cleanup](../../astm/utm/dss/clean_workspace_op_intents_during_cleanup.md) diff --git a/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.py b/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.py index cc77b9443a..84c68260b3 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.py +++ b/monitoring/uss_qualifier/scenarios/interuss/ovn_request/dss_ovn_request.py @@ -8,7 +8,6 @@ from uas_standards.astm.f3548.v21.constants import Scope from uuid6 import uuid6, uuid7 -from monitoring.monitorlib import geotemporal from monitoring.monitorlib.fetch import QueryError from monitoring.prober.infrastructure import register_resource_type from monitoring.uss_qualifier.resources import PlanningAreaResource @@ -46,7 +45,7 @@ def __init__( ) self._oir_id = id_generator.id_factory.make_id(OIR_TYPE) - self._planning_area = planning_area.specification + self._planning_area = planning_area self._expected_manager = client_identity.subject() def run(self, context: ExecutionContext): @@ -55,7 +54,7 @@ def run(self, context: ExecutionContext): now = datetime.now() extents = [ - self._planning_area.get_volume4d( + self._planning_area.resolved_volume4d_with_times( now - timedelta(seconds=10), now + timedelta(minutes=45), ).to_f3548v21() @@ -119,7 +118,7 @@ def _create_oir( extents=extents, key=[], state=OperationalIntentState.Accepted, - base_url=self._planning_area.get_base_url(), + base_url=self._planning_area.specification.get_base_url(), oi_id=self._oir_id, ovn=None, requested_ovn_suffix=req_ovn_suffix, @@ -145,7 +144,7 @@ def _activate_oir(self, extents: list[Volume4D], ovn: str, req_ovn_suffix: str): extents=extents, key=[], state=OperationalIntentState.Activated, - base_url=self._planning_area.get_base_url(), + base_url=self._planning_area.specification.get_base_url(), oi_id=self._oir_id, ovn=ovn, requested_ovn_suffix=req_ovn_suffix, @@ -171,7 +170,7 @@ def _create_invalid_oir_attempt(self, extents: list[Volume4D], req_ovn_suffix: s extents=extents, key=[], state=OperationalIntentState.Accepted, - base_url=self._planning_area.get_base_url(), + base_url=self._planning_area.specification.get_base_url(), oi_id=self._oir_id, ovn=None, requested_ovn_suffix=req_ovn_suffix, @@ -207,9 +206,7 @@ def _setup_case(self): self.begin_test_case("Setup") self.begin_test_step("Ensure clean workspace") - vol = geotemporal.Volume4D( - volume=self._planning_area.volume, - ).to_f3548v21() + vol = self._planning_area.resolved_volume4d_with_times(None, None).to_f3548v21() test_step_fragments.cleanup_active_oirs( self, diff --git a/monitoring/uss_qualifier/scenarios/interuss/unit_test.py b/monitoring/uss_qualifier/scenarios/interuss/unit_test.py index 30aac26e96..9d8ffc55f8 100644 --- a/monitoring/uss_qualifier/scenarios/interuss/unit_test.py +++ b/monitoring/uss_qualifier/scenarios/interuss/unit_test.py @@ -25,7 +25,7 @@ def run(self, context: ExecutionContext): self.end_test_scenario() def execute_unit_test(self): - context = ExecutionContext(None) + context = ExecutionContext(None, []) self.run(context) self.cleanup() return self diff --git a/monitoring/uss_qualifier/scenarios/scenario.py b/monitoring/uss_qualifier/scenarios/scenario.py index 8ef8287907..683e0752b8 100644 --- a/monitoring/uss_qualifier/scenarios/scenario.py +++ b/monitoring/uss_qualifier/scenarios/scenario.py @@ -1,25 +1,29 @@ import inspect +import time as pytime import traceback from abc import ABC, abstractmethod -from collections.abc import Callable -from datetime import UTC, datetime -from enum import Enum -from typing import TypeVar +from collections.abc import Callable, Iterable +from datetime import UTC, datetime, timedelta +from enum import StrEnum +from typing import Any, TypeVar import arrow -from implicitdict import StringBasedDateTime +from implicitdict import StringBasedDateTime, StringBasedTimeDelta from loguru import logger from monitoring import uss_qualifier as uss_qualifier_module from monitoring.monitorlib import fetch, inspection from monitoring.monitorlib.errors import current_stack_string -from monitoring.monitorlib.fetch import QueryType +from monitoring.monitorlib.fetch import Query, QueryType from monitoring.monitorlib.inspection import fullname +from monitoring.monitorlib.temporal import TestTimeContext from monitoring.uss_qualifier import scenarios as scenarios_module from monitoring.uss_qualifier.common_data_definitions import Severity +from monitoring.uss_qualifier.configurations.configuration import FullyQualifiedCheck from monitoring.uss_qualifier.reports.report import ( ErrorReport, FailedCheck, + IntentionalDelay, Note, ParticipantID, PassedCheck, @@ -57,6 +61,12 @@ QueryType.F3411v22aUSSGetFlightDetails, ] +# Different spherical models have different precisions: implementations may use a different model +# than uss_qualifier. We thus accept an error margin of 0.7% around distance limits and thresholds +# to avoid failing USSes for minor differences in precision whenever the relevant standard is not +# prescriptive in that regard. +DISTANCE_ERROR_TOLERANCE_FRACTION = 0.007 + class ScenarioCannotContinueError(Exception): def __init__(self, msg): @@ -68,7 +78,7 @@ def __init__(self, msg): super().__init__(msg) -class ScenarioPhase(str, Enum): +class ScenarioPhase(StrEnum): Undefined = "Undefined" NotStarted = "NotStarted" ReadyForTestCase = "ReadyForTestCase" @@ -117,6 +127,7 @@ def record_failed( details: str = "", query_timestamps: list[datetime] | None = None, additional_data: dict | None = None, + queries: Query | Iterable[Query] | None = None, ) -> None: self._outcome_recorded = True if "severity" in self._documentation and self._documentation.severity: @@ -149,10 +160,24 @@ def record_failed( } if additional_data is not None: kwargs["additional_data"] = additional_data - if query_timestamps is not None: - kwargs["query_report_timestamps"] = [ - StringBasedDateTime(t) for t in query_timestamps - ] + if query_timestamps is not None or queries is not None: + query_report_timestamps = [] + if query_timestamps is not None: + query_report_timestamps.extend( + StringBasedDateTime(t) for t in query_timestamps + ) + if isinstance(queries, Query): + if "initiated_at" in queries.request and queries.request.initiated_at: + query_report_timestamps.append( + StringBasedDateTime(queries.request.initiated_at) + ) + elif queries: + for query in queries: + if "initiated_at" in query.request and query.request.initiated_at: + query_report_timestamps.append( + StringBasedDateTime(query.request.initiated_at) + ) + kwargs["query_report_timestamps"] = query_report_timestamps failed_check = FailedCheck(**kwargs) self._step_report.failed_checks.append(failed_check) if self._on_failed_check is not None: @@ -213,6 +238,29 @@ def get_scenario_type_by_name(scenario_type_name: TestScenarioTypeName) -> type: return scenario_type +def are_scenario_types_equal( + scenario_type_name_1: TestScenarioTypeName, + scenario_type_name_2: TestScenarioTypeName, +) -> bool: + scenario_type_1 = get_scenario_type_by_name(scenario_type_name_1) + scenario_type_2 = get_scenario_type_by_name(scenario_type_name_2) + return scenario_type_1 == scenario_type_2 + + +def fully_qualified_check_in_collection( + check: FullyQualifiedCheck, collection: Iterable[FullyQualifiedCheck] +) -> bool: + for other in collection: + if ( + are_scenario_types_equal(check.scenario_type, other.scenario_type) + and check.test_case_name == other.test_case_name + and check.test_step_name == other.test_step_name + and check.check_name == other.check_name + ): + return True + return False + + class GenericTestScenario(ABC): """Generic Test Scenario allowing mutualization of test scenario implementation. @@ -222,6 +270,10 @@ class GenericTestScenario(ABC): declaration: TestScenarioDeclaration documentation: TestScenarioDocumentation on_failed_check: Callable[[FailedCheck], None] | None = None + time_context: TestTimeContext + + cache: dict[str, Any] + """Cached data scoped to the lifetime of the scenario.""" resource_origins: dict[ResourceID, str] """Map between local resource name (as defined in test scenario) to where that resource originated.""" @@ -242,6 +294,8 @@ class GenericTestScenario(ABC): def __init__(self): self.documentation = get_documentation(self.__class__) self._phase = ScenarioPhase.NotStarted + self.time_context = TestTimeContext() + self.cache = {} @staticmethod def make_test_scenario( @@ -335,8 +389,9 @@ def record_note(self, key: str, message: str) -> None: if self._scenario_report is None: self._make_scenario_report() + assert self._scenario_report is not None - if "notes" not in self._scenario_report: + if "notes" not in self._scenario_report or self._scenario_report.notes is None: self._scenario_report.notes = {} if key in self._scenario_report.notes: @@ -366,6 +421,7 @@ def begin_test_scenario(self, context) -> None: def begin_test_case(self, name: str) -> None: self._expect_phase(ScenarioPhase.ReadyForTestCase) + assert self._scenario_report is not None available_cases = {c.name: c for c in self.documentation.cases} if name not in available_cases: case_list = ", ".join(f'"{c}"' for c in available_cases) @@ -377,6 +433,7 @@ def begin_test_case(self, name: str) -> None: f"Test case {name} had already run in `{self.me()}` when begin_test_case was called" ) self._current_case = available_cases[name] + assert self._current_case is not None self._case_report = TestCaseReport( name=self._current_case.name, documentation_url=self._current_case.url, @@ -388,6 +445,7 @@ def begin_test_case(self, name: str) -> None: def begin_test_step(self, name: str) -> None: self._expect_phase(ScenarioPhase.ReadyForTestStep) + assert self._current_case is not None available_steps = {c.name: c for c in self._current_case.steps} if name not in available_steps: step_list = ", ".join(f'"{s}"' for s in available_steps) @@ -396,15 +454,6 @@ def begin_test_step(self, name: str) -> None: ) self._begin_test_step(available_steps[name]) - def begin_dynamic_test_step(self, step: TestStepDocumentation) -> None: - self._expect_phase(ScenarioPhase.ReadyForTestStep) - available_steps = {c.name: c for c in self._current_case.steps} - if "Dynamic" not in available_steps: - raise RuntimeError( - f'Test scenario `{self.me()}` was instructed to begin_dynamic_test_step "{step.name}" during test case "{self._current_case.name}", but there is no "Dynamic test step" declared in documentation.' - ) - self._begin_test_step(step) - def _begin_test_step(self, step: TestStepDocumentation) -> None: self._current_step = step self._step_report = TestStepReport( @@ -414,6 +463,7 @@ def _begin_test_step(self, step: TestStepDocumentation) -> None: failed_checks=[], passed_checks=[], ) + assert self._case_report is not None self._case_report.steps.append(self._step_report) self._phase = ScenarioPhase.RunningTestStep @@ -423,8 +473,16 @@ def record_queries(self, queries: list[fetch.Query]) -> None: def record_query(self, query: fetch.Query) -> None: self._expect_phase({ScenarioPhase.RunningTestStep, ScenarioPhase.CleaningUp}) - if "queries" not in self._step_report: + + # If the query has a previous one, record it first + if "_previous_query" in query and query._previous_query: + self.record_query(query._previous_query) + + assert self._step_report is not None + + if "queries" not in self._step_report or self._step_report.queries is None: self._step_report.queries = [] + for existing_query in self._step_report.queries: if query.request.timestamp == existing_query.request.timestamp: logger.error( @@ -464,6 +522,8 @@ def check( if isinstance(participants, str): participants = [participants] self._expect_phase({ScenarioPhase.RunningTestStep, ScenarioPhase.CleaningUp}) + assert self._current_step is not None + available_checks = {c.name: c for c in self._current_step.checks} if name in available_checks: check_documentation = available_checks[name] @@ -494,17 +554,26 @@ def check( raise RuntimeError( f'Test scenario `{self.me()}` was instructed to prepare to record outcome for check "{name}" during test step "{test_step_name}" during test case "{test_case_name}", but that check is not declared in documentation; declared checks are: {check_list}' ) + + assert self.context is not None + assert self._step_report is not None + return PendingCheck( phase=self._phase, documentation=check_documentation, participants=[] if participants is None else participants, step_report=self._step_report, - stop_fast=self.context.stop_fast, + stop_fast=self.context.stop_fast( + self._current_case.name if self._current_case else None, + self._current_step.name if self._current_step else None, + name, + ), on_failed_check=self.on_failed_check, ) def end_test_step(self) -> TestStepReport: self._expect_phase(ScenarioPhase.RunningTestStep) + assert self._step_report is not None self._step_report.end_time = StringBasedDateTime(datetime.now(UTC)) self._current_step = None report = self._step_report @@ -514,6 +583,7 @@ def end_test_step(self) -> TestStepReport: def end_test_case(self) -> None: self._expect_phase(ScenarioPhase.ReadyForTestStep) + assert self._case_report is not None self._case_report.end_time = StringBasedDateTime(datetime.now(UTC)) self._current_case = None self._case_report = None @@ -521,12 +591,13 @@ def end_test_case(self) -> None: def end_test_scenario(self) -> None: self._expect_phase(ScenarioPhase.ReadyForTestCase) - self._scenario_report.end_time = StringBasedDateTime(datetime.now(UTC)) + assert self._scenario_report is not None self._phase = ScenarioPhase.ReadyForCleanup def go_to_cleanup(self) -> None: self._expect_phase( { + ScenarioPhase.NotStarted, ScenarioPhase.ReadyForTestCase, ScenarioPhase.ReadyForTestStep, ScenarioPhase.RunningTestStep, @@ -542,6 +613,7 @@ def begin_cleanup(self) -> None: f"Test scenario `{self.me()}` attempted to begin_cleanup, but no cleanup step is documented" ) self._current_step = self.documentation.cleanup + assert self._current_step is not None self._step_report = TestStepReport( name=self._current_step.name, documentation_url=self._current_step.url, @@ -549,6 +621,7 @@ def begin_cleanup(self) -> None: failed_checks=[], passed_checks=[], ) + assert self._scenario_report is not None self._scenario_report.cleanup = self._step_report self._phase = ScenarioPhase.CleaningUp @@ -562,7 +635,7 @@ def skip_cleanup(self) -> None: def end_cleanup(self) -> None: self._expect_phase(ScenarioPhase.CleaningUp) - self._step_report.end_time = StringBasedDateTime(datetime.now(UTC)) + assert self._step_report is not None self._phase = ScenarioPhase.Complete def ensure_cleanup_ended(self) -> None: @@ -572,7 +645,7 @@ def ensure_cleanup_ended(self) -> None: end_cleanup was called.""" self._expect_phase({ScenarioPhase.CleaningUp, ScenarioPhase.Complete}) if self._phase == ScenarioPhase.CleaningUp: - self._step_report.end_time = StringBasedDateTime(datetime.now(UTC)) + assert self._step_report is not None self._phase = ScenarioPhase.Complete def record_execution_error(self, e: Exception) -> None: @@ -582,6 +655,7 @@ def record_execution_error(self, e: Exception) -> None: ) if self._scenario_report is None: self._make_scenario_report() + assert self._scenario_report is not None self._scenario_report.execution_error = ErrorReport.create_from_exception(e) self._scenario_report.successful = False self._phase = ScenarioPhase.Complete @@ -589,11 +663,19 @@ def record_execution_error(self, e: Exception) -> None: def get_report(self) -> TestScenarioReport: if self._scenario_report is None: self._make_scenario_report() + assert self._scenario_report is not None if "execution_error" not in self._scenario_report: try: self._expect_phase(ScenarioPhase.Complete) except RuntimeError as e: self.record_execution_error(e) + if ( + "end_time" not in self._scenario_report + or self._scenario_report.end_time is None + ): + self._scenario_report.end_time = StringBasedDateTime( + arrow.utcnow().datetime + ) # Evaluate success self._scenario_report.successful = ( @@ -604,13 +686,56 @@ def get_report(self) -> TestScenarioReport: for failed_check in step_report.failed_checks: if failed_check.severity != Severity.Low: self._scenario_report.successful = False - if "cleanup" in self._scenario_report: + if ( + "cleanup" in self._scenario_report + and self._scenario_report.cleanup is not None + ): for failed_check in self._scenario_report.cleanup.failed_checks: if failed_check.severity != Severity.Low: self._scenario_report.successful = False return self._scenario_report + def sleep(self, duration: float | timedelta, reason: str) -> None: + """Sleep for the specified amount of time, logging the fact that the delay is occurring (when appropriate). + + Args: + duration: Amount of time to sleep for; interpreted as seconds if float. + reason: Reason the delay is happening (to be printed to console/log if appropriate). + """ + MAX_SILENT_DELAY_S = 0.4 + """Number of seconds to delay above which a reasoning message should be displayed.""" + + if isinstance(duration, timedelta): + duration = duration.total_seconds() + if duration <= 0: + # No need to delay + return + + if duration > MAX_SILENT_DELAY_S: + logger.debug(f"Delaying {duration:.1f} seconds because {reason}") + delay = IntentionalDelay( + start_time=StringBasedDateTime(arrow.utcnow().datetime), + duration=StringBasedTimeDelta(duration), + reason=reason, + ) + if self._phase == ScenarioPhase.RunningTestStep and self._step_report: + if "delays" not in self._step_report or not self._step_report.delays: + self._step_report.delays = [] + self._step_report.delays.append(delay) + elif self._scenario_report: + if ( + "delays" not in self._scenario_report + or not self._scenario_report.delays + ): + self._scenario_report.delays = [] + self._scenario_report.delays.append(delay) + else: + raise RuntimeError( + f"Scenario {type(self).__name__} attempted to sleep when not executing the test scenario (phase={self._phase})" + ) + pytime.sleep(duration) + class TestScenario(GenericTestScenario): """Instance of a test scenario, ready to run after construction. @@ -654,7 +779,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) diff --git a/monitoring/uss_qualifier/scenarios/scenario_test/generic_test_scenario_test.py b/monitoring/uss_qualifier/scenarios/scenario_test/generic_test_scenario_test.py index 6ca1c78b7e..73d451e4e5 100644 --- a/monitoring/uss_qualifier/scenarios/scenario_test/generic_test_scenario_test.py +++ b/monitoring/uss_qualifier/scenarios/scenario_test/generic_test_scenario_test.py @@ -1,6 +1,5 @@ import itertools -from monitoring.monitorlib.testing import make_fake_url from monitoring.uss_qualifier.resources.dev.noop import NoOpResource, NoOpSpecification from monitoring.uss_qualifier.resources.resource import MissingResourceError from monitoring.uss_qualifier.scenarios.scenario import ( @@ -14,9 +13,6 @@ from monitoring.uss_qualifier.scenarios.scenario import ( TestScenarioDeclaration as _TestScenarioDeclaration, ) -from monitoring.uss_qualifier.scenarios.scenario import ( - TestStepDocumentation as _TestStepDocumentation, -) from .generic_test_scenario_without_cleanup import ( build_generic_test_scenario_instance_without_cleanup, @@ -385,77 +381,6 @@ def test_begin_test_step_unkown(): ) -def test_begin_dynamic_test_step(): - """Test the begin_dynamic_test_step base case""" - - gtsi = _build_generic_test_scenario_instance() - - doc = _TestStepDocumentation(name="test-doc", checks=[]) - - # Test that we must run begin_test_scenario and begin_test_case first - try: - gtsi.begin_dynamic_test_step(doc) - assert False # RuntimeError should have been called - except RuntimeError as e: - assert_runtime_is_state_error(e) - - gtsi.begin_test_scenario(build_context()) - - try: - gtsi.begin_dynamic_test_step(doc) - assert False # RuntimeError should have been called - except RuntimeError as e: - assert_runtime_is_state_error(e) - - gtsi.begin_test_case("test-case-1") - - gtsi.begin_dynamic_test_step(doc) - - -def test_begin_dynamic_test_step_twice(): - """Test that begin_dynamic_test_step cannot be called twice""" - - gtsi = _build_generic_test_scenario_instance() - doc = _TestStepDocumentation(name="test-doc", checks=[]) - gtsi.begin_test_scenario(build_context()) - gtsi.begin_test_case("test-case-1") - gtsi.begin_dynamic_test_step(doc) - - try: - gtsi.begin_dynamic_test_step(doc) - assert False # RuntimeError should have been called - except RuntimeError as e: - assert_runtime_is_state_error(e) - - -def test_begin_dynamic_test_step_after_ending(): - """Test that begin_dynamic_test_step can be called again after ending the step""" - - gtsi = _build_generic_test_scenario_instance() - doc = _TestStepDocumentation(name="test-doc", checks=[]) - advance_new_gtsi_to_case(gtsi) - gtsi.begin_dynamic_test_step(doc) - gtsi.end_test_step() - gtsi.begin_dynamic_test_step(doc) - - -def test_begin_dynamic_test_step_no_test_step(): - """Test that begin_dynamic_test_step can be called again after ending the step""" - - gtsi = _build_generic_test_scenario_instance() - doc = _TestStepDocumentation(name="test-doc", checks=[]) - gtsi.begin_test_scenario(build_context()) - gtsi.begin_test_case("test-case-2") # Test case 2 has no dynamic test step - try: - gtsi.begin_dynamic_test_step(doc) - assert False # RuntimeError should have been called - except RuntimeError as e: - assert ( - 'was instructed to begin_dynamic_test_step "test-doc" during test case "test-case-2", but there is no "Dynamic test step" declared in documentation' - in str(e) - ) - - def test_end_test_step(): """Test end_test_step in the basic case""" @@ -502,18 +427,6 @@ def test_end_test_step_twice(): assert_runtime_is_state_error(e) -def test_end_test_step_after_dynamic_test(): - """Ensure that end_test_step also works with dynamic test steps""" - - gtsi = _build_generic_test_scenario_instance() - advance_new_gtsi_to_case(gtsi) - - doc = _TestStepDocumentation(name="test-doc", checks=[], url=make_fake_url()) - - gtsi.begin_dynamic_test_step(doc) - gtsi.end_test_step() - - def test_end_test_step_report(): """Ensure end_test_step is returning a report about the test step""" @@ -532,23 +445,6 @@ def test_end_test_step_report(): assert_date_is_close_to_now(report.end_time.datetime) -def test_end_test_step_report_on_dynamic_steps(): - """Ensure end_test_step is returning a report about the test step""" - - gtsi = _build_generic_test_scenario_instance() - advance_new_gtsi_to_case(gtsi) - - url = make_fake_url() - - doc = _TestStepDocumentation(name="test-doc", checks=[], url=url) - - # We expect the TestStepDocumentation's URL to be returned in the report. - # We use the begin_dynamic_test_step to directly inject the testing doc - gtsi.begin_dynamic_test_step(doc) - report = gtsi.end_test_step() - assert report.documentation_url == url - - def test_end_test_case(): """Test the end_test_case in the basic case""" @@ -622,7 +518,7 @@ def test_go_to_cleanup(): # This is a list of step to do, in order, and if go_to_cleanup should works steps_and_result = [ - ("nop", False), + ("nop", True), ("begin_test_scenario", True), ("begin_test_case", True), ("begin_test_step", True), diff --git a/monitoring/uss_qualifier/scenarios/scenario_test/utils.py b/monitoring/uss_qualifier/scenarios/scenario_test/utils.py index 59acd1d937..c04f92ae27 100644 --- a/monitoring/uss_qualifier/scenarios/scenario_test/utils.py +++ b/monitoring/uss_qualifier/scenarios/scenario_test/utils.py @@ -6,7 +6,6 @@ from implicitdict import StringBasedDateTime from loguru import logger -from monitoring.deployment_manager.infrastructure import Context from monitoring.monitorlib.fetch import ( Query, QueryType, @@ -66,7 +65,8 @@ def run(self, context): pass class TestScenarioB(_TestScenario): - pass + def run(self, context): + pass class NotATestScenarioC: pass @@ -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 @@ -158,14 +161,17 @@ def __exit__(self, *args): logger.enable("monitoring.uss_qualifier.scenarios.scenario") -def build_context(stop_fast: bool = False) -> Context: +def build_context(stop_fast: bool = False): """Return a context that can be used with TestScenarios""" class DummyContext: - stop_fast = False + stop_fast_result = False + + def stop_fast(self, case_name: str, step_name: str, check_name: str) -> bool: + return self.stop_fast_result dc = DummyContext() - dc.stop_fast = stop_fast + dc.stop_fast_result = stop_fast return dc diff --git a/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.md b/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.md index 305b5ad24e..7defc0effe 100644 --- a/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.md +++ b/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.md @@ -66,6 +66,6 @@ uss_qualifier indicates to the flight planner a user intent to create a valid fl ## Cleanup -### 🛑 Successful flight deletion check +### ⚠️ Successful flight deletion check **[interuss.automated_testing.flight_planning.DeleteFlightSuccess](../../../requirements/interuss/automated_testing/flight_planning.md)** diff --git a/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.py b/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.py index ad8241c114..2751e997ca 100644 --- a/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.py +++ b/monitoring/uss_qualifier/scenarios/uspace/flight_auth/validation.py @@ -1,5 +1,3 @@ -import arrow - from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient from monitoring.monitorlib.clients.flight_planning.flight_info import ( AirspaceUsageState, @@ -13,7 +11,6 @@ FlightPlanStatus, PlanningActivityResult, ) -from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.uss_qualifier.resources.flight_planning import FlightIntentsResource from monitoring.uss_qualifier.resources.flight_planning.flight_intent_validation import ( ExpectedFlightIntent, @@ -32,8 +29,6 @@ class Validation(TestScenario): - times: dict[TimeDuringTest, Time] - invalid_flight_intents: list[FlightInfoTemplate] valid_flight_intent: FlightInfoTemplate ussp: FlightPlannerClient @@ -87,15 +82,9 @@ def __init__( self.invalid_flight_intents.append(templates[efi.intent_id]) def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - self.times[TimeDuringTest.TimeOfEvaluation] = Time(arrow.utcnow().datetime) - return flight_template.resolve(self.times) + return flight_template.resolve(self.time_context.evaluate_now()) def run(self, context: ExecutionContext): - self.times = { - TimeDuringTest.StartOfTestRun: Time(context.start_time), - TimeDuringTest.StartOfScenario: Time(arrow.utcnow().datetime), - } - self.begin_test_scenario(context) self.record_note("Planner", self.ussp.participant_id) @@ -120,7 +109,7 @@ def _attempt_invalid_flights(self) -> bool: for flight_intent_template in self.invalid_flight_intents: flight_intent = self.resolve_flight(flight_intent_template) - resp, _ = submit_flight( + submit_flight( scenario=self, success_check="Incorrectly planned", expected_results={ @@ -138,11 +127,12 @@ def _attempt_invalid_flights(self) -> bool: def _plan_valid_flight(self) -> bool: valid_flight_intent = self.resolve_flight(self.valid_flight_intent) - resp, _ = plan_flight( + resp, _, as_planned = plan_flight( self, self.ussp, valid_flight_intent, ) + # TODO(#1326): Validate that flight as planned still allows this scenario to proceed if resp is None: return False diff --git a/monitoring/uss_qualifier/scenarios/uspace/netrid/msl.py b/monitoring/uss_qualifier/scenarios/uspace/netrid/msl.py index ccc9d211fa..3e3774f216 100644 --- a/monitoring/uss_qualifier/scenarios/uspace/netrid/msl.py +++ b/monitoring/uss_qualifier/scenarios/uspace/netrid/msl.py @@ -6,7 +6,7 @@ ) from monitoring.monitorlib.fetch import Query, QueryType -from monitoring.monitorlib.geo import egm96_geoid_offset +from monitoring.monitorlib.geo import egm96_geoid_offset, egm2008_geoid_offset from monitoring.uss_qualifier.configurations.configuration import ParticipantID from monitoring.uss_qualifier.resources.netrid import NetRIDObserversResource from monitoring.uss_qualifier.scenarios.astm.netrid.common.nominal_behavior import ( @@ -110,12 +110,31 @@ def _evaluate_msl_altitude(self, queries: list[Query]): and flight.most_recent_position is not None ): with self.check("MSL altitude is correct", participant_id) as check: - geoid_offset = egm96_geoid_offset( - s2sphere.LatLng.from_degrees( - flight.most_recent_position.lat, - flight.most_recent_position.lng, + if ( + flight.most_recent_position.msl_alt.reference_datum + == AltitudeReference.EGM96 + ): + geoid_offset = egm96_geoid_offset( + s2sphere.LatLng.from_degrees( + flight.most_recent_position.lat, + flight.most_recent_position.lng, + ) ) - ) + elif ( + flight.most_recent_position.msl_alt.reference_datum + == AltitudeReference.EGM2008 + ): + geoid_offset = egm2008_geoid_offset( + s2sphere.LatLng.from_degrees( + flight.most_recent_position.lat, + flight.most_recent_position.lng, + ) + ) + else: + raise Exception( + "Internal error: ACCEPTABLE_DATUMS of netrid/msl.py don't match the datum we can check" + ) + expected_msl_alt = ( flight.most_recent_position.alt - geoid_offset ) diff --git a/monitoring/uss_qualifier/scenarios/versioning/get_system_versions.md b/monitoring/uss_qualifier/scenarios/versioning/get_system_versions.md index 71d1c6260b..c5df8bcb71 100644 --- a/monitoring/uss_qualifier/scenarios/versioning/get_system_versions.md +++ b/monitoring/uss_qualifier/scenarios/versioning/get_system_versions.md @@ -18,6 +18,6 @@ A [`SystemIdentityResource`](../../resources/versioning/system_identity.py) indi Each version provider is queried for the version of its system (identified by system_identity) and the result is recorded as a note in the report. -#### 🛑 Valid response check +#### ⚠️ Valid response check If a valid response is not received from a version provider, they will have failed to meet **[versioning.ReportSystemVersion](../../requirements/versioning.md)**. diff --git a/monitoring/uss_qualifier/scripts/check-netrid-cross-references.sh b/monitoring/uss_qualifier/scripts/check-netrid-cross-references.sh index 84c585c7c3..3c69476648 100755 --- a/monitoring/uss_qualifier/scripts/check-netrid-cross-references.sh +++ b/monitoring/uss_qualifier/scripts/check-netrid-cross-references.sh @@ -1,7 +1,12 @@ #!/usr/bin/env bash +set -eo pipefail + +error_found=false # It's good practice to initialize your flag + # Check for 'v19' references in netrid v22a scenario documents -filesv22a=$(find ./scenarios/astm/netrid/v22a -name '*.md' -exec grep -l 'v19' {} +) +# '|| true' to prevent 'set -e' from exiting if grep finds nothing +filesv22a=$(find ./scenarios/astm/netrid/v22a -name '*.md' -exec grep -l 'v19' {} + || true) if [ -n "$filesv22a" ]; then echo "Error: Found netrid v22a scenario document containing a 'v19' reference:" echo "$filesv22a" @@ -9,7 +14,8 @@ if [ -n "$filesv22a" ]; then fi # Check for 'v22a' references in netrid v19 scenario documents -filesv19=$(find ./scenarios/astm/netrid/v19 -name '*.md' -exec grep -l 'v22a' {} +) +# '|| true' to prevent 'set -e' from exiting if grep finds nothing +filesv19=$(find ./scenarios/astm/netrid/v19 -name '*.md' -exec grep -l 'v22a' {} + || true) if [ -n "$filesv19" ]; then echo "Error: Found netrid v19 scenario document containing a 'v22a' reference:" echo "$filesv19" @@ -20,5 +26,7 @@ fi if [ "$error_found" = true ]; then exit 1 fi + # Otherwise all is good +echo "All checks passed." exit 0 diff --git a/monitoring/uss_qualifier/scripts/format_test_documentation.sh b/monitoring/uss_qualifier/scripts/format_test_documentation.sh index e48f861a7b..795da2717f 100755 --- a/monitoring/uss_qualifier/scripts/format_test_documentation.sh +++ b/monitoring/uss_qualifier/scripts/format_test_documentation.sh @@ -15,7 +15,7 @@ cd "${BASEDIR}/../../.." || exit 1 ( cd monitoring || exit 1 -make image +make image-dev ) # shellcheck disable=SC2086 @@ -24,5 +24,5 @@ docker run --name test_documentation_formatter \ -u "$(id -u):$(id -g)" \ -v "$(pwd):/app" \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ - interuss/monitoring \ + interuss/monitoring-dev \ uss_qualifier/scripts/in_container/format_test_documentation.sh diff --git a/monitoring/uss_qualifier/scripts/format_test_suite_docs.sh b/monitoring/uss_qualifier/scripts/format_test_suite_docs.sh index 9e0adfd073..2daffb1490 100755 --- a/monitoring/uss_qualifier/scripts/format_test_suite_docs.sh +++ b/monitoring/uss_qualifier/scripts/format_test_suite_docs.sh @@ -15,7 +15,7 @@ cd "${BASEDIR}/../../.." || exit 1 ( cd monitoring || exit 1 -make image +make image-dev ) # shellcheck disable=SC2086 @@ -23,5 +23,5 @@ docker run --name test_suite_docs_formatter \ --rm \ -v "$(pwd):/app" \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ - interuss/monitoring \ + interuss/monitoring-dev \ uss_qualifier/scripts/in_container/format_test_suite_docs.sh "$@" diff --git a/monitoring/uss_qualifier/scripts/in_container/format_test_documentation.sh b/monitoring/uss_qualifier/scripts/in_container/format_test_documentation.sh index 1512b8e31e..2b668de43e 100755 --- a/monitoring/uss_qualifier/scripts/in_container/format_test_documentation.sh +++ b/monitoring/uss_qualifier/scripts/in_container/format_test_documentation.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script is intended to be called from within a Docker container running # mock_uss via the interuss/monitoring image. In that context, this script is # the entrypoint into the test documentation formatting tool. diff --git a/monitoring/uss_qualifier/scripts/in_container/format_test_suite_docs.sh b/monitoring/uss_qualifier/scripts/in_container/format_test_suite_docs.sh index b2e7a81313..cdffcf62d8 100755 --- a/monitoring/uss_qualifier/scripts/in_container/format_test_suite_docs.sh +++ b/monitoring/uss_qualifier/scripts/in_container/format_test_suite_docs.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script is intended to be called from within a Docker container running # mock_uss via the interuss/monitoring image. In that context, this script is # the entrypoint into the test definition validation tool. diff --git a/monitoring/uss_qualifier/scripts/in_container/run_unit_tests.sh b/monitoring/uss_qualifier/scripts/in_container/run_unit_tests.sh index 19e648923c..b7d378511e 100755 --- a/monitoring/uss_qualifier/scripts/in_container/run_unit_tests.sh +++ b/monitoring/uss_qualifier/scripts/in_container/run_unit_tests.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script is intended to be called from within a Docker container running # mock_uss via the interuss/monitoring image. In that context, this script is # the entrypoint into the test definition validation tool. diff --git a/monitoring/uss_qualifier/scripts/in_container/validate_test_definitions.sh b/monitoring/uss_qualifier/scripts/in_container/validate_test_definitions.sh index 26d6d80d2d..c4fd61686f 100755 --- a/monitoring/uss_qualifier/scripts/in_container/validate_test_definitions.sh +++ b/monitoring/uss_qualifier/scripts/in_container/validate_test_definitions.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script is intended to be called from within a Docker container running # mock_uss via the interuss/monitoring image. In that context, this script is # the entrypoint into the test definition validation tool. diff --git a/monitoring/uss_qualifier/scripts/report_analyzer.py b/monitoring/uss_qualifier/scripts/report_analyzer.py index 48b3e3ea01..0e9348c09d 100644 --- a/monitoring/uss_qualifier/scripts/report_analyzer.py +++ b/monitoring/uss_qualifier/scripts/report_analyzer.py @@ -58,7 +58,7 @@ def main(): for a in r.report.test_suite.actions: print("Types of actions (test_suite, test_scenario, action_generator): ") - print(a._get_applicable_report()) + print(a.get_action_type_name()) suite_reports = { subr.test_suite.name: subr.test_suite diff --git a/monitoring/uss_qualifier/scripts/run_unit_tests.sh b/monitoring/uss_qualifier/scripts/run_unit_tests.sh index 5ed3c66d8a..52d92e0bc0 100755 --- a/monitoring/uss_qualifier/scripts/run_unit_tests.sh +++ b/monitoring/uss_qualifier/scripts/run_unit_tests.sh @@ -15,7 +15,7 @@ cd "${BASEDIR}/../../.." || exit 1 ( cd monitoring || exit 1 -make image +make image-dev ) # shellcheck disable=SC2086 @@ -24,5 +24,5 @@ docker run --name uss_qualifier_unit_test \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ -v "$(pwd):/app" \ -v /var/run/docker.sock:/var/run/docker.sock \ - interuss/monitoring \ + interuss/monitoring-dev \ uss_qualifier/scripts/in_container/run_unit_tests.sh diff --git a/monitoring/uss_qualifier/scripts/validate_test_definitions.sh b/monitoring/uss_qualifier/scripts/validate_test_definitions.sh index dfdb99a042..a84ac239a9 100755 --- a/monitoring/uss_qualifier/scripts/validate_test_definitions.sh +++ b/monitoring/uss_qualifier/scripts/validate_test_definitions.sh @@ -15,12 +15,12 @@ cd "${BASEDIR}/../../.." || exit 1 ( cd monitoring || exit 1 -make image +make image-dev ) # shellcheck disable=SC2086 docker run --name test_definition_validator \ --rm \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ - interuss/monitoring \ + interuss/monitoring-dev \ uss_qualifier/scripts/in_container/validate_test_definitions.sh diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md index 3c0d498188..e70e28d836 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md @@ -8,13 +8,12 @@ 2. Scenario: [ASTM NetRID Display Provider behavior](../../../scenarios/astm/netrid/v19/dp_behavior.md) ([`scenarios.astm.netrid.v19.DisplayProviderBehavior`](../../../scenarios/astm/netrid/v19/dp_behavior.py)) 3. Scenario: [ASTM NetRID networked UAS disconnection](../../../scenarios/astm/netrid/v19/networked_uas_disconnect.md) ([`scenarios.astm.netrid.v19.NetworkedUASDisconnect`](../../../scenarios/astm/netrid/v19/networked_uas_disconnect.py)) 4. Scenario: [ASTM NetRID Service Provider operator notification on missing fields](../../../scenarios/astm/netrid/v19/sp_operator_notify_missing_fields.md) ([`scenarios.astm.netrid.v19.SpOperatorNotifyMissingFields`](../../../scenarios/astm/netrid/v19/sp_operator_notify_missing_fields.py)) -5. Action generator: [`action_generators.astm.f3411.ForEachDSS`](../../../action_generators/astm/f3411/for_each_dss.py) +5. Scenario: [ASTM NetRID Service Provider notification behavior](../../../scenarios/astm/netrid/v19/sp_notification_behavior.md) ([`scenarios.astm.netrid.v19.ServiceProviderNotificationBehavior`](../../../scenarios/astm/netrid/v19/sp_notification_behavior.py)) +6. Scenario: [ASTM NetRID nominal behavior](../../../scenarios/astm/netrid/v19/nominal_behavior.md) ([`scenarios.astm.netrid.v19.NominalBehavior`](../../../scenarios/astm/netrid/v19/nominal_behavior.py)) +7. Scenario: [ASTM NetRID SP clients misbehavior handling](../../../scenarios/astm/netrid/v19/misbehavior.md) ([`scenarios.astm.netrid.v19.Misbehavior`](../../../scenarios/astm/netrid/v19/misbehavior.py)) +8. Action generator: [`action_generators.astm.f3411.ForEachDSS`](../../../action_generators/astm/f3411/for_each_dss.py) 1. Suite: [DSS testing for ASTM NetRID F3411-19](f3411_19/dss_probing.md) ([`suites.astm.netrid.f3411_19.dss_probing`](f3411_19/dss_probing.yaml)) -6. Scenario: [ASTM NetRID Service Provider notification behavior](../../../scenarios/astm/netrid/v19/sp_notification_behavior.md) ([`scenarios.astm.netrid.v19.ServiceProviderNotificationBehavior`](../../../scenarios/astm/netrid/v19/sp_notification_behavior.py)) -7. Scenario: [ASTM NetRID nominal behavior](../../../scenarios/astm/netrid/v19/nominal_behavior.md) ([`scenarios.astm.netrid.v19.NominalBehavior`](../../../scenarios/astm/netrid/v19/nominal_behavior.py)) -8. Scenario: [ASTM NetRID SP clients misbehavior handling](../../../scenarios/astm/netrid/v19/misbehavior.md) ([`scenarios.astm.netrid.v19.Misbehavior`](../../../scenarios/astm/netrid/v19/misbehavior.py)) -9. Scenario: [ASTM NetRID: Operator interactions](../../../scenarios/astm/netrid/v19/operator_interactions.md) ([`scenarios.astm.netrid.v19.OperatorInteractions`](../../../scenarios/astm/netrid/v19/operator_interactions.py)) -10. Scenario: [ASTM F3411-19 NetRID aggregate checks](../../../scenarios/astm/netrid/v19/aggregate_checks.md) ([`scenarios.astm.netrid.v19.AggregateChecks`](../../../scenarios/astm/netrid/v19/aggregate_checks.py)) +9. Scenario: [ASTM F3411-19 NetRID aggregate checks](../../../scenarios/astm/netrid/v19/aggregate_checks.md) ([`scenarios.astm.netrid.v19.AggregateChecks`](../../../scenarios/astm/netrid/v19/aggregate_checks.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -26,7 +25,7 @@ Checked in - astm
    .f3411
    .v19
    + astm
    .f3411
    .v19
    DSS0010 Implemented ASTM NetRID DSS: Token Validation @@ -228,13 +227,13 @@ NET0030 - Implemented + TODO - ASTM NetRID Service Provider operator notification on missing fields
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification on missing fields NET0040 - Implemented + TODO - ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification under slow update rate NET0210 @@ -498,23 +497,18 @@ NET0610 - Implemented + TODO - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior
    ASTM NetRID: Operator interactions - - - NET0620 - TODO - ASTM NetRID: Operator interactions + Implemented + ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,1 Implemented - ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,2 Implemented - ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID nominal behavior + ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0730 @@ -535,7 +529,7 @@ UpsertTestResult TODO - ASTM NetRID Service Provider notification behavior
    ASTM NetRID Service Provider operator notification on missing fields
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider notification behavior
    ASTM NetRID Service Provider operator notification on missing fields
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior UpsertTestSuccess @@ -567,7 +561,7 @@ SearchISAs Implemented - ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior interuss
    .mock_uss
    .hosted_instance
    diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml index 41763eb640..f5cd1450a8 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.yaml @@ -48,6 +48,34 @@ actions: service_providers: service_providers evaluation_configuration: evaluation_configuration on_failure: Continue + - test_scenario: + scenario_type: scenarios.astm.netrid.v19.ServiceProviderNotificationBehavior + resources: + flights_data: flights_data + service_providers: service_providers + mock_uss: mock_uss_dp + id_generator: id_generator + dss_pool: dss_instances + uss_identification: uss_identification + on_failure: Continue + - test_scenario: + scenario_type: scenarios.astm.netrid.v19.NominalBehavior + resources: + flights_data: flights_data + service_providers: service_providers + observers: observers + evaluation_configuration: evaluation_configuration + dss_pool: dss_instances + on_failure: Continue + - test_scenario: + scenario_type: scenarios.astm.netrid.v19.Misbehavior + resources: + flights_data: flights_data + service_providers: service_providers + observers: observers + evaluation_configuration: evaluation_configuration + dss_pool: dss_instances + on_failure: Continue - action_generator: generator_type: action_generators.astm.f3411.ForEachDSS resources: @@ -77,37 +105,6 @@ actions: dss_instances_source: dss_instances dss_instance_id: dss on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v19.ServiceProviderNotificationBehavior - resources: - flights_data: flights_data - service_providers: service_providers - mock_uss: mock_uss_dp - id_generator: id_generator - dss_pool: dss_instances - on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v19.NominalBehavior - resources: - flights_data: flights_data - service_providers: service_providers - observers: observers - evaluation_configuration: evaluation_configuration - dss_pool: dss_instances - on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v19.Misbehavior - resources: - flights_data: flights_data - service_providers: service_providers - observers: observers - evaluation_configuration: evaluation_configuration - dss_pool: dss_instances - on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v19.OperatorInteractions - resources: {} - on_failure: Continue - test_scenario: scenario_type: scenarios.astm.netrid.v19.AggregateChecks resources: diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md index 088b3638bb..ceef49432e 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md @@ -4,16 +4,16 @@ ## [Actions](../../README.md#actions) -1. Scenario: [ASTM NetRID Service Provider operator notification under slow update rate](../../../scenarios/astm/netrid/v22a/sp_operator_notify_slow_update.md) ([`scenarios.astm.netrid.v22a.ServiceProviderNotifiesSlowUpdates`](../../../scenarios/astm/netrid/v22a/sp_operator_notify_slow_update.py)) -2. Scenario: [ASTM NetRID Display Provider behavior](../../../scenarios/astm/netrid/v22a/dp_behavior.md) ([`scenarios.astm.netrid.v22a.DisplayProviderBehavior`](../../../scenarios/astm/netrid/v22a/dp_behavior.py)) -3. Scenario: [ASTM NetRID networked UAS disconnection](../../../scenarios/astm/netrid/v22a/networked_uas_disconnect.md) ([`scenarios.astm.netrid.v22a.NetworkedUASDisconnect`](../../../scenarios/astm/netrid/v22a/networked_uas_disconnect.py)) -4. Scenario: [ASTM NetRID Service Provider operator notification on missing fields](../../../scenarios/astm/netrid/v22a/sp_operator_notify_missing_fields.md) ([`scenarios.astm.netrid.v22a.SpOperatorNotifyMissingFields`](../../../scenarios/astm/netrid/v22a/sp_operator_notify_missing_fields.py)) -5. Action generator: [`action_generators.astm.f3411.ForEachDSS`](../../../action_generators/astm/f3411/for_each_dss.py) - 1. Suite: [DSS testing for ASTM NetRID F3411-22a](f3411_22a/dss_probing.md) ([`suites.astm.netrid.f3411_22a.dss_probing`](f3411_22a/dss_probing.yaml)) +1. Scenario: [Get system versions](../../../scenarios/versioning/get_system_versions.md) ([`scenarios.versioning.GetSystemVersions`](../../../scenarios/versioning/get_system_versions.py)) +2. Scenario: [ASTM NetRID Service Provider operator notification under slow update rate](../../../scenarios/astm/netrid/v22a/sp_operator_notify_slow_update.md) ([`scenarios.astm.netrid.v22a.ServiceProviderNotifiesSlowUpdates`](../../../scenarios/astm/netrid/v22a/sp_operator_notify_slow_update.py)) +3. Scenario: [ASTM NetRID Display Provider behavior](../../../scenarios/astm/netrid/v22a/dp_behavior.md) ([`scenarios.astm.netrid.v22a.DisplayProviderBehavior`](../../../scenarios/astm/netrid/v22a/dp_behavior.py)) +4. Scenario: [ASTM NetRID networked UAS disconnection](../../../scenarios/astm/netrid/v22a/networked_uas_disconnect.md) ([`scenarios.astm.netrid.v22a.NetworkedUASDisconnect`](../../../scenarios/astm/netrid/v22a/networked_uas_disconnect.py)) +5. Scenario: [ASTM NetRID Service Provider operator notification on missing fields](../../../scenarios/astm/netrid/v22a/sp_operator_notify_missing_fields.md) ([`scenarios.astm.netrid.v22a.SpOperatorNotifyMissingFields`](../../../scenarios/astm/netrid/v22a/sp_operator_notify_missing_fields.py)) 6. Scenario: [ASTM NetRID Service Provider notification behavior](../../../scenarios/astm/netrid/v22a/sp_notification_behavior.md) ([`scenarios.astm.netrid.v22a.ServiceProviderNotificationBehavior`](../../../scenarios/astm/netrid/v22a/sp_notification_behavior.py)) 7. Scenario: [ASTM NetRID nominal behavior](../../../scenarios/astm/netrid/v22a/nominal_behavior.md) ([`scenarios.astm.netrid.v22a.NominalBehavior`](../../../scenarios/astm/netrid/v22a/nominal_behavior.py)) 8. Scenario: [ASTM NetRID SP clients misbehavior handling](../../../scenarios/astm/netrid/v22a/misbehavior.md) ([`scenarios.astm.netrid.v22a.Misbehavior`](../../../scenarios/astm/netrid/v22a/misbehavior.py)) -9. Scenario: [ASTM NetRID: Operator interactions](../../../scenarios/astm/netrid/v22a/operator_interactions.md) ([`scenarios.astm.netrid.v22a.OperatorInteractions`](../../../scenarios/astm/netrid/v22a/operator_interactions.py)) +9. Action generator: [`action_generators.astm.f3411.ForEachDSS`](../../../action_generators/astm/f3411/for_each_dss.py) + 1. Suite: [DSS testing for ASTM NetRID F3411-22a](f3411_22a/dss_probing.md) ([`suites.astm.netrid.f3411_22a.dss_probing`](f3411_22a/dss_probing.yaml)) 10. Scenario: [ASTM F3411-22a NetRID aggregate checks](../../../scenarios/astm/netrid/v22a/aggregate_checks.md) ([`scenarios.astm.netrid.v22a.AggregateChecks`](../../../scenarios/astm/netrid/v22a/aggregate_checks.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -26,7 +26,7 @@ Checked in - astm
    .f3411
    .v22a
    + astm
    .f3411
    .v22a
    DSS0010 Implemented ASTM NetRID DSS: Token Validation @@ -233,13 +233,13 @@ NET0030 - Implemented + TODO - ASTM NetRID Service Provider operator notification on missing fields
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification on missing fields NET0040 - Implemented + TODO - ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification under slow update rate NET0210 @@ -429,7 +429,7 @@ NET0340 Implemented - ASTM NetRID nominal behavior + ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0420 @@ -613,18 +613,13 @@ NET0610 - Implemented + TODO - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior
    ASTM NetRID: Operator interactions - - - NET0620 - TODO - ASTM NetRID: Operator interactions + Implemented + ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,1 Implemented - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,2 @@ -687,7 +682,7 @@ SearchISAs Implemented - ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior interuss
    .mock_uss
    .hosted_instance
    @@ -695,4 +690,10 @@ Implemented ASTM NetRID Display Provider behavior
    ASTM NetRID Service Provider notification behavior + + versioning + ReportSystemVersion + Implemented + Get system versions + diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml index 470b4be1da..76ea72b4aa 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.yaml @@ -1,8 +1,10 @@ name: ASTM F3411-22a resources: + test_env_version_providers: resources.versioning.VersionProvidersResource? + system_identity: resources.versioning.SystemIdentityResource? flights_data: resources.netrid.FlightDataResource service_providers: resources.netrid.NetRIDServiceProviders? - observers: resources.netrid.NetRIDObserversResource + observers: resources.netrid.NetRIDObserversResource? mock_uss_dp: resources.interuss.mock_uss.client.MockUSSResource? mock_uss_sp: resources.interuss.mock_uss.client.MockUSSResource? evaluation_configuration: resources.netrid.EvaluationConfigurationResource @@ -16,6 +18,12 @@ resources: test_exclusions: resources.dev.TestExclusionsResource? uss_identification: resources.interuss.uss_identification.USSIdentificationResource? actions: + - test_scenario: + scenario_type: scenarios.versioning.GetSystemVersions + resources: + version_providers: test_env_version_providers + system_identity: system_identity + on_failure: Continue - test_scenario: scenario_type: scenarios.astm.netrid.v22a.ServiceProviderNotifiesSlowUpdates resources: @@ -48,6 +56,33 @@ actions: service_providers: service_providers evaluation_configuration: evaluation_configuration on_failure: Continue + - test_scenario: + scenario_type: scenarios.astm.netrid.v22a.ServiceProviderNotificationBehavior + resources: + flights_data: flights_data + service_providers: service_providers + mock_uss: mock_uss_dp + id_generator: id_generator + dss_pool: dss_instances + uss_identification: uss_identification + on_failure: Continue + - test_scenario: + scenario_type: scenarios.astm.netrid.v22a.NominalBehavior + resources: + flights_data: flights_data + service_providers: service_providers + observers: observers? + evaluation_configuration: evaluation_configuration + dss_pool: dss_instances? + on_failure: Continue + - test_scenario: + scenario_type: scenarios.astm.netrid.v22a.Misbehavior + resources: + flights_data: flights_data + service_providers: service_providers + evaluation_configuration: evaluation_configuration + dss_pool: dss_instances + on_failure: Continue - action_generator: generator_type: action_generators.astm.f3411.ForEachDSS resources: @@ -78,42 +113,11 @@ actions: dss_instances_source: dss_instances dss_instance_id: dss on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v22a.ServiceProviderNotificationBehavior - resources: - flights_data: flights_data - service_providers: service_providers - mock_uss: mock_uss_dp - id_generator: id_generator - dss_pool: dss_instances - on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v22a.NominalBehavior - resources: - flights_data: flights_data - service_providers: service_providers - observers: observers - evaluation_configuration: evaluation_configuration - dss_pool: dss_instances? - on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v22a.Misbehavior - resources: - flights_data: flights_data - service_providers: service_providers - observers: observers - evaluation_configuration: evaluation_configuration - dss_pool: dss_instances - on_failure: Continue - - test_scenario: - scenario_type: scenarios.astm.netrid.v22a.OperatorInteractions - resources: {} - on_failure: Continue - test_scenario: scenario_type: scenarios.astm.netrid.v22a.AggregateChecks resources: service_providers: service_providers - observers: observers + observers: observers? dss_instances: dss_instances test_exclusions: test_exclusions? participant_verifiable_capabilities: @@ -203,6 +207,32 @@ participant_verifiable_capabilities: checked: requirement_sets: - "astm.f3411.v22a.service_provider#Operator Position provider" + - id: service_provider_operator_altitude_provider + name: NetRID Service Provider providing operator altitude data field + description: Participant fulfills testable requirements necessary to provide operator altitude data field according to F3411-22a. + verification_condition: + all_conditions: + conditions: + - capability_verified: + capability_ids: + - service_provider + - requirements_checked: + checked: + requirement_sets: + - "astm.f3411.v22a.service_provider#Operator Altitude provider" + - id: service_provider_operator_location_type_provider + name: NetRID Service Provider providing operator location type data field + description: Participant fulfills testable requirements necessary to provide operator location type data field according to F3411-22a. + verification_condition: + all_conditions: + conditions: + - capability_verified: + capability_ids: + - service_provider + - requirements_checked: + checked: + requirement_sets: + - "astm.f3411.v22a.service_provider#Operator Location Type provider" - id: service_provider_operational_status_provider name: NetRID Service Provider providing operational status data field description: Participant fulfills testable requirements necessary to provide operational status data field according to F3411-22a. diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md index 48b9cad099..e394ca376d 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md @@ -24,6 +24,7 @@ 18. Scenario: [ASTM UTM DSS: Direct datastore access](../../../scenarios/astm/utm/dss/datastore_access.md) ([`scenarios.astm.utm.dss.DatastoreAccess`](../../../scenarios/astm/utm/dss/datastore_access.py)) 19. Scenario: [OVN Request Optional Extension to ASTM F3548-21](../../../scenarios/interuss/ovn_request/dss_ovn_request.md) ([`scenarios.interuss.ovn_request.DSSOVNRequest`](../../../scenarios/interuss/ovn_request/dss_ovn_request.py)) 20. Scenario: [ASTM SCD DSS: Report](../../../scenarios/astm/utm/dss/report.md) ([`scenarios.astm.utm.dss.Report`](../../../scenarios/astm/utm/dss/report.py)) +21. Scenario: [ASTM Availability DSS: USS Availability Mutation](../../../scenarios/astm/utm/dss/uss_availability_mutation.md) ([`scenarios.astm.utm.dss.UssAvailabilityMutation`](../../../scenarios/astm/utm/dss/uss_availability_mutation.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -35,7 +36,7 @@ Checked in - astm
    .f3548
    .v21
    + astm
    .f3548
    .v21
    DSS0005,1 Implemented 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
    OVN Request Optional Extension to ASTM F3548-21 @@ -73,7 +74,7 @@ DSS0100,1 Implemented - ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization + ASTM Availability DSS: USS Availability Mutation
    ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization DSS0100,2 @@ -223,12 +224,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml index 7de02e3329..75079b7949 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml @@ -146,3 +146,8 @@ actions: scenario_type: scenarios.astm.utm.dss.Report resources: dss: dss + - test_scenario: + scenario_type: scenarios.astm.utm.dss.UssAvailabilityMutation + resources: + dss: dss + client_identity: utm_client_identity diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index 7ec2ebb93b..0104f4869d 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -9,25 +9,23 @@ 3. Action generator: [`action_generators.astm.f3548.ForEachDSS`](../../../action_generators/astm/f3548/for_each_dss.py) 1. Suite: [DSS testing for ASTM F3548-21](dss_probing.md) ([`suites.astm.utm.dss_probing`](dss_probing.yaml)) 4. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) - 1. Scenario: [Solo happy path](../../../scenarios/astm/utm/nominal_planning/solo_happy_path.md) ([`scenarios.astm.utm.nominal_planning.solo_happy_path.SoloHappyPath`](../../../scenarios/astm/utm/nominal_planning/solo_happy_path.py)) -5. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Validation of operational intents](../../../scenarios/astm/utm/flight_intent_validation/flight_intent_validation.md) ([`scenarios.astm.utm.FlightIntentValidation`](../../../scenarios/astm/utm/flight_intent_validation/flight_intent_validation.py)) -6. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) +5. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Nominal planning: conflict with higher priority](../../../scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md) ([`scenarios.astm.utm.ConflictHigherPriority`](../../../scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py)) -7. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) +6. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Nominal planning: not permitted conflict with equal priority](../../../scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.md) ([`scenarios.astm.utm.ConflictEqualPriorityNotPermitted`](../../../scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py)) -8. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) +7. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Data Validation of GET operational intents by USS](../../../scenarios/astm/utm/data_exchange_validation/get_op_data_validation.md) ([`scenarios.astm.utm.data_exchange_validation.GetOpResponseDataValidationByUSS`](../../../scenarios/astm/utm/data_exchange_validation/get_op_data_validation.py)) -9. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) +8. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Awareness of relevant operational intents](../../../scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.md) ([`scenarios.astm.utm.subscription_notifications.ReceiveNotificationsForAwareness`](../../../scenarios/astm/utm/subscription_notifications/receive_notifications_for_awareness/receive_notifications_for_awareness.py)) -10. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) +9. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Off-Nominal planning: down USS](../../../scenarios/astm/utm/off_nominal_planning/down_uss.md) ([`scenarios.astm.utm.DownUSS`](../../../scenarios/astm/utm/off_nominal_planning/down_uss.py)) -11. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) +10. Action generator: [`action_generators.flight_planning.FlightPlannerCombinations`](../../../action_generators/flight_planning/planner_combinations.py) 1. Scenario: [Off-Nominal planning: down USS with equal priority conflicts not permitted](../../../scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.md) ([`scenarios.astm.utm.DownUSSEqualPriorityNotPermitted`](../../../scenarios/astm/utm/off_nominal_planning/down_uss_equal_priority_not_permitted.py)) -12. Scenario: [ASTM F3548 flight planners preparation](../../../scenarios/astm/utm/prep_planners.md) ([`scenarios.astm.utm.PrepareFlightPlanners`](../../../scenarios/astm/utm/prep_planners.py)) -13. Scenario: [ASTM F3548 makeUssReport](../../../scenarios/astm/utm/make_uss_report.md) ([`scenarios.astm.utm.make_uss_report.MakeUssReport`](../../../scenarios/astm/utm/make_uss_report.py)) -14. Scenario: [ASTM F3548-21 evaluate system versions](../../../scenarios/astm/utm/versioning/evaluate_system_versions.md) ([`scenarios.astm.utm.versioning.evaluate_system_versions.EvaluateSystemVersions`](../../../scenarios/astm/utm/versioning/evaluate_system_versions.py)) -15. Scenario: [ASTM F3548 UTM aggregate checks](../../../scenarios/astm/utm/aggregate_checks.md) ([`scenarios.astm.utm.AggregateChecks`](../../../scenarios/astm/utm/aggregate_checks.py)) +11. Scenario: [ASTM F3548 flight planners preparation](../../../scenarios/astm/utm/prep_planners.md) ([`scenarios.astm.utm.PrepareFlightPlanners`](../../../scenarios/astm/utm/prep_planners.py)) +12. Scenario: [ASTM F3548 makeUssReport](../../../scenarios/astm/utm/make_uss_report.md) ([`scenarios.astm.utm.make_uss_report.MakeUssReport`](../../../scenarios/astm/utm/make_uss_report.py)) +13. Scenario: [ASTM F3548-21 evaluate system versions](../../../scenarios/astm/utm/versioning/evaluate_system_versions.md) ([`scenarios.astm.utm.versioning.evaluate_system_versions.EvaluateSystemVersions`](../../../scenarios/astm/utm/versioning/evaluate_system_versions.py)) +14. Scenario: [ASTM F3548 UTM aggregate checks](../../../scenarios/astm/utm/aggregate_checks.md) ([`scenarios.astm.utm.AggregateChecks`](../../../scenarios/astm/utm/aggregate_checks.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -39,15 +37,15 @@ Checked in - astm
    .f3548
    .v21
    + 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
    Solo happy path + 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 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Solo happy path
    Validation of operational intents + 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Validation of operational intents DSS0005,3 @@ -77,7 +75,7 @@ DSS0100,1 Implemented - ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM Availability DSS: USS Availability Mutation
    ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0100,2 @@ -227,12 +225,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a @@ -322,17 +330,17 @@ OPIN0015 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0020 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0025 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0030 @@ -432,17 +440,17 @@ USS0005 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,1 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,2 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,3 @@ -463,22 +471,22 @@ DeleteFlightSuccess Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents ExpectedBehavior Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents FlightCoveredByOperationalIntent Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents ImplementAPI Implemented - ASTM F3548 flight planners preparation + ASTM F3548 flight planners preparation
    Nominal planning: conflict with higher priority Readiness diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml index 367ca62f1b..c2e001cf08 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml @@ -76,25 +76,6 @@ actions: dss_instances_source: dss_instances dss_instance_id: dss on_failure: Continue -- action_generator: - generator_type: action_generators.flight_planning.FlightPlannerCombinations - resources: - flight_planners: flight_planners - flight_intents: conflicting_flights - dss: dss - specification: - action_to_repeat: - test_scenario: - scenario_type: scenarios.astm.utm.nominal_planning.solo_happy_path.SoloHappyPath - resources: - flight_intents: flight_intents - tested_uss: tested_uss - dss: dss - on_failure: Continue - flight_planners_source: flight_planners - roles: - - tested_uss - on_failure: Continue - action_generator: generator_type: action_generators.flight_planning.FlightPlannerCombinations resources: diff --git a/monitoring/uss_qualifier/suites/astm/utm/prod_probe.md b/monitoring/uss_qualifier/suites/astm/utm/prod_probe.md index b03a723c1a..f7ac3ed79e 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/prod_probe.md +++ b/monitoring/uss_qualifier/suites/astm/utm/prod_probe.md @@ -17,7 +17,7 @@ Checked in - astm
    .f3548
    .v21
    + astm
    .f3548
    .v21
    DSS0005,1 Implemented ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: Operational Intent Reference Synchronization
    ASTM SCD DSS: Subscription and entity deletion interaction
    ASTM SCD DSS: Subscription and entity interaction @@ -190,12 +190,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a diff --git a/monitoring/uss_qualifier/suites/astm/utm/prod_probe_suite1.md b/monitoring/uss_qualifier/suites/astm/utm/prod_probe_suite1.md index 494149482e..7834e13cdf 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/prod_probe_suite1.md +++ b/monitoring/uss_qualifier/suites/astm/utm/prod_probe_suite1.md @@ -25,7 +25,7 @@ Defined in [parent suite](prod_probe.md) [`suites.astm.utm.prod_probe`](./prod_p Checked in - astm
    .f3548
    .v21
    + astm
    .f3548
    .v21
    DSS0005,1 Implemented ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: Operational Intent Reference Synchronization
    ASTM SCD DSS: Subscription and entity deletion interaction
    ASTM SCD DSS: Subscription and entity interaction @@ -198,12 +198,22 @@ Defined in [parent suite](prod_probe.md) [`suites.astm.utm.prod_probe`](./prod_p DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a diff --git a/monitoring/uss_qualifier/suites/definitions.py b/monitoring/uss_qualifier/suites/definitions.py index 1ba3caddbd..ba30f96c11 100644 --- a/monitoring/uss_qualifier/suites/definitions.py +++ b/monitoring/uss_qualifier/suites/definitions.py @@ -1,8 +1,8 @@ from __future__ import annotations -from enum import Enum +from enum import StrEnum -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, Optional from monitoring.uss_qualifier.action_generators.definitions import ( ActionGeneratorDefinition, @@ -22,13 +22,13 @@ class TestSuiteDeclaration(ImplicitDict): - suite_type: TestSuiteTypeName | None + suite_type: Optional[TestSuiteTypeName] """Type/location of test suite. Usually expressed as the file name of the suite definition (without extension) qualified relative to the `uss_qualifier` folder""" - suite_definition: TestSuiteDefinition | None + suite_definition: Optional[TestSuiteDefinition] """Definition of test suite internal to the configuration -- specified instead of `suite_type`.""" - resources: dict[ResourceID, ResourceID] | None + resources: Optional[dict[ResourceID, ResourceID]] """Mapping of the ID a resource will be known by in the child test suite -> the ID a resource is known by in the parent test suite. The child suite resource is supplied by the parent suite resource . @@ -60,7 +60,7 @@ def type_name(self) -> str: return "" -class ReactionToFailure(str, Enum): +class ReactionToFailure(StrEnum): Continue = "Continue" """If the test suite action fails, continue to the next action in that test suite""" @@ -68,63 +68,59 @@ class ReactionToFailure(str, Enum): """If the test suite action fails, do not execute any more actions in that test suite""" -class ActionType(str, Enum): - TestScenario = "test_scenario" - TestSuite = "test_suite" - ActionGenerator = "action_generator" - - @staticmethod - def build_invalid_action_declaration() -> Exception: - return ValueError( - f"Exactly one of ({', '.join(a for a in ActionType)}) must be specified in a TestSuiteActionDeclaration" - ) - - class TestSuiteActionDeclaration(ImplicitDict): """Defines a step in the sequence of things to do for a test suite. Exactly one of `test_scenario`, `test_suite`, or `action_generator` must be specified. """ - test_scenario: TestScenarioDeclaration | None + test_scenario: Optional[TestScenarioDeclaration] """If this field is populated, declaration of the test scenario to run""" - test_suite: TestSuiteDeclaration | None + test_suite: Optional[TestSuiteDeclaration] """If this field is populated, declaration of the test suite to run""" - action_generator: ActionGeneratorDefinition | None + action_generator: Optional[ActionGeneratorDefinition] """If this field is populated, declaration of a generator that will produce 0 or more test suite actions""" on_failure: ReactionToFailure = ReactionToFailure.Continue """What to do if this action fails""" - def get_action_type(self) -> ActionType: - matches = [v for v in ActionType if v in self and self[v]] - if len(matches) != 1: - raise ActionType.build_invalid_action_declaration() - return ActionType(matches[0]) + @property + def invalid_type_error(self): + return ValueError( + "Invalid TestSuiteActionDeclaration: test_scenario, test_suite or action_generator must be specified" + ) def get_resource_links(self) -> dict[ResourceID, ResourceID]: - action_type = self.get_action_type() - if action_type == ActionType.TestScenario and self.test_scenario: + if "test_scenario" in self and self.test_scenario: return self.test_scenario.resources or {} - elif action_type == ActionType.TestSuite and self.test_suite: + elif "test_suite" in self and self.test_suite: return self.test_suite.resources or {} - elif action_type == ActionType.ActionGenerator and self.action_generator: + elif "action_generator" in self and self.action_generator: return self.action_generator.resources else: - raise ActionType.build_invalid_action_declaration() + raise self.invalid_type_error def get_child_type(self) -> str: - action_type = self.get_action_type() - if action_type == ActionType.TestScenario and self.test_scenario: + if "test_scenario" in self and self.test_scenario: return self.test_scenario.scenario_type - elif action_type == ActionType.TestSuite and self.test_suite: + elif "test_suite" in self and self.test_suite: return self.test_suite.type_name - elif action_type == ActionType.ActionGenerator and self.action_generator: + elif "action_generator" in self and self.action_generator: return self.action_generator.generator_type else: - raise ActionType.build_invalid_action_declaration() + raise self.invalid_type_error + + def get_action_type_name(self) -> str: + if "test_scenario" in self and self.test_scenario: + return "TestScenario" + elif "test_suite" in self and self.test_suite: + return "TestSuite" + elif "action_generator" in self and self.action_generator: + return "ActionGenerator" + else: + return "UnknownType" ResourceTypeNameSpecifyingOptional = ResourceTypeName @@ -140,13 +136,13 @@ class TestSuiteDefinition(ImplicitDict): resources: dict[ResourceID, ResourceTypeNameSpecifyingOptional] """Enumeration of the resources used by this test suite""" - local_resources: dict[ResourceID, ResourceDeclaration] | None + local_resources: Optional[dict[ResourceID, ResourceDeclaration]] """Declarations of resources originating in this test suite. If a resource is defined in both `resources` and `local_resources`, the resource in `local_resources` will be ignored (`resources` overrides `local_resources`).""" actions: list[TestSuiteActionDeclaration] """The actions to take when running the test suite. Components will be executed in order.""" - participant_verifiable_capabilities: list[ParticipantCapabilityDefinition] | None + participant_verifiable_capabilities: Optional[list[ParticipantCapabilityDefinition]] """Definitions of capabilities verified by this test suite for individual participants.""" @staticmethod diff --git a/monitoring/uss_qualifier/suites/documentation/documentation.py b/monitoring/uss_qualifier/suites/documentation/documentation.py index 6e915eaf22..8a93e29bb6 100644 --- a/monitoring/uss_qualifier/suites/documentation/documentation.py +++ b/monitoring/uss_qualifier/suites/documentation/documentation.py @@ -39,7 +39,6 @@ from monitoring.uss_qualifier.scenarios.scenario import get_scenario_type_by_name from monitoring.uss_qualifier.suites import suite as suite_module from monitoring.uss_qualifier.suites.definitions import ( - ActionType, TestSuiteActionDeclaration, TestSuiteDefinition, TestSuiteTypeName, @@ -285,10 +284,9 @@ def _render_action( action: TestSuiteActionDeclaration | PotentialGeneratedAction, context: TestSuiteRenderContext, ) -> list[str]: - action_type = action.get_action_type() - if action_type == ActionType.TestScenario and action.test_scenario: + if "test_scenario" in action and action.test_scenario: return _render_scenario(action.test_scenario.scenario_type, context) - elif action_type == ActionType.TestSuite and action.test_suite: + elif "test_suite" in action and action.test_suite: if "suite_type" in action.test_suite and action.test_suite.suite_type: return _render_suite_by_type(action.test_suite.suite_type, context) elif ( @@ -302,10 +300,10 @@ def _render_action( raise ValueError( f"Test suite action {context.list_index} missing suite type or definition" ) - elif action_type == ActionType.ActionGenerator and action.action_generator: + elif "action_generator" in action and action.action_generator: return _render_action_generator(action.action_generator, context) else: - raise NotImplementedError(f"Unsupported test suite action type: {action_type}") + raise action.invalid_type_error @dataclass @@ -342,10 +340,9 @@ def combine(new_reqs: dict[RequirementID, RequirementInSuite]) -> None: def _collect_requirements_from_action( action: TestSuiteActionDeclaration | PotentialGeneratedAction, ) -> dict[RequirementID, RequirementInSuite]: - action_type = action.get_action_type() - if action_type == ActionType.TestScenario and action.test_scenario: + if "test_scenario" in action and action.test_scenario: return _collect_requirements_from_scenario(action.test_scenario.scenario_type) - elif action_type == ActionType.TestSuite and action.test_suite: + elif "test_suite" in action and action.test_suite: if "suite_type" in action.test_suite and action.test_suite.suite_type: suite_def = ImplicitDict.parse( load_dict_with_references(action.test_suite.suite_type), @@ -363,12 +360,10 @@ def _collect_requirements_from_action( raise ValueError( "Neither suite_type nor suite_definition specified in test_suite action" ) - elif action_type == ActionType.ActionGenerator and action.action_generator: + elif "action_generator" in action and action.action_generator: return _collect_requirements_from_action_generator(action.action_generator) else: - raise NotImplementedError( - f"Test suite action type {action_type} not yet supported" - ) + raise action.invalid_type_error def _collect_requirements_from_scenario( diff --git a/monitoring/uss_qualifier/suites/faa/uft/design/README.md b/monitoring/uss_qualifier/suites/faa/uft/design/README.md index 887d5bb242..a3420a7746 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/design/README.md +++ b/monitoring/uss_qualifier/suites/faa/uft/design/README.md @@ -12,7 +12,7 @@ The test setup includes running the following components, as also shown in the [ 1. uss_qualifier - This is the test driver that injects the operations for the SCD flow tests in USSes. 2. Auth - This auth server provides access tokens and the public key for token validation for USS-to-USS and USS-to-DSS communication. In a local deployment of the test infrastructure, this can be supplied by an instance of dummy auth running as a dockerized container exposing port 8085, as per build/dev/run_locally.sh. -3. DSS - This is a DSS instance supporting SCD in the UTM environment defined by the auth server. In a local deployment of the test infrastructure, this can be supplied by the local DSS instance running as a dockerized container exposing port 8082, as per build/dev/run_locally.sh. +3. DSS - This is a DSS instance supporting SCD in the UTM environment defined by the auth server. In a local deployment of the test infrastructure, this can be supplied by the local DSS instance running as a dockerized container exposing port 8001, as per build/dev/run_locally.sh. 4. Mock USS - This is an instance of the InterUSS monitoring tool mock_uss with scd and messagesigning capabilities enabled. In a local deployment of the test infrastructure, this can be supplied by a local instance of mock_uss running as a dockerized container exposing port 8077, as per monitoring/mock_uss/run_locally_msgsigning.sh. 5. USS-under-Test - This is the USS that needs to be tested. 6. uss_qualifier interface. USSes need to develop an interface for their USS @@ -39,7 +39,7 @@ The property to set is `resources.resource_declarations.flight_planners.specific ``` 4. Prepare your USS to run with 1. The auth server used by the UTM ecosystem under test (dummy auth at http://localhost:8085/token or http://host.docker.internal:8085/token in a local deployment of the test infrastructure). - 2. A DSS instance supporting SCD in the UTM ecosystem under test (DSS at http://localhost:8082 or http://host.docker.internal:8085 in a local deployment of the test infrastructure). + 2. A DSS instance supporting SCD in the UTM ecosystem under test (DSS at http://localhost:8001 or http://host.docker.internal:8085 in a local deployment of the test infrastructure). 5. Run the uss_qualifier interface for your USS. 6. Run uss_qualifier tests using script [run_locally.sh](../../../../../../monitoring/uss_qualifier/run_locally.sh) with config ```bash diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index a16537a44a..3f713d127e 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md @@ -18,15 +18,15 @@ Checked in - astm
    .f3548
    .v21
    + 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
    Solo happy path + 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 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Solo happy path
    Validation of operational intents + 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Validation of operational intents DSS0005,3 @@ -56,7 +56,7 @@ DSS0100,1 Implemented - ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM Availability DSS: USS Availability Mutation
    ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0100,2 @@ -206,12 +206,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a @@ -301,17 +311,17 @@ OPIN0015 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0020 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0025 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0030 @@ -411,17 +421,17 @@ USS0005 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,1 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,2 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,3 @@ -442,22 +452,22 @@ DeleteFlightSuccess Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents ExpectedBehavior Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents FlightCoveredByOperationalIntent Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents ImplementAPI Implemented - ASTM F3548 flight planners preparation + ASTM F3548 flight planners preparation
    Nominal planning: conflict with higher priority Readiness diff --git a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md index f5c4c8765d..d1eecc7ad3 100644 --- a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md +++ b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md @@ -438,7 +438,7 @@ ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: ISA Subscription Interactions
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Submitted ISA Validations
    ASTM NetRID DSS: Subscription Simple
    ASTM NetRID DSS: Subscription Validation
    ASTM NetRID DSS: Token Validation - astm
    .f3548
    .v21
    + astm
    .f3548
    .v21
    DSS0005,1 Implemented 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
    OVN Request Optional Extension to ASTM F3548-21 @@ -476,7 +476,7 @@ DSS0100,1 Implemented - ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization + ASTM Availability DSS: USS Availability Mutation
    ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization DSS0100,2 @@ -626,12 +626,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a diff --git a/monitoring/uss_qualifier/suites/suite.py b/monitoring/uss_qualifier/suites/suite.py index c548cfe934..bc921512fb 100644 --- a/monitoring/uss_qualifier/suites/suite.py +++ b/monitoring/uss_qualifier/suites/suite.py @@ -15,6 +15,7 @@ from monitoring.monitorlib.dicts import JSONAddress from monitoring.monitorlib.fetch import Query from monitoring.monitorlib.inspection import fullname +from monitoring.monitorlib.temporal import Time, TimeDuringTest from monitoring.monitorlib.versioning import repo_url_of from monitoring.uss_qualifier.action_generators.action_generator import ( ActionGenerator, @@ -22,6 +23,7 @@ ) from monitoring.uss_qualifier.configurations.configuration import ( ExecutionConfiguration, + FullyQualifiedCheck, TestSuiteActionSelectionCondition, ) from monitoring.uss_qualifier.fileio import resolve_filename @@ -49,16 +51,19 @@ ScenarioCannotContinueError, TestRunCannotContinueError, TestScenario, + are_scenario_types_equal, + fully_qualified_check_in_collection, get_scenario_type_by_name, ) from monitoring.uss_qualifier.suites.definitions import ( - ActionType, ReactionToFailure, TestSuiteActionDeclaration, TestSuiteDeclaration, TestSuiteDefinition, ) +TEST_RUN_TIMEOUT_SKIP_REASON = "Maximum test run time has been exceeded" + def _print_failed_check(failed_check: FailedCheck) -> None: yaml_lines = yaml.dump(json.loads(json.dumps(failed_check))).split("\n") @@ -87,25 +92,24 @@ def __init__( resources_for_child = make_child_resources( resources, action.get_resource_links(), - f"Test suite action to run {action.get_action_type()} {action.get_child_type()}", + f"Test suite action to run {action.get_action_type_name()} {action.get_child_type()}", ) - action_type = action.get_action_type() - if action_type == ActionType.TestScenario and action.test_scenario: + if "test_scenario" in action and action.test_scenario: self.test_scenario = TestScenario.make_test_scenario( declaration=action.test_scenario, resource_pool=resources_for_child ) - elif action_type == ActionType.TestSuite and action.test_suite: + elif "test_suite" in action and action.test_suite: self.test_suite = TestSuite( declaration=action.test_suite, resources=resources, ) - elif action_type == ActionType.ActionGenerator and action.action_generator: + elif "action_generator" in action and action.action_generator: self.action_generator = ActionGenerator.make_from_definition( definition=action.action_generator, resources=resources_for_child ) else: - raise ActionType.build_invalid_action_declaration() + raise action.invalid_type_error def get_name(self) -> str: if self.test_suite: @@ -124,7 +128,7 @@ def run(self, context: ExecutionContext) -> TestSuiteActionReport: skip_report = context.evaluate_skip() if skip_report: logger.warning( - f"Skipping {self.declaration.get_action_type()} '{self.get_name()}' because: {skip_report.reason}" + f"Skipping {self.declaration.get_action_type_name()} '{self.get_name()}' because: {skip_report.reason}" ) report = TestSuiteActionReport(skipped_action=skip_report) else: @@ -153,6 +157,11 @@ def _run_test_scenario(self, context: ExecutionContext) -> TestScenarioReport: logger.info(f'Running "{scenario.documentation.name}" scenario...') scenario.on_failed_check = _print_failed_check + scenario.time_context[TimeDuringTest.StartOfTestRun] = Time(context.start_time) + scenario.time_context[TimeDuringTest.StartOfScenario] = Time( + arrow.utcnow().datetime + ) + try: try: scenario.run(context) @@ -168,15 +177,15 @@ def _run_test_scenario(self, context: ExecutionContext) -> TestScenarioReport: except Exception as e: scenario.record_execution_error(e) report = scenario.get_report() - if report.successful: - logger.info(f'SUCCESS for "{scenario.documentation.name}" scenario') + if "execution_error" in report and report.execution_error: + lines = report.execution_error.stacktrace.split("\n") + logger.error( + 'Execution error in scenario "{}":\n{}', + scenario.documentation.name, + "\n".join(" " + line for line in lines), + ) else: - if "execution_error" in report and report.execution_error: - lines = report.execution_error.stacktrace.split("\n") - logger.error( - "Execution error:\n{}", "\n".join(" " + line for line in lines) - ) - logger.warning(f'FAILURE for "{scenario.documentation.name}" scenario') + logger.info(f'"{scenario.documentation.name}" scenario completed') return report def _run_test_suite(self, context: ExecutionContext) -> TestSuiteReport: @@ -282,7 +291,7 @@ def __init__( ) except MissingResourceError as e: logger.warning( - f"Skipping action {a} ({action_dec.get_action_type()} {action_dec.get_child_type()}) because {str(e)}" + f"Skipping action {a} ({action_dec.get_action_type_name()} {action_dec.get_child_type()}) because {str(e)}" ) actions.append( SkippedActionReport( @@ -348,6 +357,15 @@ def _run_actions( for a, action in enumerate(actions): if isinstance(action, SkippedActionReport): action_report = TestSuiteActionReport(skipped_action=action) + elif context.should_stop_early_now(): + assert context.current_frame + action_report = TestSuiteActionReport( + skipped_action=SkippedActionReport( + timestamp=StringBasedDateTime(arrow.utcnow().datetime), + reason=TEST_RUN_TIMEOUT_SKIP_REASON, + declaration=context.current_frame.action.declaration, + ) + ) else: action_report = action.run(context) report.actions.append(action_report) @@ -405,11 +423,17 @@ def address(self) -> JSONAddress: class ExecutionContext: start_time: datetime config: ExecutionConfiguration | None + acceptable_findings: list[FullyQualifiedCheck] top_frame: ActionStackFrame | None current_frame: ActionStackFrame | None - def __init__(self, config: ExecutionConfiguration | None): + def __init__( + self, + config: ExecutionConfiguration | None, + acceptable_findings: list[FullyQualifiedCheck], + ): self.config = config + self.acceptable_findings = acceptable_findings self.top_frame = None self.current_frame = None self.start_time = arrow.utcnow().datetime @@ -449,16 +473,43 @@ def test_scenario_reports( for child in frame.children: yield from self.test_scenario_reports(child) - @property - def stop_fast(self) -> bool: + def stop_fast( + self, test_case_name: str, test_step_name: str, check_name: str + ) -> bool: if ( self.config is not None and "stop_fast" in self.config - and self.config.stop_fast is not None + and self.config.stop_fast ): - return self.config.stop_fast + if ( + "do_not_stop_fast_for_acceptable_findings" in self.config + and self.config.do_not_stop_fast_for_acceptable_findings + ): + # See if there is an exception for the particular check being considered + if self.current_frame and self.current_frame.action.test_scenario: + current_check = FullyQualifiedCheck( + scenario_type=self.current_frame.action.test_scenario.declaration.scenario_type, + test_case_name=test_case_name, + test_step_name=test_step_name, + check_name=check_name, + ) + if fully_qualified_check_in_collection( + current_check, self.acceptable_findings + ): + return False + return True return False + def should_stop_early_now(self) -> bool: + if ( + not self.config + or "stop_after" not in self.config + or not self.config.stop_after + ): + return False + dt = arrow.utcnow() - self.start_time + return dt >= self.config.stop_after.timedelta + def _compute_n_of( self, target: TestSuiteAction, condition: TestSuiteActionSelectionCondition ) -> int: @@ -541,10 +592,15 @@ def _is_selected_by( "types" in f.is_test_scenario and f.is_test_scenario.types is not None ): - if ( - action.test_scenario.declaration.scenario_type - not in f.is_test_scenario.types - ): + matches_scenario_type = False + for scenario_type in f.is_test_scenario.types: + if are_scenario_types_equal( + scenario_type, + action.test_scenario.declaration.scenario_type, + ): + matches_scenario_type = True + break + if not matches_scenario_type: return False result = True else: @@ -625,6 +681,21 @@ def evaluate_skip(self) -> SkippedActionReport | None: declaration=self.current_frame.action.declaration, ) + if ( + "scenarios_filter" in self.config + and self.config.scenarios_filter + and self.current_frame.action.test_scenario + ): + scenario_type = ( + self.current_frame.action.test_scenario.declaration.scenario_type + ) + if not re.search(self.config.scenarios_filter, scenario_type): + return SkippedActionReport( + timestamp=StringBasedDateTime(arrow.utcnow()), + reason=f"Scenario type '{scenario_type}' did not match against scenarios_filter regex `{self.config.scenarios_filter}`", + declaration=self.current_frame.action.declaration, + ) + return None def begin_action(self, action: TestSuiteAction) -> None: @@ -648,7 +719,7 @@ def end_action( if self.current_frame.action is not action: raise RuntimeError( - f"Action {self.current_frame.action.declaration.get_action_type()} {self.current_frame.action.declaration.get_child_type()} was started, but a different action {action.declaration.get_action_type()} {action.declaration.get_child_type()} was ended" + f"Action {self.current_frame.action.declaration.get_action_type_name()} {self.current_frame.action.declaration.get_child_type()} was started, but a different action {action.declaration.get_action_type_name()} {action.declaration.get_child_type()} was ended" ) self.current_frame.report = report self.current_frame = self.current_frame.parent diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index 7ac3f20f3a..4cd470425a 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.md +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.md @@ -19,15 +19,15 @@ Checked in - astm
    .f3548
    .v21
    + 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
    Solo happy path + 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 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Solo happy path
    Validation of operational intents + 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Validation of operational intents DSS0005,3 @@ -57,7 +57,7 @@ DSS0100,1 Implemented - ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM Availability DSS: USS Availability Mutation
    ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0100,2 @@ -207,12 +207,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a @@ -302,17 +312,17 @@ OPIN0015 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0020 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0025 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0030 @@ -412,17 +422,17 @@ USS0005 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,1 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,2 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,3 @@ -443,22 +453,22 @@ DeleteFlightSuccess Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents ExpectedBehavior Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents FlightCoveredByOperationalIntent Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents ImplementAPI Implemented - ASTM F3548 flight planners preparation
    Generic flight planners preparation + ASTM F3548 flight planners preparation
    Generic flight planners preparation
    Nominal planning: conflict with higher priority Readiness diff --git a/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.md b/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.md index 2c5098b044..bed04b50b7 100644 --- a/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.md +++ b/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.md @@ -5,6 +5,7 @@ ## [Actions](../README.md#actions) 1. Scenario: [EUROCAE ED-269 UAS geographical zone model](../../scenarios/eurocae/ed269/source_data_model.md) ([`scenarios.eurocae.ed269.source_data_model.SourceDataModelValidation`](../../scenarios/eurocae/ed269/source_data_model.py)) +2. Scenario: [EUROCAE ED-318 UAS geographical zone model](../../scenarios/eurocae/ed318/source_data_model.md) ([`scenarios.eurocae.ed318.source_data_model.SourceDataModelValidation`](../../scenarios/eurocae/ed318/source_data_model.py)) ## [Checked requirements](../README.md#checked-requirements) diff --git a/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.yaml b/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.yaml index c4b9a0d435..47c9c5e334 100644 --- a/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.yaml +++ b/monitoring/uss_qualifier/suites/uspace/geo_awareness_cis.yaml @@ -1,9 +1,15 @@ name: U-Space Common Information Service resources: - source_document: resources.eurocae.ed269.source_document.SourceDocument + source_document_ed269: resources.eurocae.ed269.source_document.SourceDocument + source_document_ed318: resources.eurocae.ed318.source_document.SourceDocument actions: - test_scenario: scenario_type: scenarios.eurocae.ed269.source_data_model.SourceDataModelValidation resources: - source_document: source_document + source_document: source_document_ed269 + on_failure: Abort + - test_scenario: + scenario_type: scenarios.eurocae.ed318.source_data_model.SourceDataModelValidation + resources: + source_document: source_document_ed318 on_failure: Abort diff --git a/monitoring/uss_qualifier/suites/uspace/network_identification.md b/monitoring/uss_qualifier/suites/uspace/network_identification.md index 6e6b2d880f..5d1e228e3a 100644 --- a/monitoring/uss_qualifier/suites/uspace/network_identification.md +++ b/monitoring/uss_qualifier/suites/uspace/network_identification.md @@ -17,7 +17,7 @@ Checked in - astm
    .f3411
    .v22a
    + astm
    .f3411
    .v22a
    DSS0010 Implemented ASTM NetRID DSS: Token Validation @@ -224,13 +224,13 @@ NET0030 - Implemented + TODO - ASTM NetRID Service Provider operator notification on missing fields
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification on missing fields NET0040 - Implemented + TODO - ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification under slow update rate NET0210 @@ -420,7 +420,7 @@ NET0340 Implemented - ASTM NetRID nominal behavior + ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0420 @@ -604,18 +604,13 @@ NET0610 - Implemented + TODO - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior
    ASTM NetRID: Operator interactions - - - NET0620 - TODO - ASTM NetRID: Operator interactions + Implemented + ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,1 Implemented - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,2 @@ -678,7 +673,7 @@ SearchISAs Implemented - ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior interuss
    .mock_uss
    .hosted_instance
    @@ -692,4 +687,10 @@ Implemented U-space MSL altitude + + versioning + ReportSystemVersion + Implemented + Get system versions + diff --git a/monitoring/uss_qualifier/suites/uspace/network_identification.yaml b/monitoring/uss_qualifier/suites/uspace/network_identification.yaml index 4c11f96868..3f86c490e7 100644 --- a/monitoring/uss_qualifier/suites/uspace/network_identification.yaml +++ b/monitoring/uss_qualifier/suites/uspace/network_identification.yaml @@ -56,6 +56,8 @@ participant_verifiable_capabilities: - service_provider_uas_id_serial_number_provider # Serial Number - service_provider_height_provider # Height - service_provider_operator_position_provider # Operator Location + - service_provider_operator_altitude_provider # Operator Altitude + - service_provider_operator_location_type_provider # Operator Location Type - service_provider_operational_status_provider # Operational Status - display_provider_operator_id_transmitter # Operator Registration - display_provider_uas_id_serial_number_transmitter # Serial Number @@ -65,5 +67,7 @@ participant_verifiable_capabilities: - display_provider_track_direction_transmitter # Track - display_provider_speed_transmitter # Speed - display_provider_operator_position_transmitter # Operator Location + - display_provider_operator_altitude_transmitter # Operator Altitude + - display_provider_operator_location_type_transmitter # Operator Location Type - display_provider_operational_status_transmitter # Operational Status capability_location: '$..*[?(@.suite_type=="suites.astm.netrid.f3411_22a")]' diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index d77a243573..3c731b6df3 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -18,7 +18,7 @@ Checked in - astm
    .f3411
    .v22a
    + astm
    .f3411
    .v22a
    DSS0010 Implemented ASTM NetRID DSS: Token Validation @@ -225,13 +225,13 @@ NET0030 - Implemented + TODO - ASTM NetRID Service Provider operator notification on missing fields
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification on missing fields NET0040 - Implemented + TODO - ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID: Operator interactions + Implemented + ASTM NetRID Service Provider operator notification under slow update rate NET0210 @@ -421,7 +421,7 @@ NET0340 Implemented - ASTM NetRID nominal behavior + ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0420 @@ -605,18 +605,13 @@ NET0610 - Implemented + TODO - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior
    ASTM NetRID: Operator interactions - - - NET0620 - TODO - ASTM NetRID: Operator interactions + Implemented + ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,1 Implemented - ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior NET0710,2 @@ -634,15 +629,15 @@ ASTM NetRID Service Provider notification behavior - astm
    .f3548
    .v21
    + 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
    Solo happy path + 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 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Solo happy path
    Validation of operational intents + 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
    Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    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
    Validation of operational intents DSS0005,3 @@ -672,7 +667,7 @@ DSS0100,1 Implemented - ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM Availability DSS: USS Availability Mutation
    ASTM SCD DSS: Interfaces authentication
    ASTM SCD DSS: USS Availability Synchronization
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0100,2 @@ -822,12 +817,22 @@ DSS0210,A2-7-2,3a Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization DSS0210,A2-7-2,3b Implemented - ASTM SCD DSS: Constraint Reference Synchronization
    ASTM SCD DSS: Operational Intent Reference Synchronization + ASTM SCD DSS: Operational Intent Reference Synchronization + + + DSS0210,A2-7-2,3c + Implemented + ASTM SCD DSS: Constraint Reference Synchronization + + + DSS0210,A2-7-2,3d + Implemented + ASTM SCD DSS: Constraint Reference Synchronization DSS0210,A2-7-2,4a @@ -917,17 +922,17 @@ OPIN0015 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0020 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0025 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents OPIN0030 @@ -1027,17 +1032,17 @@ USS0005 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,1 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,2 Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents USS0105,3 @@ -1058,22 +1063,22 @@ DeleteFlightSuccess Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents ExpectedBehavior Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Flight authorisation validation
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Off-Nominal planning: down USS with equal priority conflicts not permitted
    Validation of operational intents FlightCoveredByOperationalIntent Implemented - Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Solo happy path
    Validation of operational intents + Awareness of relevant operational intents
    Data Validation of GET operational intents by USS
    Nominal planning: conflict with higher priority
    Nominal planning: not permitted conflict with equal priority
    Off-Nominal planning: down USS
    Validation of operational intents ImplementAPI Implemented - ASTM F3548 flight planners preparation
    Generic flight planners preparation + ASTM F3548 flight planners preparation
    Generic flight planners preparation
    Nominal planning: conflict with higher priority Readiness @@ -1126,7 +1131,7 @@ SearchISAs Implemented - ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior + ASTM NetRID DSS: Concurrent Requests
    ASTM NetRID DSS: ISA Expiry
    ASTM NetRID DSS: Simple ISA
    ASTM NetRID DSS: Token Validation
    ASTM NetRID SP clients misbehavior handling
    ASTM NetRID Service Provider operator notification under slow update rate
    ASTM NetRID networked UAS disconnection
    ASTM NetRID nominal behavior interuss
    .f3548
    .notification_requirements
    diff --git a/monitoring/uss_qualifier/test_data/aus/incident_translator.jsonnet b/monitoring/uss_qualifier/test_data/aus/incident_translator.jsonnet new file mode 100644 index 0000000000..efad301520 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/aus/incident_translator.jsonnet @@ -0,0 +1,76 @@ +// Translates "incidents" GeoJSON provided via "geojson_example" external variable into a FeatureCheckTable +local features = std.extVar("geojson_example")["incidents"]["features"]; +local timestamp_of = std.native("timestamp_of"); +local now = timestamp_of("2025-10-11T13:30:08.503557+09:30"); +local no_older_than = 24 * 60 * 60; + +local make_row = function(index, feature, volumes) + if feature["properties"]["_status"] == "closed" then + null + else if now - timestamp_of(feature["properties"]["_lastupdate"]) > no_older_than then + null + else + { + geospatial_check_id: "TEST_" + std.format("%03d", index + 1), + requirement_ids: ["REQ_002"], + description: feature["properties"]["_category"] + " at " + feature["properties"]["_datenotified"], + operation_rule_set: "Rules1", + restriction_source: "ThisRegulator", + expected_result: "Block", + volumes: volumes + }; + +local make_4d_volume = function(footprint) + footprint + { + altitude_lower: {value: 0, units: "M", reference: "SFC"}, + altitude_upper: {value: 100, units: "M", reference: "SFC"}, + start_time: {time_during_test: "StartOfTestRun"}, + end_time: { + offset_from: { + starting_from: {time_during_test: "StartOfTestRun"}, + offset: "1h" + } + } + }; + +local make_point_row = function(index, feature) + local footprint = { + outline_circle: { + center: { + lat: feature["geometry"]["coordinates"][1], + lng: feature["geometry"]["coordinates"][0] + }, + radius: {value: 10, units: "M"} + }, + }; + local volumes = [make_4d_volume(footprint)]; + make_row(index, feature, volumes); + +local make_polygon_row = function(index, feature) + local footprint = { + outline_polygon: { + vertices: [ + {lng: coords[0], lat: coords[1]} + for coords in feature["geometry"]["coordinates"][0] + ] + } + }; + local volumes = [make_4d_volume(footprint)]; + make_row(index, feature, volumes); + +local append_row = function(rows, feature) + local new_row = + if feature["geometry"]["type"] == "Point" then + make_point_row(std.length(rows), feature) + else if feature["geometry"]["type"] == "Polygon" then + make_polygon_row(std.length(rows), feature) + else + null; + if new_row == null then + rows + else + rows + [new_row]; + +{ + rows: std.foldl(append_row, features, []) +} diff --git a/monitoring/uss_qualifier/test_data/aus/incidents.json b/monitoring/uss_qualifier/test_data/aus/incidents.json new file mode 100644 index 0000000000..a0be0bda83 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/aus/incidents.json @@ -0,0 +1,134 @@ +{ + "title": "Example GeoJSON source", + "note": "The Features in this file are intended to be used to create a FeatureCheckTable for geospatial comprehension", + "lastupdated": "2025-10-11T11:05:08.503557+09:30", + "incidents": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 131.0, + -12.8 + ] + }, + "properties": { + "_category": "fire", + "_status": "closed", + "_dateclosed": "2025-10-10T14:02:19+09:30", + "_datenotified": "2025-10-09T15:39:49+09:30", + "_lastupdate": "2025-10-10T14:02:19+09:30", + "Alert Level": "Advice" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 130.9, + -12.5 + ] + }, + "properties": { + "_category": "alarm", + "_status": "active", + "_dateclosed": null, + "_datenotified": "2025-10-10T12:04:50+09:30", + "_lastupdate": "2025-10-10T13:03:31+09:30", + "Alert Level": "Advice" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 130.9, + -12.4 + ] + }, + "properties": { + "_category": "fire", + "_status": "active", + "_dateclosed": null, + "_datenotified": "2025-10-11T10:46:26+09:30", + "_lastupdate": "2025-10-11T10:53:27+09:30", + "Alert Level": "Advice" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 130.8, + -12.5 + ] + }, + "properties": { + "_category": "roadcrash", + "_status": "active", + "_dateclosed": null, + "_datenotified": "2025-10-11T11:04:10+09:30", + "_lastupdate": "2025-10-11T11:04:10+09:30", + "Alert Level": "Advice" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 133.6, + -23.7 + ], + [ + 133.7, + -23.7 + ], + [ + 133.8, + -23.7 + ], + [ + 133.6, + -23.7 + ] + ] + ] + }, + "properties": { + "_category": "bushfire-advice", + "_status": "active", + "_dateclosed": null, + "_datenotified": "2025-10-05T14:49:00+09:30", + "_lastupdate": "2025-10-11T09:35:00+09:30", + "Alert Level": "Advice" + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 133.7, + -23.7 + ] + }, + "properties": { + "_category": "bushfire-advice", + "_status": "active", + "_dateclosed": null, + "_datenotified": "2025-10-05T14:49:00+09:30", + "_lastupdate": "2025-10-11T09:35:00+09:30", + "Alert Level": "Advice" + } + } + ] + } +} diff --git a/monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample.json b/monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample_ed269.json similarity index 100% rename from monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample.json rename to monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample_ed269.json diff --git a/monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample_ed318.json b/monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample_ed318.json new file mode 100644 index 0000000000..42ebb1268c --- /dev/null +++ b/monitoring/uss_qualifier/test_data/che/geoawareness/cis_source_sample_ed318.json @@ -0,0 +1,432 @@ +{ + "type" : "FeatureCollection", + "name" : "Test data of the Swiss UAS Geozones according to ED-318 converted from the ED-269 data model", + "bbox" : [ 5.9643748, 45.8193544, 10.5591277, 47.7720945 ], + "description" : "Test data of the Swiss UAS Geozones according to ED-318 - format version 2.0", + "metadata" : { + "validFrom" : "2016-09-15T00:00:00+02:00", + "provider" : [ + { + "text" : "BAZL", + "lang" : "de-CH" + }, + { + "text" : "OFAC", + "lang" : "fr-CH" + }, + { + "text" : "UFAC", + "lang" : "it-CH" + }, + { + "text" : "FOCA", + "lang" : "en-GB" + } + ], + "issued" : "2025-03-13T13:58:00+01:00", + "description" : [ + { + "text" : "Testdaten der Schweizerischen UAS Geozones, herausgegeben vom Bundesamt für Zivilluftfahrt (BAZL). Der Datensatz hat keine Rechtskraft. Umwandlung aus dem Modell ED-269", + "lang" : "de-CH" + }, + { + "text" : "Données de test des UAS Geozones suisses publiées par l'Office fédéral de l'aviation civile (OFAC). L'ensemble de données n'a pas de valeur juridique. Conversion à partir du modèle ED-269", + "lang" : "fr-CH" + }, + { + "text" : "Dati di prova delle Geozones UAS svizzere emesse dall'Ufficio federale dell'aviazione civile (UFAC). Il dataset non ha valore legale. Conversione dal modello ED-269", + "lang" : "it-CH" + }, + { + "text" : "Test data of the Swiss UAS Geozones issued by the Federal Office of Civil Aviation (FOCA). The dataset has no legal validity. Conversion from the ED-269 model", + "lang" : "en-GB" + } + ], + "otherGeoid" : "CHGeo2004", + "technicalLimitation" : [ + { + "text" : "Der Datensatz entsteht durch die Umwandlung der Originaldaten des ED-269-Modells ins neue ED-318. Für die Umwandlung sind einige Datenänderungen nötig", + "lang" : "de-CH" + }, + { + "text" : "Le fichier a été créé en convertissant les données originales du modèle ED-269 dans le nouveau ED-318. La conversion nécessite des modifications des données", + "lang" : "fr-CH" + }, + { + "text" : "Il dataset è stato creato convertendo i dati originali del modello ED-269 nel nuovo ED-318. Per la conversione alcune modifiche dei dati sono necessarie", + "lang" : "it-CH" + }, + { + "text" : "The dataset was created by converting the original data from the ED-269 model to the new ED-318. Some data modifications are necessary for conversion", + "lang" : "en-GB" + } + ] + }, + "features" : [ + { + "type" : "Feature", + "geometry" : { + "type" : "Polygon", + "coordinates" : [ + [ + [ 7.4355068, 46.9402526 ], + [ 7.4368695, 46.9418327 ], + [ 7.4387706, 46.9437947 ], + [ 7.4408192, 46.9456857 ], + [ 7.4430096, 46.9475007 ], + [ 7.4453359, 46.9492347 ], + [ 7.4477917, 46.9508829 ], + [ 7.4503702, 46.9524407 ], + [ 7.4530644, 46.9539039 ], + [ 7.4558669, 46.9552685 ], + [ 7.45877, 46.9565308 ], + [ 7.4617657, 46.9576872 ], + [ 7.4648458, 46.9587345 ], + [ 7.4680019, 46.95967 ], + [ 7.4712253, 46.9604911 ], + [ 7.4745071, 46.9611954 ], + [ 7.4778383, 46.961781 ], + [ 7.4812098, 46.9622464 ], + [ 7.4846123, 46.9625903 ], + [ 7.4880365, 46.9628117 ], + [ 7.4914728, 46.9629099 ], + [ 7.494912, 46.9628848 ], + [ 7.4983446, 46.9627365 ], + [ 7.5017610999999995, 46.9624652 ], + [ 7.5051521, 46.9620718 ], + [ 7.5085083, 46.9615573 ], + [ 7.5093889, 46.9613887 ], + [ 7.4355068, 46.9402526 ] + ] + ], + "layer" : { + "upper" : 99999, + "upperReference" : "AGL", + "lower" : 60, + "lowerReference" : "AGL", + "uom" : "m" + } + }, + "properties" : { + "identifier" : "LSZB003", + "country" : "CHE", + "name" : [ + { + "text" : "LSZB Bern-Belp 2", + "lang" : "de-CH" + }, + { + "text" : "LSZB Bern-Belp 2", + "lang" : "fr-CH" + }, + { + "text" : "LSZB Bern-Belp 2", + "lang" : "it-CH" + }, + { + "text" : "LSZB Bern-Belp 2", + "lang" : "en-GB" + } + ], + "type" : "REQ_AUTHORIZATION", + "variant" : "COMMON", + "restrictionConditions" : "The operation of unmanned aircraft weighing more than 250 g is only allowed with exemption permit.", + "region" : 0, + "reason" : [ "AIR_TRAFFIC" ], + "extendedProperties" : { + "addInfo" : "Exemption permits may be applied for at the competent authority." + }, + "zoneAuthority" : [ + { + "name" : [ + { + "text" : "Skyguide", + "lang" : "de-CH" + }, + { + "text" : "Skyguide", + "lang" : "fr-CH" + }, + { + "text" : "Skyguide", + "lang" : "it-CH" + }, + { + "text" : "Skyguide", + "lang" : "en-GB" + } + ], + "service" : [ + { + "text" : "Special Flight Office (SFO)", + "lang" : "de-CH" + }, + { + "text" : "Special Flight Office (SFO)", + "lang" : "fr-CH" + }, + { + "text" : "Special Flight Office (SFO)", + "lang" : "it-CH" + }, + { + "text" : "Special Flight Office (SFO)", + "lang" : "en-GB" + } + ], + "siteURL" : "https://skyguide.ch/services/special-flights", + "purpose" : "AUTHORIZATION", + "intervalBefore" : "P10DT00H" + } + ] + } + }, + { + "type" : "Feature", + "geometry" : { + "type" : "Polygon", + "coordinates" : [ + [ + [ 7.5380771, 47.4931128 ], + [ 7.5380571, 47.4931546 ], + [ 7.538315, 47.4932532 ], + [ 7.5385897, 47.493358 ], + [ 7.5386419, 47.4933807 ], + [ 7.5387059, 47.4933811 ], + [ 7.538958, 47.493485 ], + [ 7.5390456, 47.4935621 ], + [ 7.5391291, 47.4935929 ], + [ 7.5391899, 47.493658 ], + [ 7.5392657, 47.4937026 ], + [ 7.5394173, 47.493705 ], + [ 7.5394293, 47.49368 ], + [ 7.5394407, 47.4936561 ], + [ 7.5396604, 47.4931961 ], + [ 7.538932, 47.4930931 ], + [ 7.5381406, 47.4929805 ], + [ 7.5380771, 47.4931128 ] + ] + ], + "layer" : { + "upper" : 99999, + "upperReference" : "AGL", + "lower" : 0, + "lowerReference" : "AGL", + "uom" : "m" + } + }, + "properties" : { + "identifier" : "BLns058", + "country" : "CHE", + "name" : [ + { + "text" : "Geschütztes Naturobjekt Langmatt", + "lang" : "de-CH" + }, + { + "text" : "Objet naturel protégé Langmatt", + "lang" : "fr-CH" + }, + { + "text" : "Oggetto naturale protetto Langmatt", + "lang" : "it-CH" + }, + { + "text" : "Protected natural object Langmatt", + "lang" : "en-GB" + } + ], + "type" : "REQ_AUTHORIZATION", + "variant" : "COMMON", + "restrictionConditions" : "The operation of unmanned aircraft is only allowed with exemption permit.", + "region" : 0, + "reason" : [ "NATURE" ], + "extendedProperties" : { + "addInfo" : "Exemption permits may be applied for at the competent authority." + }, + "zoneAuthority" : [ + { + "name" : [ + { + "text" : "Ebenrain-Zentrum für Landwirtschaft, Natur und Ernährung", + "lang" : "de-CH" + }, + { + "text" : "Centre Ebenrain pour l’Agriculture, la Nature et l’Alimentation", + "lang" : "fr-CH" + }, + { + "text" : "Centro Ebenrain per l’Agricoltura, la Natura e l’Alimentazione", + "lang" : "it-CH" + }, + { + "text" : "Ebenrain Centre for Agriculture, Nature and Food", + "lang" : "en-GB" + } + ], + "service" : [ + { + "text" : "Abteilung Natur und Landschaft", + "lang" : "de-CH" + }, + { + "text" : "Département Nature et Paysage", + "lang" : "fr-CH" + }, + { + "text" : "Dipartimento Natura e Paesaggio", + "lang" : "it-CH" + }, + { + "text" : "Department for Nature and Landscape", + "lang" : "en-GB" + } + ], + "contactName" : [ + { + "text" : "Abteilungsleiter", + "lang" : "de-CH" + }, + { + "text" : "Abteilungsleiter", + "lang" : "fr-CH" + }, + { + "text" : "Abteilungsleiter", + "lang" : "it-CH" + }, + { + "text" : "Abteilungsleiter", + "lang" : "en-GB" + } + ], + "siteURL" : "http://www.natur-und-landschaft.bl.ch", + "email" : "naturundlandschaft@bl.ch", + "phone" : "+41 61 552 21 21", + "purpose" : "AUTHORIZATION", + "intervalBefore" : "P07DT00H" + } + ] + } + }, + { + "type" : "Feature", + "geometry" : { + "type" : "Polygon", + "coordinates" : [ + [ + [ 7.1289534, 46.9783049 ], + [ 7.1285816, 46.9789731 ], + [ 7.136025, 46.9802911 ], + [ 7.136396, 46.9795329 ], + [ 7.130964, 46.9786 ], + [ 7.1311429, 46.9781174 ], + [ 7.1295214, 46.9778234 ], + [ 7.1292357, 46.9783542 ], + [ 7.1289534, 46.9783049 ] + ] + ], + "layer" : { + "upper" : 99999, + "upperReference" : "AGL", + "lower" : 0, + "lowerReference" : "AGL", + "uom" : "m" + } + }, + "properties" : { + "identifier" : "LSTB002", + "country" : "CHE", + "name" : [ + { + "text" : "LSTB Bellechasse (Flugplatzperimeter)", + "lang" : "de-CH" + }, + { + "text" : "LSTB Bellechasse (Périmètre d'aérodrome)", + "lang" : "fr-CH" + }, + { + "text" : "LSTB Bellechasse (Perimetro dell'aerodromo)", + "lang" : "it-CH" + }, + { + "text" : "LSTB Bellechasse (Airport perimeter)", + "lang" : "en-GB" + } + ], + "type" : "REQ_AUTHORIZATION", + "variant" : "COMMON", + "restrictionConditions" : "The operation of unmanned aircraft is only allowed with exemption permit.", + "region" : 0, + "reason" : [ "AIR_TRAFFIC" ], + "extendedProperties" : { + "addInfo" : "Exemption permits may be applied for at the competent authority." + }, + "zoneAuthority" : [ + { + "name" : [ + { + "text" : "Segelfluggruppe Freiburg", + "lang" : "de-CH" + }, + { + "text" : "Segelfluggruppe Freiburg", + "lang" : "fr-CH" + }, + { + "text" : "Segelfluggruppe Freiburg", + "lang" : "it-CH" + }, + { + "text" : "Segelfluggruppe Freiburg", + "lang" : "en-GB" + } + ], + "service" : [ + { + "text" : "Flugplatzleitung", + "lang" : "de-CH" + }, + { + "text" : "Flugplatzleitung", + "lang" : "fr-CH" + }, + { + "text" : "Flugplatzleitung", + "lang" : "it-CH" + }, + { + "text" : "Flugplatzleitung", + "lang" : "en-GB" + } + ], + "contactName" : [ + { + "text" : "Reto Petri", + "lang" : "de-CH" + }, + { + "text" : "Reto Petri", + "lang" : "fr-CH" + }, + { + "text" : "Reto Petri", + "lang" : "it-CH" + }, + { + "text" : "Reto Petri", + "lang" : "en-GB" + } + ], + "siteURL" : "https://www.sg-freiburg.com/kontakt", + "email" : "flugplatzleiter@sg-freiburg.ch", + "phone" : "0041266731933", + "purpose" : "AUTHORIZATION", + "intervalBefore" : "P02DT00H" + } + ] + } + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/che/rid/zurich.kml b/monitoring/uss_qualifier/test_data/che/rid/zurich.kml index 6b4b2d4a81..26d931dbdc 100644 --- a/monitoring/uss_qualifier/test_data/che/rid/zurich.kml +++ b/monitoring/uss_qualifier/test_data/che/rid/zurich.kml @@ -312,11 +312,11 @@ zurich 1 - flight: south u + flight: train tracks 1 serial_number: 1AKGAAN8G70R7JS aircraft_type: Helicopter -operation_description: U-turn south of takeoff +operation_description: Train tracks operator_id: CHE-RP-f39mqfitkh9e5 operator_name: Jane Doe timestamp_accuracy: 1.0 @@ -325,12 +325,12 @@ sample_rate: 1.0 accuracy_h: HAUnknown accuracy_v: VAUnknown - U turn south + Train #msn_ylw-pushpin10 1 - 8.488086010193248,47.37068005363017,0.0 8.48812954746956,47.370587242025366,0.0 8.488096461986222,47.37048552808266,0.0 8.488058869601295,47.370437593647054,0.0 8.487992713476777,47.37035616148304,0.0 8.48798464448449,47.370333056885144,0.0 8.487973904140395,47.3703023046547,0.0 8.487966840917556,47.370266323893794,0.0 8.487962372282103,47.37023816260832,0.0 8.487967675859187,47.370207342634934,0.0 8.487976559973054,47.370171398603745,0.0 8.48799807719684,47.37014036227894,0.0 8.488019499902185,47.37010942090538,0.0 8.488053429665532,47.37008339144921,0.0 8.488089449646983,47.37006527181478,0.0 8.488121925894466,47.370052282478674,0.0 8.48815088026746,47.37004440849303,0.0 8.48827518488533,47.36999864947174,0.0 8.48831139751754,47.369972421106304,0.0 8.48835375032042,47.36995426515459,0.0 8.4883924829112,47.3699319376166,0.0 8.488426644184889,47.369913985083514,0.0 8.48846068845649,47.36989610981006,0.0 8.488495983730138,47.369878322762034,0.0 8.48853439095125,47.369864919157145,0.0 8.488562728428482,47.36985722273458,0.0 8.488595159283255,47.369853852306214,0.0 8.488636589108003,47.369849906174295,0.0 8.48867952953342,47.36985020494702,0.0 8.488708578938505,47.36984763686421,0.0 + 8.5233997,47.3714318,0 8.5231624,47.3716534,0 8.522988,47.3717651,0 8.5226743,47.3720161,0 8.5221003,47.372423,0 8.5210578,47.3731616,0 8.5203497,47.3736957,0 8.5199045,47.3740953,0 8.519368,47.3746584,0 8.5190408,47.375127,0 8.5188692,47.3757809,0 8.5187887,47.3766963,0 8.519368,47.3779908,0 8.519883,47.378434,0 8.5203121,47.3789425,0 8.5208378,47.3794983,0 8.5214333,47.3800069,0 8.5219268,47.3803338,0 8.5229407,47.3807043,0 8.5242496,47.3808968,0 8.5254298,47.3811002,0 8.5259984,47.3813399,0 8.5264078,47.3816867,0 8.5264721,47.382188,0 8.5263327,47.3824858,0 8.5258606,47.3828926,0 8.5251847,47.3832123,0 8.5241762,47.3833648,0 8.5228673,47.3835101,0 8.5211077,47.3836699,0 8.5200134,47.3838152,0 8.5185757,47.3841203,0 8.5175458,47.384331,0 8.5154845,47.3849293,0 8.5141112,47.3852489,0 8.5131886,47.3857937,0 8.5123517,47.3864475,0 8.5117831,47.3869051,0 8.5109784,47.3874499,0 8.5104205,47.3877622,0 8.5092725,47.3879656,0 8.5081782,47.3880019,0 8.5067513,47.3879802,0 8.5053565,47.3879802,0 8.5045518,47.3880019,0 8.5028138,47.3879947,0 8.5012044,47.3881545,0 8.4999556,47.3887242,0 8.4995157,47.3891455,0 8.4986359,47.3897629,0 8.4973485,47.3898936,0 8.4965867,47.3895522,0 8.4958035,47.3896394,0 8.4951491,47.3901115,0 8.4949989,47.3905909,0 8.4949667,47.3911793,0 8.4951383,47.3915787,0 8.4956855,47.3918838,0 8.4965867,47.3919056,0 8.4973163,47.3918838,0 8.4978527,47.3917458,0 8.498357,47.3914553,0 8.4986252,47.3912301,0 8.4989471,47.3909904,0 8.4993762,47.3908451,0 8.499741,47.3908233,0 @@ -343,7 +343,7 @@ accuracy_v: VAUnknown - 8.487599206246548,47.370506331316705,496.0 8.48773686257555,47.370357145137795,496.0 8.488527190331814,47.37084454793311,496.0 8.488389720684472,47.371003666625,496.0 8.487599206246548,47.370506331316705,496.0 + 8.5207302,47.3679382,450 8.518331,47.372555,450 8.527615,47.3731521,450 8.5270762,47.371081,450 8.5207302,47.3679382,450 @@ -359,7 +359,7 @@ accuracy_v: VAUnknown - 8.48957979534743,47.369927140429084,551.0 8.490587218626521,47.37027611497966,551.0 8.490661563415705,47.37107682800347,551.0 8.489204440074536,47.370663517599084,551.0 8.48957979534743,47.369927140429084,551.0 + 8.5118972,47.3744194,570 8.5202778,47.3750873,570 8.5206942,47.378602,570 8.5119676,47.3784622,570 8.5118972,47.3744194,570 @@ -375,7 +375,7 @@ accuracy_v: VAUnknown - 8.491850511360857,47.36774777095399,521.0 8.493236481345823,47.37021642084468,521.0 8.490620125766261,47.369968081354884,521.0 8.48962145300232,47.368391591949525,521.0 8.491850511360857,47.36774777095399,521.0 + 8.5022266,47.3860771,478 8.5011602,47.392333,478 8.4880906,47.3936745,478 8.487937,47.3869263,478 8.5022266,47.3860771,478 @@ -389,7 +389,7 @@ accuracy_v: VAUnknown - 8.487627177694923,47.37022567192925,0.0 8.488541433192031,47.370700520846526,0.0 8.488146211772209,47.371136886332906,0.0 8.487235428789324,47.37064948552166,0.0 8.487627177694923,47.37022567192925,0.0 + 8.5247928,47.3713996,0 8.5240951,47.3721486,0 8.5222242,47.3716635,0 8.5231591,47.370904,0 8.5247928,47.3713996,0 @@ -403,7 +403,7 @@ accuracy_v: VAUnknown - 8.489638509156725,47.367439591965365,0.0 8.492925998859539,47.37003155923135,0.0 8.49104344194709,47.371956544316674,0.0 8.488021081836711,47.369439356682854,0.0 8.489638509156725,47.367439591965365,0.0 + 8.490508,47.3932007,0 8.5044748,47.3920025,0 8.5057811,47.3847686,0 8.4907558,47.3853825,0 8.490508,47.3932007,0 @@ -414,16 +414,19 @@ accuracy_v: VAUnknown #m_ylw-pushpin1 1 - 8.487908591798265,47.37085983569079,0.0 + + 8.523948,47.370838,412.2 + + absolute - flight: circle + flight: ETA 1 serial_number: ULYK563L5G aircraft_type: Helicopter -operation_description: Circle flight +operation_description: Circle flight around ETH operator_id: CHE-RP-iwetcg7ucfopc operator_name: John Doe timestamp_accuracy: 1.0 @@ -432,12 +435,12 @@ sample_rate: 1.0 accuracy_h: HAUnknown accuracy_v: VAUnknown - South + ETH #msn_ylw-pushpin10 1 - 8.488229854920197,47.37074558658443,0.0 8.488329471365594,47.370817837505804,0.0 8.488375471496616,47.37084917525504,0.0 8.488421911721352,47.37088057176483,0.0 8.488456791062694,47.370907107576116,0.0 8.488519008377102,47.370935098462986,0.0 8.488581245669442,47.37096309822762,0.0 8.488647221360685,47.371003666692424,0.0 8.48878573903851,47.371065588974794,0.0 8.4888116351726,47.3710910227504,0.0 8.488825848236209,47.37113965697517,0.0 8.488843658918347,47.37119784355049,0.0 8.488861648775275,47.37125596075003,0.0 8.488870643430166,47.371285017030544,0.0 8.48888532292006,47.37133349567566,0.0 8.488888431723344,47.37140544348874,0.0 8.488881475041415,47.371448736079756,0.0 8.488854363332072,47.37150754532702,0.0 8.488812856615459,47.37157081371983,0.0 8.48877499150027,47.371644036289254,0.0 8.4887471867464,47.371693159220264,0.0 + 8.5506164,47.3752894,0 8.5507881,47.3752022,0 8.550949,47.3752349,0 8.5510778,47.3753366,0 8.5510671,47.3754892,0 8.5509705,47.3755728,0 8.5507935,47.3756236,0 8.5505521,47.3756382,0 8.5502892,47.3755365,0 8.5502141,47.3753475,0 8.5501766,47.3751732,0 8.5502785,47.3750097,0 8.5506486,47.3749044,0 8.5510027,47.3748971,0 8.5513353,47.3749443,0 8.5515659,47.3749988,0 8.5516571,47.3751659,0 8.5516679,47.3752531,0 8.5516303,47.3754456,0 8.5515499,47.3756309,0 8.5514211,47.375758,0 8.5512494,47.3758852,0 8.5509866,47.3760232,0 8.5507613,47.3761249,0 8.5505145,47.3762339,0 8.5502248,47.3763538,0 8.5500156,47.3764701,0 8.5498601,47.3765173,0 8.5496455,47.3765863,0 8.5494524,47.3766807,0 8.5492378,47.3767897,0 8.5489642,47.3769387,0 8.548696,47.3770367,0 8.5483848,47.3771494,0 8.5481488,47.3772765,0 8.5479396,47.3773782,0 8.5475051,47.377469,0 8.5473602,47.3773891,0 8.5473066,47.3771312,0 8.547151,47.3770258,0 8.546797,47.3769859,0 8.5465341,47.3770404,0 8.5462874,47.3771966,0 8.5461693,47.3773964,0 8.5460781,47.3776289,0 8.546046,47.3778141,0 8.5459279,47.3779558,0 8.5458046,47.3781302,0 8.5456973,47.3783517,0 8.5456758,47.3784135,0 8.545987,47.3785188,0 8.5462391,47.3786423,0 8.5464,47.3787005,0 8.5463142,47.3788639,0 8.5462284,47.379031,0 8.5461908,47.3791218,0 8.5465341,47.379209,0 8.5467755,47.3792744,0 8.5470169,47.3792926,0 8.5471349,47.3791981,0 8.5472154,47.3790419,0 8.5473227,47.378893,0 8.5474675,47.3786896,0 8.547607,47.378497,0 8.5477304,47.3783735,0 8.5478484,47.3782682,0 8.5480093,47.3780757,0 8.5481434,47.3780502,0 8.5483366,47.3779558,0 8.548696,47.3779413,0 8.5489642,47.3779304,0 8.5492378,47.3779304,0 8.5494577,47.3779485,0 8.5496348,47.377974,0 8.5498762,47.3780611,0 8.5500693,47.3781302,0 8.5501497,47.3781846,0 8.5500371,47.3783735,0 8.5498279,47.378497,0 8.5496294,47.3786678,0 8.5494953,47.3787804,0 8.5491627,47.3788167,0 8.548814,47.3788167,0 8.5485029,47.3788131,0 8.5483097,47.3788167,0 8.5481274,47.3788167,0 8.5480576,47.3786569,0 8.5480791,47.3784244,0 8.5480844,47.3782391,0 8.5480683,47.378003,0 8.5480952,47.3779013,0 8.5482025,47.3777451,0 8.5482668,47.3775562,0 8.548358,47.3774218,0 8.5484492,47.3773237,0 8.548637,47.3771784,0 8.5488676,47.3771167,0 8.5491466,47.3770985,0 8.5493558,47.3771748,0 8.5496777,47.3772874,0 8.5498118,47.3773601,0 8.5499674,47.3774145,0 8.5503053,47.3774908,0 8.5504341,47.3775707,0 8.5506915,47.3776616,0 8.5509008,47.3777415,0 8.551169,47.3777524,0 8.5514211,47.3775925,0 8.5515659,47.3774654,0 8.5518073,47.3773455,0 8.5520166,47.3772438,0 8.5522365,47.3771494,0 8.552494,47.3770513,0 8.55273,47.3770476,0 8.5528856,47.3771239,0 8.5532557,47.3771094,0 8.5535454,47.3770694,0 @@ -450,7 +453,7 @@ accuracy_v: VAUnknown - 8.487599206246548,47.370506331316705,529.0 8.48773686257555,47.370357145137795,529.0 8.488527190331814,47.37084454793311,529.0 8.488389720684472,47.371003666625,529.0 8.487599206246548,47.370506331316705,529.0 + 8.550622767214215,47.37532471080826,529 8.550968690310363,47.37517998821773,529 8.55139147011187,47.37525984247981,529 8.55105745570194,47.3757494351859,529 8.550583069630015,47.3755709047547,529 8.550622767214215,47.37532471080826,529 @@ -466,7 +469,7 @@ accuracy_v: VAUnknown - 8.489267459600915,47.37166062967849,655.0 8.489977536459923,47.370984073534544,655.0 8.493313619548607,47.37238015512596,655.0 8.491518892767237,47.374387522193466,655.0 8.489267459600915,47.37166062967849,655.0 + 8.544333807459472,47.3798843562703,655 8.545011542595343,47.37680317592417,655 8.547742218426757,47.37668348981232,655 8.552944747200721,47.37950457579466,655 8.544333807459472,47.3798843562703,655 @@ -482,7 +485,7 @@ accuracy_v: VAUnknown - 8.490488970874699,47.375428562929855,570.0 8.491780618890493,47.378005797139494,570.0 8.4887685286589,47.378074018769155,570.0 8.487453168467146,47.37563485906732,570.0 8.490488970874699,47.375428562929855,570.0 + 8.554876335105625,47.37664451545904,570 8.555238965142005,47.37783481116264,570 8.552105010325326,47.37790597015967,570 8.551718647190281,47.37669461906262,570 8.554876335105625,47.37664451545904,570 @@ -496,21 +499,21 @@ accuracy_v: VAUnknown - 8.487706170711212,47.36995718766655,0.0 8.488846636784208,47.37042600425997,0.0 8.488196425088466,47.371282684720995,0.0 8.487065280715347,47.37068957178074,0.0 8.487706170711212,47.36995718766655,0.0 + 8.549095584214793,47.37494736510541,0 8.551815498608766,47.37437716793783,0 8.552606086871242,47.37519352831755,0 8.55101776114218,47.37630422675167,0 8.549095584214793,47.37494736510541,0 - speed: Enroute (30) + speed: Enroute (60) #msn_ylw-pushpin2 1 - 8.488098610981899,47.371873583853734,0.0 8.493152274264935,47.37332523922927,0.0 8.494160531251842,47.37729110145004,0.0 8.489590109282963,47.37815602158183,0.0 8.48515942299658,47.37523070910003,0.0 8.488098610981899,47.371873583853734,0.0 + 8.544927914901734,47.37580272082323,0 8.554358343240745,47.3768789300979,0 8.554196235097036,47.37898777417489,0 8.547851026312099,47.3798679606927,0 8.542984878232653,47.37889733073179,0 8.544927914901734,47.37580272082323,0 @@ -521,7 +524,8 @@ accuracy_v: VAUnknown #m_ylw-pushpin 1 - 8.487979542766373,47.370902783740675,0.0 + 8.5510206,47.3757389,459.0 + absolute diff --git a/monitoring/uss_qualifier/test_data/make_flight_intent_kml.py b/monitoring/uss_qualifier/test_data/make_flight_intent_kml.py index da94195b45..16dfd62e17 100644 --- a/monitoring/uss_qualifier/test_data/make_flight_intent_kml.py +++ b/monitoring/uss_qualifier/test_data/make_flight_intent_kml.py @@ -14,7 +14,7 @@ from monitoring.monitorlib.kml.flight_planning import flight_planning_styles from monitoring.monitorlib.kml.generation import make_placemark_from_volume -from monitoring.monitorlib.temporal import Time, TimeDuringTest +from monitoring.monitorlib.temporal import TestTimeContext, Time from monitoring.uss_qualifier.fileio import load_dict_with_references, resolve_filename from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( FlightIntentCollection, @@ -47,11 +47,7 @@ def main() -> int: output_path = os.path.splitext(resolve_filename(path))[0] + ".kml" start_of_test_run = Time(args.start_of_test_run or arrow.utcnow().datetime) - times = { - TimeDuringTest.StartOfTestRun: start_of_test_run, - TimeDuringTest.StartOfScenario: start_of_test_run, - TimeDuringTest.TimeOfEvaluation: start_of_test_run, - } + context = TestTimeContext.all_times_are(start_of_test_run) raw = load_dict_with_references(path) collection: FlightIntentCollection = ImplicitDict.parse(raw, FlightIntentCollection) @@ -59,7 +55,7 @@ def main() -> int: folders = [] for name, template in flight_intents.items(): - flight_intent = template.resolve(times) + flight_intent = template.resolve(context) folder = kml.Folder(kml.name(name)) non_basic_info = json.loads( diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_h.json b/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_h.json new file mode 100644 index 0000000000..56e3ec46ea --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_h.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_h.kml b/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_h.kml new file mode 100644 index 0000000000..cd680681f4 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_h.kml @@ -0,0 +1,530 @@ + + + + rid.kml + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl0 + + + + + normal + #s_ylw-pushpin0 + + + highlight + #s_ylw-pushpin_hl + + + + + normal + #s_ylw-pushpin1 + + + highlight + #s_ylw-pushpin_hl00 + + + + + normal + #sn_ylw-pushpin + + + highlight + #sh_ylw-pushpin + + + + + normal + #sn_ylw-pushpin0 + + + highlight + #sh_ylw-pushpin2 + + + + + normal + #sn_ylw-pushpin10 + + + highlight + #sh_ylw-pushpin1 + + + + + normal + #sn_ylw-pushpin20 + + + highlight + #sh_ylw-pushpin00 + + + + + normal + #sn_ylw-pushpin2 + + + highlight + #sh_ylw-pushpin0 + + + + + normal + #sn_ylw-pushpin3 + + + highlight + #sh_ylw-pushpin3 + + + + + + + + + + + + + + + + + + + + + + zurich + 1 + + flight: south u + 1 + serial_number: 1AKGAAN8G70R7JS +aircraft_type: Helicopter +operation_description: U-turn south of takeoff +operator_id: CHE-RP-f39mqfitkh9e5 +operator_name: Jane Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_v: VAUnknown + + U turn south + #msn_ylw-pushpin10 + + 1 + + 8.488086010193248,47.37068005363017,0.0 8.48812954746956,47.370587242025366,0.0 8.488096461986222,47.37048552808266,0.0 8.488058869601295,47.370437593647054,0.0 8.487992713476777,47.37035616148304,0.0 8.48798464448449,47.370333056885144,0.0 8.487973904140395,47.3703023046547,0.0 8.487966840917556,47.370266323893794,0.0 8.487962372282103,47.37023816260832,0.0 8.487967675859187,47.370207342634934,0.0 8.487976559973054,47.370171398603745,0.0 8.48799807719684,47.37014036227894,0.0 8.488019499902185,47.37010942090538,0.0 8.488053429665532,47.37008339144921,0.0 8.488089449646983,47.37006527181478,0.0 8.488121925894466,47.370052282478674,0.0 8.48815088026746,47.37004440849303,0.0 8.48827518488533,47.36999864947174,0.0 8.48831139751754,47.369972421106304,0.0 8.48835375032042,47.36995426515459,0.0 8.4883924829112,47.3699319376166,0.0 8.488426644184889,47.369913985083514,0.0 8.48846068845649,47.36989610981006,0.0 8.488495983730138,47.369878322762034,0.0 8.48853439095125,47.369864919157145,0.0 8.488562728428482,47.36985722273458,0.0 8.488595159283255,47.369853852306214,0.0 8.488636589108003,47.369849906174295,0.0 8.48867952953342,47.36985020494702,0.0 8.488708578938505,47.36984763686421,0.0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.487599206246548,47.370506331316705,496.0 8.48773686257555,47.370357145137795,496.0 8.488527190331814,47.37084454793311,496.0 8.488389720684472,47.371003666625,496.0 8.487599206246548,47.370506331316705,496.0 + + + + + + + alt: High + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.48957979534743,47.369927140429084,551.0 8.490587218626521,47.37027611497966,551.0 8.490661563415705,47.37107682800347,551.0 8.489204440074536,47.370663517599084,551.0 8.48957979534743,47.369927140429084,551.0 + + + + + + + alt: Low + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.491850511360857,47.36774777095399,521.0 8.493236481345823,47.37021642084468,521.0 8.490620125766261,47.369968081354884,521.0 8.48962145300232,47.368391591949525,521.0 8.491850511360857,47.36774777095399,521.0 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin20 + + 1 + + + + 8.487627177694923,47.37022567192925,0.0 8.488541433192031,47.370700520846526,0.0 8.488146211772209,47.371136886332906,0.0 8.487235428789324,47.37064948552166,0.0 8.487627177694923,47.37022567192925,0.0 + + + + + + + speed: Enroute (30) + #msn_ylw-pushpin20 + + 1 + + + + 8.489638509156725,47.367439591965365,0.0 8.492925998859539,47.37003155923135,0.0 8.49104344194709,47.371956544316674,0.0 8.488021081836711,47.369439356682854,0.0 8.489638509156725,47.367439591965365,0.0 + + + + + + + operator_location + #m_ylw-pushpin1 + + 1 + 8.487908591798265,47.37085983569079,0.0 + + + + + flight: circle + 1 + serial_number: ULYK563L5G +aircraft_type: Helicopter +operation_description: Circle flight +operator_id: CHE-RP-iwetcg7ucfopc +operator_name: John Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + South + #msn_ylw-pushpin10 + + 1 + + 8.508229854920197,47.37074558658443,0.0 8.508329471365594,47.370817837505804,0.0 8.508375471496616,47.37084917525504,0.0 8.508421911721352,47.37088057176483,0.0 8.508456791062694,47.370907107576116,0.0 8.508519008377102,47.370935098462986,0.0 8.508581245669442,47.37096309822762,0.0 8.508647221360685,47.371003666692424,0.0 8.50878573903851,47.371065588974794,0.0 8.5088116351726,47.3710910227504,0.0 8.508825848236209,47.37113965697517,0.0 8.508843658918347,47.37119784355049,0.0 8.508861648775275,47.37125596075003,0.0 8.508870643430166,47.371285017030544,0.0 8.50888532292006,47.37133349567566,0.0 8.508888431723344,47.37140544348874,0.0 8.508881475041415,47.371448736079756,0.0 8.508854363332072,47.37150754532702,0.0 8.508812856615459,47.37157081371983,0.0 8.50877499150027,47.371644036289254,0.0 8.5087471867464,47.371693159220264,0.0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.507599206246548,47.370506331316705,529.0 8.50773686257555,47.370357145137795,529.0 8.508527190331814,47.37084454793311,529.0 8.508389720684472,47.371003666625,529.0 8.507599206246548,47.370506331316705,529.0 + + + + + + + alt: High + #msn_ylw-pushpin3 + + 1 + 1 + absolute + + + + 8.509267459600915,47.37166062967849,655.0 8.509977536459923,47.370984073534544,655.0 8.513313619548607,47.37238015512596,655.0 8.511518892767237,47.374387522193466,655.0 8.509267459600915,47.37166062967849,655.0 + + + + + + + alt: Low + #msn_ylw-pushpin0 + + 1 + 1 + absolute + + + + 8.510488970874699,47.375428562929855,570.0 8.511780618890493,47.378005797139494,570.0 8.5087685286589,47.378074018769155,570.0 8.507453168467146,47.37563485906732,570.0 8.510488970874699,47.375428562929855,570.0 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin2 + + 1 + + + + 8.507706170711212,47.36995718766655,0.0 8.508846636784208,47.37042600425997,0.0 8.508196425088466,47.371282684720995,0.0 8.507065280715347,47.37068957178074,0.0 8.507706170711212,47.36995718766655,0.0 + + + + + + + speed: Enroute (30) + #msn_ylw-pushpin2 + + 1 + + + + 8.508098610981899,47.371873583853734,0.0 8.513152274264935,47.37332523922927,0.0 8.514160531251842,47.37729110145004,0.0 8.509590109282963,47.37815602158183,0.0 8.50515942299658,47.37523070910003,0.0 8.508098610981899,47.371873583853734,0.0 + + + + + + + operator_location + #m_ylw-pushpin + + 1 + 8.507979542766373,47.370902783740675,0.0 + + + + + + + diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_v.json b/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_v.json new file mode 100644 index 0000000000..d25caf3dde --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_accuracy_v.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_alt.json b/monitoring/uss_qualifier/test_data/test/invalid_no_alt.json new file mode 100644 index 0000000000..93aaaadd23 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_alt.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_height.json b/monitoring/uss_qualifier/test_data/test/invalid_no_height.json new file mode 100644 index 0000000000..ef15ea3992 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_height.json @@ -0,0 +1,784 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "reference": "TakeoffLocation" + } + }, + "height": { + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_height_type.json b/monitoring/uss_qualifier/test_data/test/invalid_no_height_type.json new file mode 100644 index 0000000000..3d6bbb27f1 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_height_type.json @@ -0,0 +1,784 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0 + } + }, + "height": { + "distance": 0.0 + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_speed.json b/monitoring/uss_qualifier/test_data/test/invalid_no_speed.json new file mode 100644 index 0000000000..19fd86d9e2 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_speed.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_speed_accuracy.json b/monitoring/uss_qualifier/test_data/test/invalid_no_speed_accuracy.json new file mode 100644 index 0000000000..4012bde18f --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_speed_accuracy.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_speed_accuracy.kml b/monitoring/uss_qualifier/test_data/test/invalid_no_speed_accuracy.kml new file mode 100644 index 0000000000..ecd7205ef0 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_speed_accuracy.kml @@ -0,0 +1,531 @@ + + + + rid.kml + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl0 + + + + + normal + #s_ylw-pushpin0 + + + highlight + #s_ylw-pushpin_hl + + + + + normal + #s_ylw-pushpin1 + + + highlight + #s_ylw-pushpin_hl00 + + + + + normal + #sn_ylw-pushpin + + + highlight + #sh_ylw-pushpin + + + + + normal + #sn_ylw-pushpin0 + + + highlight + #sh_ylw-pushpin2 + + + + + normal + #sn_ylw-pushpin10 + + + highlight + #sh_ylw-pushpin1 + + + + + normal + #sn_ylw-pushpin20 + + + highlight + #sh_ylw-pushpin00 + + + + + normal + #sn_ylw-pushpin2 + + + highlight + #sh_ylw-pushpin0 + + + + + normal + #sn_ylw-pushpin3 + + + highlight + #sh_ylw-pushpin3 + + + + + + + + + + + + + + + + + + + + + + zurich + 1 + + flight: south u + 1 + serial_number: 1AKGAAN8G70R7JS +aircraft_type: Helicopter +operation_description: U-turn south of takeoff +operator_id: CHE-RP-f39mqfitkh9e5 +operator_name: Jane Doe +timestamp_accuracy: 1.0 +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + + U turn south + #msn_ylw-pushpin10 + + 1 + + 8.488086010193248,47.37068005363017,0.0 8.48812954746956,47.370587242025366,0.0 8.488096461986222,47.37048552808266,0.0 8.488058869601295,47.370437593647054,0.0 8.487992713476777,47.37035616148304,0.0 8.48798464448449,47.370333056885144,0.0 8.487973904140395,47.3703023046547,0.0 8.487966840917556,47.370266323893794,0.0 8.487962372282103,47.37023816260832,0.0 8.487967675859187,47.370207342634934,0.0 8.487976559973054,47.370171398603745,0.0 8.48799807719684,47.37014036227894,0.0 8.488019499902185,47.37010942090538,0.0 8.488053429665532,47.37008339144921,0.0 8.488089449646983,47.37006527181478,0.0 8.488121925894466,47.370052282478674,0.0 8.48815088026746,47.37004440849303,0.0 8.48827518488533,47.36999864947174,0.0 8.48831139751754,47.369972421106304,0.0 8.48835375032042,47.36995426515459,0.0 8.4883924829112,47.3699319376166,0.0 8.488426644184889,47.369913985083514,0.0 8.48846068845649,47.36989610981006,0.0 8.488495983730138,47.369878322762034,0.0 8.48853439095125,47.369864919157145,0.0 8.488562728428482,47.36985722273458,0.0 8.488595159283255,47.369853852306214,0.0 8.488636589108003,47.369849906174295,0.0 8.48867952953342,47.36985020494702,0.0 8.488708578938505,47.36984763686421,0.0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.487599206246548,47.370506331316705,496.0 8.48773686257555,47.370357145137795,496.0 8.488527190331814,47.37084454793311,496.0 8.488389720684472,47.371003666625,496.0 8.487599206246548,47.370506331316705,496.0 + + + + + + + alt: High + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.48957979534743,47.369927140429084,551.0 8.490587218626521,47.37027611497966,551.0 8.490661563415705,47.37107682800347,551.0 8.489204440074536,47.370663517599084,551.0 8.48957979534743,47.369927140429084,551.0 + + + + + + + alt: Low + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.491850511360857,47.36774777095399,521.0 8.493236481345823,47.37021642084468,521.0 8.490620125766261,47.369968081354884,521.0 8.48962145300232,47.368391591949525,521.0 8.491850511360857,47.36774777095399,521.0 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin20 + + 1 + + + + 8.487627177694923,47.37022567192925,0.0 8.488541433192031,47.370700520846526,0.0 8.488146211772209,47.371136886332906,0.0 8.487235428789324,47.37064948552166,0.0 8.487627177694923,47.37022567192925,0.0 + + + + + + + speed: Enroute (30) + #msn_ylw-pushpin20 + + 1 + + + + 8.489638509156725,47.367439591965365,0.0 8.492925998859539,47.37003155923135,0.0 8.49104344194709,47.371956544316674,0.0 8.488021081836711,47.369439356682854,0.0 8.489638509156725,47.367439591965365,0.0 + + + + + + + operator_location + #m_ylw-pushpin1 + + 1 + 8.487908591798265,47.37085983569079,0.0 + + + + + flight: circle + 1 + serial_number: ULYK563L5G +aircraft_type: Helicopter +operation_description: Circle flight +operator_id: CHE-RP-iwetcg7ucfopc +operator_name: John Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + South + #msn_ylw-pushpin10 + + 1 + + 8.508229854920197,47.37074558658443,0.0 8.508329471365594,47.370817837505804,0.0 8.508375471496616,47.37084917525504,0.0 8.508421911721352,47.37088057176483,0.0 8.508456791062694,47.370907107576116,0.0 8.508519008377102,47.370935098462986,0.0 8.508581245669442,47.37096309822762,0.0 8.508647221360685,47.371003666692424,0.0 8.50878573903851,47.371065588974794,0.0 8.5088116351726,47.3710910227504,0.0 8.508825848236209,47.37113965697517,0.0 8.508843658918347,47.37119784355049,0.0 8.508861648775275,47.37125596075003,0.0 8.508870643430166,47.371285017030544,0.0 8.50888532292006,47.37133349567566,0.0 8.508888431723344,47.37140544348874,0.0 8.508881475041415,47.371448736079756,0.0 8.508854363332072,47.37150754532702,0.0 8.508812856615459,47.37157081371983,0.0 8.50877499150027,47.371644036289254,0.0 8.5087471867464,47.371693159220264,0.0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.507599206246548,47.370506331316705,529.0 8.50773686257555,47.370357145137795,529.0 8.508527190331814,47.37084454793311,529.0 8.508389720684472,47.371003666625,529.0 8.507599206246548,47.370506331316705,529.0 + + + + + + + alt: High + #msn_ylw-pushpin3 + + 1 + 1 + absolute + + + + 8.509267459600915,47.37166062967849,655.0 8.509977536459923,47.370984073534544,655.0 8.513313619548607,47.37238015512596,655.0 8.511518892767237,47.374387522193466,655.0 8.509267459600915,47.37166062967849,655.0 + + + + + + + alt: Low + #msn_ylw-pushpin0 + + 1 + 1 + absolute + + + + 8.510488970874699,47.375428562929855,570.0 8.511780618890493,47.378005797139494,570.0 8.5087685286589,47.378074018769155,570.0 8.507453168467146,47.37563485906732,570.0 8.510488970874699,47.375428562929855,570.0 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin2 + + 1 + + + + 8.507706170711212,47.36995718766655,0.0 8.508846636784208,47.37042600425997,0.0 8.508196425088466,47.371282684720995,0.0 8.507065280715347,47.37068957178074,0.0 8.507706170711212,47.36995718766655,0.0 + + + + + + + speed: Enroute (30) + #msn_ylw-pushpin2 + + 1 + + + + 8.508098610981899,47.371873583853734,0.0 8.513152274264935,47.37332523922927,0.0 8.514160531251842,47.37729110145004,0.0 8.509590109282963,47.37815602158183,0.0 8.50515942299658,47.37523070910003,0.0 8.508098610981899,47.371873583853734,0.0 + + + + + + + operator_location + #m_ylw-pushpin + + 1 + 8.507979542766373,47.370902783740675,0.0 + + + + + + + diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_timestamp.json b/monitoring/uss_qualifier/test_data/test/invalid_no_timestamp.json new file mode 100644 index 0000000000..9ffd24ad0f --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_timestamp.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_timestamp_accuracy.json b/monitoring/uss_qualifier/test_data/test/invalid_no_timestamp_accuracy.json new file mode 100644 index 0000000000..8a829aba80 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_timestamp_accuracy.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_track.json b/monitoring/uss_qualifier/test_data/test/invalid_no_track.json new file mode 100644 index 0000000000..a197df1a47 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_track.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_uas_id.json b/monitoring/uss_qualifier/test_data/test/invalid_no_uas_id.json new file mode 100644 index 0000000000..4d42f5403b --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_uas_id.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_no_vertical_speed.json b/monitoring/uss_qualifier/test_data/test/invalid_no_vertical_speed.json new file mode 100644 index 0000000000..55ce8d3f85 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_no_vertical_speed.json @@ -0,0 +1,785 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps" + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_accuracy_h.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_accuracy_h.json new file mode 100644 index 0000000000..d13f7e652c --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_accuracy_h.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "WRONG", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_accuracy_v.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_accuracy_v.json new file mode 100644 index 0000000000..59f13430d1 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_accuracy_v.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "WRONG", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_height_type.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_height_type.json new file mode 100644 index 0000000000..8613ee2b4e --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_height_type.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "WRONG" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_operational_status.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_operational_status.json new file mode 100644 index 0000000000..627908c14a --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_operational_status.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "WRONG", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_registration_id.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_registration_id.json new file mode 100644 index 0000000000..796199b4b2 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_registration_id.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "registration_id": "WRONG", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_serial_number.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_serial_number.json new file mode 100644 index 0000000000..5c20a3de31 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_serial_number.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "WRONG", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_serial_number.kml b/monitoring/uss_qualifier/test_data/test/invalid_wrong_serial_number.kml new file mode 100644 index 0000000000..955bebcb60 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_serial_number.kml @@ -0,0 +1,532 @@ + + + + rid.kml + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl0 + + + + + normal + #s_ylw-pushpin0 + + + highlight + #s_ylw-pushpin_hl + + + + + normal + #s_ylw-pushpin1 + + + highlight + #s_ylw-pushpin_hl00 + + + + + normal + #sn_ylw-pushpin + + + highlight + #sh_ylw-pushpin + + + + + normal + #sn_ylw-pushpin0 + + + highlight + #sh_ylw-pushpin2 + + + + + normal + #sn_ylw-pushpin10 + + + highlight + #sh_ylw-pushpin1 + + + + + normal + #sn_ylw-pushpin20 + + + highlight + #sh_ylw-pushpin00 + + + + + normal + #sn_ylw-pushpin2 + + + highlight + #sh_ylw-pushpin0 + + + + + normal + #sn_ylw-pushpin3 + + + highlight + #sh_ylw-pushpin3 + + + + + + + + + + + + + + + + + + + + + + zurich + 1 + + flight: train tracks + 1 + serial_number: WRONG +aircraft_type: Helicopter +operation_description: Train tracks +operator_id: CHE-RP-f39mqfitkh9e5 +operator_name: Jane Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + Train + #msn_ylw-pushpin10 + + 1 + + 8.5233997,47.3714318,0 8.5231624,47.3716534,0 8.522988,47.3717651,0 8.5226743,47.3720161,0 8.5221003,47.372423,0 8.5210578,47.3731616,0 8.5203497,47.3736957,0 8.5199045,47.3740953,0 8.519368,47.3746584,0 8.5190408,47.375127,0 8.5188692,47.3757809,0 8.5187887,47.3766963,0 8.519368,47.3779908,0 8.519883,47.378434,0 8.5203121,47.3789425,0 8.5208378,47.3794983,0 8.5214333,47.3800069,0 8.5219268,47.3803338,0 8.5229407,47.3807043,0 8.5242496,47.3808968,0 8.5254298,47.3811002,0 8.5259984,47.3813399,0 8.5264078,47.3816867,0 8.5264721,47.382188,0 8.5263327,47.3824858,0 8.5258606,47.3828926,0 8.5251847,47.3832123,0 8.5241762,47.3833648,0 8.5228673,47.3835101,0 8.5211077,47.3836699,0 8.5200134,47.3838152,0 8.5185757,47.3841203,0 8.5175458,47.384331,0 8.5154845,47.3849293,0 8.5141112,47.3852489,0 8.5131886,47.3857937,0 8.5123517,47.3864475,0 8.5117831,47.3869051,0 8.5109784,47.3874499,0 8.5104205,47.3877622,0 8.5092725,47.3879656,0 8.5081782,47.3880019,0 8.5067513,47.3879802,0 8.5053565,47.3879802,0 8.5045518,47.3880019,0 8.5028138,47.3879947,0 8.5012044,47.3881545,0 8.4999556,47.3887242,0 8.4995157,47.3891455,0 8.4986359,47.3897629,0 8.4973485,47.3898936,0 8.4965867,47.3895522,0 8.4958035,47.3896394,0 8.4951491,47.3901115,0 8.4949989,47.3905909,0 8.4949667,47.3911793,0 8.4951383,47.3915787,0 8.4956855,47.3918838,0 8.4965867,47.3919056,0 8.4973163,47.3918838,0 8.4978527,47.3917458,0 8.498357,47.3914553,0 8.4986252,47.3912301,0 8.4989471,47.3909904,0 8.4993762,47.3908451,0 8.499741,47.3908233,0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.5207302,47.3679382,450 8.518331,47.372555,450 8.527615,47.3731521,450 8.5270762,47.371081,450 8.5207302,47.3679382,450 + + + + + + + alt: High + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.5118972,47.3744194,570 8.5202778,47.3750873,570 8.5206942,47.378602,570 8.5119676,47.3784622,570 8.5118972,47.3744194,570 + + + + + + + alt: Low + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.5022266,47.3860771,478 8.5011602,47.392333,478 8.4880906,47.3936745,478 8.487937,47.3869263,478 8.5022266,47.3860771,478 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin20 + + 1 + + + + 8.5247928,47.3713996,0 8.5240951,47.3721486,0 8.5222242,47.3716635,0 8.5231591,47.370904,0 8.5247928,47.3713996,0 + + + + + + + speed: Enroute (30) + #msn_ylw-pushpin20 + + 1 + + + + 8.490508,47.3932007,0 8.5044748,47.3920025,0 8.5057811,47.3847686,0 8.4907558,47.3853825,0 8.490508,47.3932007,0 + + + + + + + operator_location + #m_ylw-pushpin1 + + 1 + + 8.523948,47.370838,0 + + + + + + flight: ETA + 1 + serial_number: ULYK563L5G +aircraft_type: Helicopter +operation_description: Circle flight around ETH +operator_id: CHE-RP-iwetcg7ucfopc +operator_name: John Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + ETH + #msn_ylw-pushpin10 + + 1 + + 8.5506164,47.3752894,0 8.5507881,47.3752022,0 8.550949,47.3752349,0 8.5510778,47.3753366,0 8.5510671,47.3754892,0 8.5509705,47.3755728,0 8.5507935,47.3756236,0 8.5505521,47.3756382,0 8.5502892,47.3755365,0 8.5502141,47.3753475,0 8.5501766,47.3751732,0 8.5502785,47.3750097,0 8.5506486,47.3749044,0 8.5510027,47.3748971,0 8.5513353,47.3749443,0 8.5515659,47.3749988,0 8.5516571,47.3751659,0 8.5516679,47.3752531,0 8.5516303,47.3754456,0 8.5515499,47.3756309,0 8.5514211,47.375758,0 8.5512494,47.3758852,0 8.5509866,47.3760232,0 8.5507613,47.3761249,0 8.5505145,47.3762339,0 8.5502248,47.3763538,0 8.5500156,47.3764701,0 8.5498601,47.3765173,0 8.5496455,47.3765863,0 8.5494524,47.3766807,0 8.5492378,47.3767897,0 8.5489642,47.3769387,0 8.548696,47.3770367,0 8.5483848,47.3771494,0 8.5481488,47.3772765,0 8.5479396,47.3773782,0 8.5475051,47.377469,0 8.5473602,47.3773891,0 8.5473066,47.3771312,0 8.547151,47.3770258,0 8.546797,47.3769859,0 8.5465341,47.3770404,0 8.5462874,47.3771966,0 8.5461693,47.3773964,0 8.5460781,47.3776289,0 8.546046,47.3778141,0 8.5459279,47.3779558,0 8.5458046,47.3781302,0 8.5456973,47.3783517,0 8.5456758,47.3784135,0 8.545987,47.3785188,0 8.5462391,47.3786423,0 8.5464,47.3787005,0 8.5463142,47.3788639,0 8.5462284,47.379031,0 8.5461908,47.3791218,0 8.5465341,47.379209,0 8.5467755,47.3792744,0 8.5470169,47.3792926,0 8.5471349,47.3791981,0 8.5472154,47.3790419,0 8.5473227,47.378893,0 8.5474675,47.3786896,0 8.547607,47.378497,0 8.5477304,47.3783735,0 8.5478484,47.3782682,0 8.5480093,47.3780757,0 8.5481434,47.3780502,0 8.5483366,47.3779558,0 8.548696,47.3779413,0 8.5489642,47.3779304,0 8.5492378,47.3779304,0 8.5494577,47.3779485,0 8.5496348,47.377974,0 8.5498762,47.3780611,0 8.5500693,47.3781302,0 8.5501497,47.3781846,0 8.5500371,47.3783735,0 8.5498279,47.378497,0 8.5496294,47.3786678,0 8.5494953,47.3787804,0 8.5491627,47.3788167,0 8.548814,47.3788167,0 8.5485029,47.3788131,0 8.5483097,47.3788167,0 8.5481274,47.3788167,0 8.5480576,47.3786569,0 8.5480791,47.3784244,0 8.5480844,47.3782391,0 8.5480683,47.378003,0 8.5480952,47.3779013,0 8.5482025,47.3777451,0 8.5482668,47.3775562,0 8.548358,47.3774218,0 8.5484492,47.3773237,0 8.548637,47.3771784,0 8.5488676,47.3771167,0 8.5491466,47.3770985,0 8.5493558,47.3771748,0 8.5496777,47.3772874,0 8.5498118,47.3773601,0 8.5499674,47.3774145,0 8.5503053,47.3774908,0 8.5504341,47.3775707,0 8.5506915,47.3776616,0 8.5509008,47.3777415,0 8.551169,47.3777524,0 8.5514211,47.3775925,0 8.5515659,47.3774654,0 8.5518073,47.3773455,0 8.5520166,47.3772438,0 8.5522365,47.3771494,0 8.552494,47.3770513,0 8.55273,47.3770476,0 8.5528856,47.3771239,0 8.5532557,47.3771094,0 8.5535454,47.3770694,0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.550622767214215,47.37532471080826,529 8.550968690310363,47.37517998821773,529 8.55139147011187,47.37525984247981,529 8.55105745570194,47.3757494351859,529 8.550583069630015,47.3755709047547,529 8.550622767214215,47.37532471080826,529 + + + + + + + alt: High + #msn_ylw-pushpin3 + + 1 + 1 + absolute + + + + 8.544333807459472,47.3798843562703,655 8.545011542595343,47.37680317592417,655 8.547742218426757,47.37668348981232,655 8.552944747200721,47.37950457579466,655 8.544333807459472,47.3798843562703,655 + + + + + + + alt: Low + #msn_ylw-pushpin0 + + 1 + 1 + absolute + + + + 8.554876335105625,47.37664451545904,570 8.555238965142005,47.37783481116264,570 8.552105010325326,47.37790597015967,570 8.551718647190281,47.37669461906262,570 8.554876335105625,47.37664451545904,570 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin2 + + 1 + + + + 8.549095584214793,47.37494736510541,0 8.551815498608766,47.37437716793783,0 8.552606086871242,47.37519352831755,0 8.55101776114218,47.37630422675167,0 8.549095584214793,47.37494736510541,0 + + + + + + + speed: Enroute (60) + #msn_ylw-pushpin2 + + 1 + + + + 8.544927914901734,47.37580272082323,0 8.554358343240745,47.3768789300979,0 8.554196235097036,47.37898777417489,0 8.547851026312099,47.3798679606927,0 8.542984878232653,47.37889733073179,0 8.544927914901734,47.37580272082323,0 + + + + + + + operator_location + #m_ylw-pushpin + + 1 + 8.5510206,47.3757389,0 + + + + + + diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_speed.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_speed.json new file mode 100644 index 0000000000..5e34c0b498 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_speed.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": -5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_speed_accuracy.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_speed_accuracy.json new file mode 100644 index 0000000000..7a3a29d3b8 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_speed_accuracy.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "WRONG", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_timestamp.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_timestamp.json new file mode 100644 index 0000000000..b3dec9ac72 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_timestamp.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "42", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_timestamp_accuracy.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_timestamp_accuracy.json new file mode 100644 index 0000000000..793ace4693 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_timestamp_accuracy.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": -42, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_track.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_track.json new file mode 100644 index 0000000000..9ec539c20c --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_track.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 365, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_ua_type.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_ua_type.json new file mode 100644 index 0000000000..240f8e5686 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_ua_type.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "WRONG" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "WRONG" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_ua_type.kml b/monitoring/uss_qualifier/test_data/test/invalid_wrong_ua_type.kml new file mode 100644 index 0000000000..ea601ac861 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_ua_type.kml @@ -0,0 +1,532 @@ + + + + rid.kml + + + normal + #s_ylw-pushpin + + + highlight + #s_ylw-pushpin_hl0 + + + + + normal + #s_ylw-pushpin0 + + + highlight + #s_ylw-pushpin_hl + + + + + normal + #s_ylw-pushpin1 + + + highlight + #s_ylw-pushpin_hl00 + + + + + normal + #sn_ylw-pushpin + + + highlight + #sh_ylw-pushpin + + + + + normal + #sn_ylw-pushpin0 + + + highlight + #sh_ylw-pushpin2 + + + + + normal + #sn_ylw-pushpin10 + + + highlight + #sh_ylw-pushpin1 + + + + + normal + #sn_ylw-pushpin20 + + + highlight + #sh_ylw-pushpin00 + + + + + normal + #sn_ylw-pushpin2 + + + highlight + #sh_ylw-pushpin0 + + + + + normal + #sn_ylw-pushpin3 + + + highlight + #sh_ylw-pushpin3 + + + + + + + + + + + + + + + + + + + + + + zurich + 1 + + flight: train tracks + 1 + serial_number: 1AKGAAN8G70R7JS +aircraft_type: WRONG +operation_description: Train tracks +operator_id: CHE-RP-f39mqfitkh9e5 +operator_name: Jane Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + Train + #msn_ylw-pushpin10 + + 1 + + 8.5233997,47.3714318,0 8.5231624,47.3716534,0 8.522988,47.3717651,0 8.5226743,47.3720161,0 8.5221003,47.372423,0 8.5210578,47.3731616,0 8.5203497,47.3736957,0 8.5199045,47.3740953,0 8.519368,47.3746584,0 8.5190408,47.375127,0 8.5188692,47.3757809,0 8.5187887,47.3766963,0 8.519368,47.3779908,0 8.519883,47.378434,0 8.5203121,47.3789425,0 8.5208378,47.3794983,0 8.5214333,47.3800069,0 8.5219268,47.3803338,0 8.5229407,47.3807043,0 8.5242496,47.3808968,0 8.5254298,47.3811002,0 8.5259984,47.3813399,0 8.5264078,47.3816867,0 8.5264721,47.382188,0 8.5263327,47.3824858,0 8.5258606,47.3828926,0 8.5251847,47.3832123,0 8.5241762,47.3833648,0 8.5228673,47.3835101,0 8.5211077,47.3836699,0 8.5200134,47.3838152,0 8.5185757,47.3841203,0 8.5175458,47.384331,0 8.5154845,47.3849293,0 8.5141112,47.3852489,0 8.5131886,47.3857937,0 8.5123517,47.3864475,0 8.5117831,47.3869051,0 8.5109784,47.3874499,0 8.5104205,47.3877622,0 8.5092725,47.3879656,0 8.5081782,47.3880019,0 8.5067513,47.3879802,0 8.5053565,47.3879802,0 8.5045518,47.3880019,0 8.5028138,47.3879947,0 8.5012044,47.3881545,0 8.4999556,47.3887242,0 8.4995157,47.3891455,0 8.4986359,47.3897629,0 8.4973485,47.3898936,0 8.4965867,47.3895522,0 8.4958035,47.3896394,0 8.4951491,47.3901115,0 8.4949989,47.3905909,0 8.4949667,47.3911793,0 8.4951383,47.3915787,0 8.4956855,47.3918838,0 8.4965867,47.3919056,0 8.4973163,47.3918838,0 8.4978527,47.3917458,0 8.498357,47.3914553,0 8.4986252,47.3912301,0 8.4989471,47.3909904,0 8.4993762,47.3908451,0 8.499741,47.3908233,0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.5207302,47.3679382,450 8.518331,47.372555,450 8.527615,47.3731521,450 8.5270762,47.371081,450 8.5207302,47.3679382,450 + + + + + + + alt: High + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.5118972,47.3744194,570 8.5202778,47.3750873,570 8.5206942,47.378602,570 8.5119676,47.3784622,570 8.5118972,47.3744194,570 + + + + + + + alt: Low + #msn_ylw-pushpin + + 1 + 1 + absolute + + + + 8.5022266,47.3860771,478 8.5011602,47.392333,478 8.4880906,47.3936745,478 8.487937,47.3869263,478 8.5022266,47.3860771,478 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin20 + + 1 + + + + 8.5247928,47.3713996,0 8.5240951,47.3721486,0 8.5222242,47.3716635,0 8.5231591,47.370904,0 8.5247928,47.3713996,0 + + + + + + + speed: Enroute (30) + #msn_ylw-pushpin20 + + 1 + + + + 8.490508,47.3932007,0 8.5044748,47.3920025,0 8.5057811,47.3847686,0 8.4907558,47.3853825,0 8.490508,47.3932007,0 + + + + + + + operator_location + #m_ylw-pushpin1 + + 1 + + 8.523948,47.370838,0 + + + + + + flight: ETA + 1 + serial_number: ULYK563L5G +aircraft_type: Helicopter +operation_description: Circle flight around ETH +operator_id: CHE-RP-iwetcg7ucfopc +operator_name: John Doe +timestamp_accuracy: 1.0 +speed_accuracy: SA3mps +sample_rate: 1.0 +accuracy_h: HAUnknown +accuracy_v: VAUnknown + + ETH + #msn_ylw-pushpin10 + + 1 + + 8.5506164,47.3752894,0 8.5507881,47.3752022,0 8.550949,47.3752349,0 8.5510778,47.3753366,0 8.5510671,47.3754892,0 8.5509705,47.3755728,0 8.5507935,47.3756236,0 8.5505521,47.3756382,0 8.5502892,47.3755365,0 8.5502141,47.3753475,0 8.5501766,47.3751732,0 8.5502785,47.3750097,0 8.5506486,47.3749044,0 8.5510027,47.3748971,0 8.5513353,47.3749443,0 8.5515659,47.3749988,0 8.5516571,47.3751659,0 8.5516679,47.3752531,0 8.5516303,47.3754456,0 8.5515499,47.3756309,0 8.5514211,47.375758,0 8.5512494,47.3758852,0 8.5509866,47.3760232,0 8.5507613,47.3761249,0 8.5505145,47.3762339,0 8.5502248,47.3763538,0 8.5500156,47.3764701,0 8.5498601,47.3765173,0 8.5496455,47.3765863,0 8.5494524,47.3766807,0 8.5492378,47.3767897,0 8.5489642,47.3769387,0 8.548696,47.3770367,0 8.5483848,47.3771494,0 8.5481488,47.3772765,0 8.5479396,47.3773782,0 8.5475051,47.377469,0 8.5473602,47.3773891,0 8.5473066,47.3771312,0 8.547151,47.3770258,0 8.546797,47.3769859,0 8.5465341,47.3770404,0 8.5462874,47.3771966,0 8.5461693,47.3773964,0 8.5460781,47.3776289,0 8.546046,47.3778141,0 8.5459279,47.3779558,0 8.5458046,47.3781302,0 8.5456973,47.3783517,0 8.5456758,47.3784135,0 8.545987,47.3785188,0 8.5462391,47.3786423,0 8.5464,47.3787005,0 8.5463142,47.3788639,0 8.5462284,47.379031,0 8.5461908,47.3791218,0 8.5465341,47.379209,0 8.5467755,47.3792744,0 8.5470169,47.3792926,0 8.5471349,47.3791981,0 8.5472154,47.3790419,0 8.5473227,47.378893,0 8.5474675,47.3786896,0 8.547607,47.378497,0 8.5477304,47.3783735,0 8.5478484,47.3782682,0 8.5480093,47.3780757,0 8.5481434,47.3780502,0 8.5483366,47.3779558,0 8.548696,47.3779413,0 8.5489642,47.3779304,0 8.5492378,47.3779304,0 8.5494577,47.3779485,0 8.5496348,47.377974,0 8.5498762,47.3780611,0 8.5500693,47.3781302,0 8.5501497,47.3781846,0 8.5500371,47.3783735,0 8.5498279,47.378497,0 8.5496294,47.3786678,0 8.5494953,47.3787804,0 8.5491627,47.3788167,0 8.548814,47.3788167,0 8.5485029,47.3788131,0 8.5483097,47.3788167,0 8.5481274,47.3788167,0 8.5480576,47.3786569,0 8.5480791,47.3784244,0 8.5480844,47.3782391,0 8.5480683,47.378003,0 8.5480952,47.3779013,0 8.5482025,47.3777451,0 8.5482668,47.3775562,0 8.548358,47.3774218,0 8.5484492,47.3773237,0 8.548637,47.3771784,0 8.5488676,47.3771167,0 8.5491466,47.3770985,0 8.5493558,47.3771748,0 8.5496777,47.3772874,0 8.5498118,47.3773601,0 8.5499674,47.3774145,0 8.5503053,47.3774908,0 8.5504341,47.3775707,0 8.5506915,47.3776616,0 8.5509008,47.3777415,0 8.551169,47.3777524,0 8.5514211,47.3775925,0 8.5515659,47.3774654,0 8.5518073,47.3773455,0 8.5520166,47.3772438,0 8.5522365,47.3771494,0 8.552494,47.3770513,0 8.55273,47.3770476,0 8.5528856,47.3771239,0 8.5532557,47.3771094,0 8.5535454,47.3770694,0 + + + + + alt: Takeoff + #m_ylw-pushpin0 + + 1 + absolute + + + + 8.550622767214215,47.37532471080826,529 8.550968690310363,47.37517998821773,529 8.55139147011187,47.37525984247981,529 8.55105745570194,47.3757494351859,529 8.550583069630015,47.3755709047547,529 8.550622767214215,47.37532471080826,529 + + + + + + + alt: High + #msn_ylw-pushpin3 + + 1 + 1 + absolute + + + + 8.544333807459472,47.3798843562703,655 8.545011542595343,47.37680317592417,655 8.547742218426757,47.37668348981232,655 8.552944747200721,47.37950457579466,655 8.544333807459472,47.3798843562703,655 + + + + + + + alt: Low + #msn_ylw-pushpin0 + + 1 + 1 + absolute + + + + 8.554876335105625,47.37664451545904,570 8.555238965142005,47.37783481116264,570 8.552105010325326,47.37790597015967,570 8.551718647190281,47.37669461906262,570 8.554876335105625,47.37664451545904,570 + + + + + + + speed: Departure (5) + #msn_ylw-pushpin2 + + 1 + + + + 8.549095584214793,47.37494736510541,0 8.551815498608766,47.37437716793783,0 8.552606086871242,47.37519352831755,0 8.55101776114218,47.37630422675167,0 8.549095584214793,47.37494736510541,0 + + + + + + + speed: Enroute (60) + #msn_ylw-pushpin2 + + 1 + + + + 8.544927914901734,47.37580272082323,0 8.554358343240745,47.3768789300979,0 8.554196235097036,47.37898777417489,0 8.547851026312099,47.3798679606927,0 8.542984878232653,47.37889733073179,0 8.544927914901734,47.37580272082323,0 + + + + + + + operator_location + #m_ylw-pushpin + + 1 + 8.5510206,47.3757389,0 + + + + + + diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_utm_id.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_utm_id.json new file mode 100644 index 0000000000..b00e5c175a --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_utm_id.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "utm_id": "!", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/invalid_wrong_vertical_speed.json b/monitoring/uss_qualifier/test_data/test/invalid_wrong_vertical_speed.json new file mode 100644 index 0000000000..6ac2669c94 --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/invalid_wrong_vertical_speed.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 999999999999999999999999999999999 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/test/zurich.json b/monitoring/uss_qualifier/test_data/test/zurich.json new file mode 100644 index 0000000000..220e4d4e1e --- /dev/null +++ b/monitoring/uss_qualifier/test_data/test/zurich.json @@ -0,0 +1,786 @@ +{ + "flights": [ + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488106090860573, + "lat": 47.37063724619898, + "alt": 496.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 162.3752362098515, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488126171527895, + "lat": 47.37059443876779, + "alt": 496.18307267903697, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.1830726790369681, + "reference": "TakeoffLocation" + }, + "track": 187.50097320343912, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.18 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488117678127974, + "lat": 47.370550752380716, + "alt": 498.53226199914917, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 2.5322619991491706, + "reference": "TakeoffLocation" + }, + "track": 192.42362296934377, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.35 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488103410051705, + "lat": 47.37050688836104, + "alt": 500.40031939929486, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 4.400319399294858, + "reference": "TakeoffLocation" + }, + "track": 200.40286640336953, + "speed": 5.61, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.87 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488080501861498, + "lat": 47.37046517716392, + "alt": 501.65525225916707, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.655252259167071, + "reference": "TakeoffLocation" + }, + "track": 208.23182285522853, + "speed": 7.35, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.25 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488049130286102, + "lat": 47.37042560543707, + "alt": 502.4106030975036, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 6.410603097503611, + "reference": "TakeoffLocation" + }, + "track": 208.81982213774143, + "speed": 8.15, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.76 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488013259623491, + "lat": 47.37038145191838, + "alt": 503.1337164705349, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 7.133716470534921, + "reference": "TakeoffLocation" + }, + "track": 202.1993676258766, + "speed": 9.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.72 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487985396361207, + "lat": 47.37033520979453, + "alt": 504.077325459153, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.077325459152974, + "reference": "TakeoffLocation" + }, + "track": 193.30773490939308, + "speed": 11.96, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.94 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487973904140395, + "lat": 47.3703023046547, + "alt": 504.909136585584, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 8.909136585584008, + "reference": "TakeoffLocation" + }, + "track": 186.94220133247893, + "speed": 14.37, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.83 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487962372282103, + "lat": 47.37023816260832, + "alt": 506.62385364810143, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 10.62385364810143, + "reference": "TakeoffLocation" + }, + "track": 171.81024867542703, + "speed": 16.74, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.71 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.487976559973054, + "lat": 47.370171398603745, + "alt": 508.75800509123735, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.75800509123735, + "reference": "TakeoffLocation" + }, + "track": 154.86309008170363, + "speed": 18.36, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.13 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488019499902185, + "lat": 47.37010942090538, + "alt": 511.13171827832986, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 15.131718278329856, + "reference": "TakeoffLocation" + }, + "track": 132.98217445043147, + "speed": 23.11, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.37 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.488089449646983, + "lat": 47.37006527181479, + "alt": 513.3848015774483, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 17.384801577448343, + "reference": "TakeoffLocation" + }, + "track": 116.6325467769568, + "speed": 25.65, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 2.25 + }, + { + "timestamp": "2022-01-01T00:00:14+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48815088026746, + "lat": 47.37004440849303, + "alt": 514.9199444465004, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 18.91994444650038, + "reference": "TakeoffLocation" + }, + "track": 123.51221393134806, + "speed": 27.84, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.54 + }, + { + "timestamp": "2022-01-01T00:00:15+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48831139751754, + "lat": 47.369972421106304, + "alt": 518.8514455015629, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 22.85144550156292, + "reference": "TakeoffLocation" + }, + "track": 126.39759736353494, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 3.93 + }, + { + "timestamp": "2022-01-01T00:00:16+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.4883924829112, + "lat": 47.3699319376166, + "alt": 520.6710082621752, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 24.671008262175178, + "reference": "TakeoffLocation" + }, + "track": 127.79792720040928, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.82 + }, + { + "timestamp": "2022-01-01T00:00:17+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.48846068845649, + "lat": 47.36989610981006, + "alt": 522.1109485203592, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 26.110948520359216, + "reference": "TakeoffLocation" + }, + "track": 122.00014201767304, + "speed": 30.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 1.44 + } + ], + "flight_details": { + "id": "d22a1cef-a3e0-437f-92b5-44c1453f4068", + "serial_number": "1AKGAAN8G70R7JS", + "operation_description": "U-turn south of takeoff", + "operator_location": { + "lat": 47.37085983569079, + "lng": 8.487908591798265 + }, + "operator_id": "CHE-RP-f39mqfitkh9e5" + }, + "aircraft_type": "Helicopter" + }, + { + "reference_time": "2022-01-01T00:00:00+00:00", + "states": [ + { + "timestamp": "2022-01-01T00:00:01+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5082751179102, + "lat": 47.37077841542829, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 43.03829301396051, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:02+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508320380900203, + "lat": 47.37081124427215, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 44.471165943871895, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:03+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50836683820487, + "lat": 47.37084329379501, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 45.00987803090026, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:04+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508413742027768, + "lat": 47.37087504853866, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 42.283916233986616, + "speed": 5.0, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:05+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508456791062693, + "lat": 47.370907107576116, + "alt": 529.0, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 0.0, + "reference": "TakeoffLocation" + }, + "track": 56.404457560286055, + "speed": 6.78, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 0.0 + }, + { + "timestamp": "2022-01-01T00:00:06+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508512033806365, + "lat": 47.37093196068016, + "alt": 534.3462453934874, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 5.346245393487379, + "reference": "TakeoffLocation" + }, + "track": 56.40454827550896, + "speed": 8.08, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 5.35 + }, + { + "timestamp": "2022-01-01T00:00:07+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508567276608135, + "lat": 47.37095681372497, + "alt": 541.0071850407926, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 12.007185040792592, + "reference": "TakeoffLocation" + }, + "track": 49.37103887319031, + "speed": 9.5, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 6.66 + }, + { + "timestamp": "2022-01-01T00:00:08+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508635411250422, + "lat": 47.37099640465343, + "alt": 549.7092175828766, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 20.70921758287659, + "reference": "TakeoffLocation" + }, + "track": 55.26415971173848, + "speed": 10.93, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.7 + }, + { + "timestamp": "2022-01-01T00:00:09+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50872335395775, + "lat": 47.37103770064569, + "alt": 559.9711540980651, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 30.971154098065085, + "reference": "TakeoffLocation" + }, + "track": 48.272018247873895, + "speed": 12.34, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.26 + }, + { + "timestamp": "2022-01-01T00:00:10+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.5088116351726, + "lat": 47.3710910227504, + "alt": 570.9516494270789, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 41.951649427078905, + "reference": "TakeoffLocation" + }, + "track": 11.45105545883768, + "speed": 13.38, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 10.98 + }, + { + "timestamp": "2022-01-01T00:00:11+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508840417269266, + "lat": 47.37118725325097, + "alt": 580.314615001263, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 51.31461500126295, + "reference": "TakeoffLocation" + }, + "track": 11.820237778805804, + "speed": 14.58, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 9.36 + }, + { + "timestamp": "2022-01-01T00:00:12+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.508861648775275, + "lat": 47.37125596075003, + "alt": 587.3889523597257, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 58.38895235972575, + "reference": "TakeoffLocation" + }, + "track": 11.683449532931599, + "speed": 16.24, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 7.07 + }, + { + "timestamp": "2022-01-01T00:00:13+00:00", + "operational_status": "Airborne", + "position": { + "lng": 8.50888532292006, + "lat": 47.37133349567566, + "alt": 595.7395125812138, + "accuracy_h": "HAUnknown", + "accuracy_v": "VAUnknown", + "extrapolated": false, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + } + }, + "height": { + "distance": 66.73951258121383, + "reference": "TakeoffLocation" + }, + "track": 358.70456587570527, + "speed": 18.01, + "timestamp_accuracy": 1.0, + "speed_accuracy": "SA3mps", + "vertical_speed": 8.35 + } + ], + "flight_details": { + "id": "b238e812-7282-49c4-8567-f15da4815430", + "serial_number": "ULYK563L5G", + "operation_description": "Circle flight", + "operator_location": { + "lat": 47.370902783740675, + "lng": 8.507979542766373 + }, + "operator_id": "CHE-RP-iwetcg7ucfopc" + }, + "aircraft_type": "Helicopter" + } + ] +} diff --git a/monitoring/uss_qualifier/test_data/usa/kentland/rid.kml b/monitoring/uss_qualifier/test_data/usa/kentland/rid.kml index be8d4366bd..36556eed56 100644 --- a/monitoring/uss_qualifier/test_data/usa/kentland/rid.kml +++ b/monitoring/uss_qualifier/test_data/usa/kentland/rid.kml @@ -314,12 +314,12 @@ flight: south u 1 - serial_number: EXPL3123 + serial_number: MNFR5FAKE1 operation_description: U-turn south of takeoff -operator_id: EXAMPLE_OPERATOR2 +operator_id: EXAMPLE_OPERATOR1 registration_number: N.123456 aircraft_type: HybridLift -operator_name: UFT Participant 2 +operator_name: Participant 1 timestamp_accuracy: 1.0 speed_accuracy: SA3mps sample_rate: 1.0 @@ -331,7 +331,7 @@ accuracy_v: VAUnknown 1 - -80.57802028980674,37.19714355363017,0 -80.57797675253043,37.19705074202537,0 -80.57800983801377,37.19694902808266,0 -80.5780474303987,37.19690109364706,0 -80.57811358652322,37.19681966148304,0 -80.5781216555155,37.19679655688515,0 -80.5781323958596,37.1967658046547,0 -80.57813945908244,37.1967298238938,0 -80.57814392771789,37.19670166260832,0 -80.57813862414081,37.19667084263494,0 -80.57812974002694,37.19663489860375,0 -80.57810822280315,37.19660386227894,0 -80.57808680009781,37.19657292090538,0 -80.57805287033446,37.19654689144921,0 -80.57801685035301,37.19652877181478,0 -80.57798437410553,37.19651578247868,0 -80.57795541973253,37.19650790849303,0 -80.57783111511466,37.19646214947174,0 -80.57779490248245,37.19643592110631,0 -80.57775254967957,37.19641776515459,0 -80.57771381708879,37.1963954376166,0 -80.5776796558151,37.19637748508352,0 -80.5776456115435,37.19635960981006,0 -80.57761031626985,37.19634182276204,0 -80.57757190904874,37.19632841915715,0 -80.57754357157151,37.19632072273458,0 -80.57751114071674,37.19631735230622,0 -80.57746971089199,37.1963134061743,0 -80.57742677046657,37.19631370494702,0 -80.57739772106149,37.19631113686421,0 + -80.57802028980674,37.19714355363017,0 -80.57797675253043,37.19705074202537,0 -80.57800983801377,37.19694902808266,0 -80.5780474303987,37.19690109364706,0 -80.57811358652322,37.19681966148304,0 -80.5781216555155,37.19679655688515,0 -80.5781323958596,37.1967658046547,0 -80.57813945908244,37.1967298238938,0 -80.57814392771789,37.19670166260832,0 -80.57813862414081,37.19667084263494,0 -80.57812974002694,37.19663489860375,0 -80.57810822280315,37.19660386227894,0 -80.57808680009781,37.19657292090538,0 -80.57805287033446,37.19654689144921,0 -80.57801685035301,37.19652877181478,0 -80.57798437410553,37.19651578247868,0 -80.57795541973253,37.19650790849303,0 -80.57783111511466,37.19646214947174,0 -80.57779490248245,37.19643592110631,0 -80.57775254967957,37.19641776515459,0 -80.57771381708879,37.1963954376166,0 -80.5776796558151,37.19637748508352,0 -80.5776456115435,37.19635960981006,0 -80.57761031626985,37.19634182276204,0 -80.57757190904874,37.19632841915715,0 -80.57754357157151,37.19632072273458,0 -80.57751114071674,37.19631735230622,0 -80.57746971089199,37.1963134061743,0 -80.57742677046657,37.19631370494702,0 -80.57739772106149,37.19631113686421,0 -80.57737059999999,37.1963054,0 -80.57723780000001,37.1963075,0 -80.5771453,37.1963577,0 -80.5771024,37.1964656,0 -80.57712650000001,37.1965735,0 -80.5772874,37.1966355,0 -80.57744700000001,37.1966504,0 -80.57764419999999,37.1966013,0 -80.57773109999999,37.1965061,0 -80.577825,37.1963319,0 -80.5778559,37.1961578,0 -80.57773109999999,37.1960563,0 -80.57752189999999,37.196051,0 -80.5773543,37.1960606,0 -80.5771357,37.1961151,0 -80.5770485,37.1961653,0 -80.5769627,37.1962283,0 -80.5769399,37.1962913,0 -80.5769184,37.1963885,0 -80.5768514,37.1964334,0 -80.5767347,37.1964356,0 -80.5766648,37.1963944,0 -80.5766366,37.1963538,0 -80.5766393,37.196294,0 -80.5766634,37.1962096,0 -80.5766902,37.1961615,0 -80.5767707,37.1961284,0 -80.5768284,37.196091,0 -80.5768887,37.1960493,0 -80.5770175,37.195998,0 -80.5770684,37.1959745,0 -80.5771543,37.1959617,0 -80.5771757,37.1959607,0 -80.57724140000001,37.1959553,0 -80.5772937,37.195982,0 -80.57733260000001,37.1960248,0 -80.57733260000001,37.1960942,0 -80.5773246,37.1961359,0 -80.57728299999999,37.1961978,0 -80.57725619999999,37.1962512,0 -80.5772602,37.1963495,0 -80.5772656,37.1964008,0 -80.5772817,37.1964692,0 -80.5773112,37.1965076,0 -80.5773863,37.1965546,0 -80.57746,37.1965589,0 -80.57754060000001,37.1965589,0 -80.5776036,37.1965568,0 -80.57763850000001,37.1965782,0 -80.5776492,37.1966262,0 -80.57763850000001,37.1966679,0 -80.5776063,37.1966925,0 -80.57754730000001,37.1967074,0 -80.57748290000001,37.1967074,0 -80.5773823,37.1966967,0 -80.5773502,37.1966668,0 -80.5773421,37.1966241,0 -80.5773341,37.1965771,0 -80.57730050000001,37.196528,0 -80.5772281,37.1965087,0 -80.57715570000001,37.1965055,0 -80.57710470000001,37.1965205,0 -80.5770457,37.196544,0 -80.57696660000001,37.1965589,0 -80.5768834,37.196545,0 -80.5768405,37.1965119,0 -80.5767869,37.1964831,0 -80.5767601,37.1964489,0 -80.5767547,37.1964083,0 -80.5767601,37.1963528,0 -80.5767587,37.1963228,0 -80.5767654,37.1962224,0 -80.5767587,37.1961893,0 -80.5767386,37.1961669,0 -80.5766836,37.1961348,0 -80.57665280000001,37.1960889,0 -80.5766836,37.1960205,0 -80.5767373,37.1960109,0 -80.5767896,37.1960077,0 -80.5769062,37.1959863,0 -80.5769277,37.1959671,0 -80.57700680000001,37.1959329,0 -80.5770806,37.1959051,0 -80.5771275,37.1958977,0 -80.5771999,37.1958966,0 -80.57721340000001,37.1959105,0 -80.57722010000001,37.1959233,0 -80.5771999,37.1960056,0 -80.5771329,37.196043,0 -80.5770752,37.1960718,0 -80.57700010000001,37.1961188,0 -80.57699479999999,37.1961434,0 -80.5770189,37.1962085,0 @@ -424,19 +424,20 @@ accuracy_v: VAUnknown #m_ylw-pushpin1 1 - -80.57819770820173,37.19732333569079,0 + -80.57819770820173,37.19732333569079,526.3 + absolute flight: dairy circle 1 - serial_number: EXAMPLE_SERIAL1 + serial_number: MNFR5FAKE2 operation_description: Circle around dairy complex -operator_id: EXAMPLE_OPERATOR1 +operator_id: EXAMPLE_OPERATOR2 registration_number: N.123456 aircraft_type: Helicopter -operator_name: UFT Participant 1 +operator_name: Participant 2 timestamp_accuracy: 1.0 speed_accuracy: SA3mps sample_rate: 1.0 @@ -448,7 +449,7 @@ accuracy_v: VAUnknown 1 - -80.5778764450798,37.19720908658443,0 -80.5777768286344,37.19728133750581,0 -80.57773082850338,37.19731267525504,0 -80.57768438827864,37.19734407176483,0 -80.5776495089373,37.19737060757612,0 -80.57758729162289,37.19739859846299,0 -80.57752505433055,37.19742659822762,0 -80.57745907863931,37.19746716669243,0 -80.57732056096148,37.1975290889748,0 -80.57729466482739,37.1975545227504,0 -80.57728045176378,37.19760315697517,0 -80.57726264108165,37.19766134355049,0 -80.57724465122472,37.19771946075003,0 -80.57723565656983,37.19774851703055,0 -80.57722097707993,37.19779699567566,0 -80.57721786827665,37.19786894348874,0 -80.57722482495858,37.19791223607976,0 -80.57725193666792,37.19797104532702,0 -80.57729344338453,37.19803431371983,0 -80.57733130849972,37.19810753628926,0 -80.57735911325359,37.19815665922027,0 + -80.5778764450798,37.19720908658443,0 -80.5777768286344,37.19728133750581,0 -80.57773082850338,37.19731267525504,0 -80.57768438827864,37.19734407176483,0 -80.5776495089373,37.19737060757612,0 -80.57758729162289,37.19739859846299,0 -80.57752505433055,37.19742659822762,0 -80.57745907863931,37.19746716669243,0 -80.57732056096148,37.1975290889748,0 -80.57729466482739,37.1975545227504,0 -80.57728045176378,37.19760315697517,0 -80.57726264108165,37.19766134355049,0 -80.57724465122472,37.19771946075003,0 -80.57723565656983,37.19774851703055,0 -80.57722097707993,37.19779699567566,0 -80.57721786827665,37.19786894348874,0 -80.57722482495858,37.19791223607976,0 -80.57725193666792,37.19797104532702,0 -80.57729344338453,37.19803431371983,0 -80.57733130849972,37.19810753628926,0 -80.57735911325359,37.19815665922027,0 -80.5773793,37.1981824,0 -80.5774222,37.1982038,0 -80.5775094,37.1982198,0 -80.5775993,37.1982273,0 -80.5776958,37.1982347,0 -80.577787,37.1982465,0 -80.5778675,37.1982582,0 -80.57809810000001,37.1982796,0 -80.5783449,37.1983052,0 -80.5785487,37.1983394,0 -80.5787311,37.1983651,0 -80.57897250000001,37.198442,0 -80.5791641,37.1984958,0 -80.5794216,37.1985385,0 -80.5796684,37.1985514,0 -80.5799313,37.1985855,0 -80.5800868,37.1986283,0 -80.5803068,37.1986625,0 -80.58047310000001,37.198671,0 -80.580634,37.1986796,0 -80.5807949,37.1986924,0 -80.5809666,37.1987052,0 -80.5811597,37.1987223,0 -80.581326,37.198718,0 -80.5814869,37.1987266,0 -80.58165320000001,37.1987479,0 -80.5818142,37.1987479,0 -80.5819697,37.1987479,0 -80.5821038,37.1987693,0 -80.582297,37.1988163,0 -80.58252229999999,37.1988847,0 -80.5825974,37.1989103,0 -80.5827422,37.1989402,0 -80.58289240000001,37.1989359,0 -80.5830158,37.1989188,0 -80.5831821,37.1988633,0 -80.5832626,37.1988077,0 -80.5834342,37.1987308,0 -80.5835737,37.1986625,0 -80.58371320000001,37.1985855,0 -80.58385800000001,37.1985428,0 -80.58387949999999,37.1984659,0 -80.5839277,37.1983591,0 -80.58394920000001,37.1982693,0 -80.584035,37.1981326,0 -80.5841638,37.1980429,0 -80.58436399999999,37.1979753,0 -80.5845195,37.1979796,0 -80.5846536,37.197971,0 -80.5849058,37.1979454,0 -80.585115,37.1979197,0 -80.5853403,37.1978599,0 -80.5855763,37.1978044,0 -80.5857372,37.1977488,0 -80.585925,37.1977104,0 -80.58622,37.1976463,0 -80.58639169999999,37.1976164,0 -80.58662769999999,37.1975864,0 -80.5868799,37.1975608,0 -80.587073,37.1975223,0 -80.58726609999999,37.1974625,0 -80.5874056,37.1974412,0 @@ -477,7 +478,7 @@ accuracy_v: VAUnknown - -80.57683884039908,37.19812412967849,655 -80.57612876354007,37.19744757353455,655 -80.57279268045139,37.19884365512596,655 -80.57458740723276,37.20085102219347,655 -80.57683884039908,37.19812412967849,655 + -80.57683900000001,37.198124,655 -80.57612899999999,37.197448,655 -80.572793,37.198844,655 -80.57458699999999,37.200851,655 -80.57683900000001,37.198124,655 @@ -493,7 +494,7 @@ accuracy_v: VAUnknown - -80.57561732912529,37.20189206292986,570 -80.5743256811095,37.2044692971395,570 -80.57733777134109,37.20453751876916,570 -80.57865313153285,37.20209835906732,570 -80.57561732912529,37.20189206292986,570 + -80.58769770000001,37.1983284,570 -80.5847759,37.1986323,570 -80.5811575,37.1972143,570 -80.58734339999999,37.1957996,570 -80.58769770000001,37.1983284,570 @@ -521,7 +522,7 @@ accuracy_v: VAUnknown - -80.57800768901809,37.19833708385374,0 -80.57295402573506,37.19978873922927,0 -80.57194576874815,37.20375460145004,0 -80.57651619071703,37.20461952158183,0 -80.58094687700341,37.20169420910003,0 -80.57800768901809,37.19833708385374,0 + -80.584102,37.1972431,0 -80.5880602,37.1966355,0 -80.5886615,37.1997556,0 -80.5868801,37.1990482,0 -80.5839618,37.1989593,0 -80.584102,37.1972431,0 @@ -541,7 +542,8 @@ accuracy_v: VAUnknown #m_ylw-pushpin 1 - -80.57812675723362,37.19736628374068,0 + -80.57812675723362,37.19736628374068,526.1 + absolute diff --git a/monitoring/uss_qualifier/test_data/usa/netrid/dcdemo.kml b/monitoring/uss_qualifier/test_data/usa/netrid/dcdemo.kml index fb54d2560c..1713b1b98f 100644 --- a/monitoring/uss_qualifier/test_data/usa/netrid/dcdemo.kml +++ b/monitoring/uss_qualifier/test_data/usa/netrid/dcdemo.kml @@ -98,7 +98,7 @@ flight: test_flight_north 1 - serial_number: EXMPLx31bclx2guk + serial_number: 21206Y824YZ operation_description: Traffic Monitoring operator_id: OP-d7p4o3l2 registration_number: N.123456 @@ -115,7 +115,7 @@ accuracy_v: VAUnknown 1 - -77.55585786189934,39.07946259742567,116 -77.55585786189934,39.07946259742567,140 -77.55590933873647,39.07942136290154,140 -77.55640842624625,39.07909259546974,140 -77.55649533040686,39.07907891407359,140 -77.55656511299719,39.07907891839287,140 -77.55665246960693,39.07907899099997,140 -77.55670489443796,39.07907904512823,140 -77.55680975038514,39.07907915754353,140 -77.55689713574773,39.07907925110334,140 -77.55696739403606,39.07909300702381,140 -77.55703851497563,39.07912071630042,140 -77.55707472267461,39.07916199005058,140 -77.55712912144568,39.0792305884167,140 -77.55716620171791,39.07928583292331,140 -77.55720330828555,39.07934111689317,140 -77.55722227978741,39.07939600625936,140 -77.55725765375473,39.0794501116203,140 -77.55727627514482,39.07950482351895,140 -77.55729490448188,39.07955955731004,140 -77.5573137222033,39.07961467471912,140 -77.55733256972054,39.0796695953988,140 -77.55733383683219,39.07972447970517,140 -77.55733483647411,39.07976568977167,140 -77.55733655271884,39.07983444497697,140 -77.55733835028354,39.07990323030949,140 -77.55734066746388,39.07997247920742,140 -77.55734188188015,39.08002742894315,140 -77.5573429671918,39.08006880680381,140 -77.55734477441732,39.08013780308103,140 -77.55734661460282,39.08020683233811,140 -77.55734812343894,39.08026213198261,140 -77.55734925527744,39.08030362198904,140 -77.55735189603955,39.08040049472836,140 -77.55735473821215,39.08049758267686,140 -77.55733867260282,39.0805529976702,140 -77.55732254553646,39.08060840544565,140 -77.55730641159623,39.08066383949771,140 -77.55729063887122,39.08073317882214,140 -77.55727411914556,39.08077478496765,140 -77.55727596510425,39.08084423278386,140 -77.55727744481189,39.08089981792946,140 -77.55726127062646,39.0809553416028,140 -77.55724467575784,39.08099696856388,140 -77.5572108168311,39.08105249752664,140 -77.55721285835257,39.08113590549704,140 -77.55721431069817,39.08119160561851,140 -77.55721605619564,39.08126121254306,140 -77.55721780195692,39.08133085977851,140 -77.55721920016256,39.08138660663652,140 -77.5572209478924,39.08145632573775,140 -77.55723964351446,39.08149819361299,140 -77.55725940226051,39.08158195475191,140 -77.55727882094916,39.08165180407102,140 -77.5573155596023,39.08170773336774,140 -77.5573537218297,39.08181964183621,140 -77.55739126508709,39.08190366249713,140 -77.55741075481436,39.08197370309114,140 -77.55742989236764,39.08202976825081,140 -77.55746673091821,39.08208588214432,140 -77.55750395416608,39.08215605018171,140 -77.55752312921487,39.08221220220258,140 -77.55756039027766,39.08228244492017,140 -77.55761650863666,39.08239491284143,140 -77.55763553674896,39.0824786445446,140 -77.55765220724287,39.08257287805522,140 -77.55770716486758,39.08264328515591,140 -77.55772673427646,39.08271364984513,140 -77.5577637001733,39.08277002027143,140 -77.55781996414343,39.08288284089186,140 -77.55787509592827,39.08295340690584,140 -77.55791213411052,39.08300988936742,140 -77.55796691926469,39.08306640047869,140 -77.55798554733651,39.08309458476205,140 -77.55807731118985,39.08318019431032,140 -77.5581326954467,39.083251175298,140 -77.55820598238988,39.08332220106428,140 -77.55827893286441,39.08337912435288,140 -77.55831542456814,39.08340760827233,140 -77.55840667644598,39.08347884831436,140 -77.5584803097011,39.08352275600644,140 -77.55855590040765,39.08358247810339,140 -77.55864963114301,39.0836288491478,140 -77.5586872996325,39.08365845244096,140 -77.55877561975136,39.0836852110051,140 -77.55881196800674,39.08372724389021,140 -77.55888207276951,39.08376739354381,140 -77.55895378958176,39.08380911273427,140 -77.55900829665589,39.08386540539416,140 -77.55908192268197,39.0839227365851,140 -77.55917268182804,39.08397958871022,140 -77.55922732769311,39.08402208783372,140 -77.55928314352212,39.08406582591044,140 -77.55937618311421,39.08413867385124,140 -77.55948659472193,39.084183516959,140 -77.55957735237219,39.08424021441549,140 -77.55966811011146,39.08429691422753,140 -77.55974117773606,39.08435375675585,140 -77.55977827144832,39.08439652340423,140 -77.55983361495983,39.08445348227293,140 -77.5598893651937,39.0845246470446,140 -77.55992736101369,39.08459592652191,140 -77.55998376756895,39.08468158694628,140 -77.56002194530954,39.08475308344101,140 -77.56004232576443,39.08482467974318,140 -77.56004387014573,39.08486769511959,140 -77.56006530410035,39.08496805910059,140 -77.56008571439023,39.08503977634307,140 -77.56010614006799,39.08511153483582,140 -77.56010821598646,39.08516902168956,140 -77.56014600711254,39.08522644399622,140 -77.56018473452748,39.08528484469436,140 -77.56020479897869,39.0853286325069,140 -77.56029856309361,39.08542977811321,140 -77.56037170191239,39.08547297341944,140 -77.5604446427199,39.08551590589044,140 -77.56053492323277,39.08554426573667,140 -77.56058898017126,39.08555839535935,140 -77.56067930866311,39.08558678686381,140 -77.56076938095575,39.08560105295813,140 -77.56087742645325,39.08560149734588,140 -77.56094943309608,39.08560178104684,140 -77.56102147979149,39.08560208575253,140 -77.5611657999616,39.08560294261057,140 -77.56123745722293,39.08558899506006,140 -77.56132641633209,39.08556059300776,140 -77.56139731872591,39.08553208725606,140 -77.56146761317322,39.08548917173798,140 -77.56151983525353,39.08544617815146,140 -77.56157119201157,39.08538849057368,140 -77.56162086987578,39.08531560725678,140 -77.56163581899976,39.08525755341633,140 -77.56166813286508,39.08518505305642,140 -77.56166503858519,39.08509930865799,140 -77.56166252346775,39.08502799350562,140 -77.56166057971002,39.08497100733103,140 -77.56164118022734,39.08492823170229,140 -77.56160288184191,39.08485695449321,140 -77.56156508122687,39.0847999328137,140 -77.56150888477002,39.08472865318019,140 -77.56145332674336,39.08467163578867,140 -77.5614162046886,39.08462918774698,140 -77.56134201072645,39.08457142962737,140 -77.5612862683198,39.08452822583025,140 -77.56121264180871,39.08448492324413,140 -77.56113902267745,39.08444163314086,140 -77.56106543462494,39.08439836238011,140 -77.56099187827621,39.08435510991445,140 -77.56093736388794,39.08434047557984,140 -77.56086486201541,39.0843257108371,140 -77.56077388493226,39.08429658876273,140 -77.56070126838328,39.08426784861782,140 -77.56061157230064,39.08425362890086,140 -77.56053917898473,39.0842251316778,140 -77.56044918833256,39.08419673128364,140 -77.56037667807952,39.08418199482239,140 -77.56030303785046,39.08413855599943,140 -77.5602670661336,39.08413823125802,140 -77.56013874829463,39.0840798058203,140 -77.56008279586669,39.08402250957319,140 -77.56000931309487,39.08396548957539,140 -77.55997269843466,39.08393704567608,140 -77.55989922834354,39.08386610087945,140 -77.55984444399296,39.08380973545334,140 -77.5598249666531,39.08375317749219,140 -77.5598054415824,39.08369658699363,140 -77.55976775013441,39.08362598083686,140 -77.55976587662194,39.08356930729322,140 -77.55974587377055,39.08349860887009,140 -77.55974353973761,39.0834278374239,140 -77.55974209622167,39.08338521980384,140 -77.55973920018414,39.08331414496942,140 -77.55973698929522,39.08325729971704,140 -77.55975256028826,39.08320041950684,140 -77.55974960303938,39.08312944285297,140 -77.55974747185472,39.08307264035541,140 -77.55977999655281,39.08300160981339,140 -77.55981311761245,39.08294481574898,140 -77.55982866375261,39.08288817558021,140 -77.55986243939915,39.08283200825699,140 -77.55989638450897,39.0827760439398,140 -77.55992973133668,39.08270585574268,140 -77.55998143947886,39.0826499589561,140 -77.56003312771981,39.0825940775678,140 -77.56008523575989,39.0825385728109,140 -77.560137240923,39.08249670597641,140 -77.56018961532554,39.08245514794299,140 -77.56026039126324,39.08241376873489,140 -77.5603132034641,39.08237253364082,140 -77.5603641317674,39.0823163397931,140 -77.56041602136153,39.08228811174298,140 -77.56046719809441,39.08224572793074,140 -77.56053540009121,39.08218924282025,140 -77.56058582248266,39.08213275215235,140 -77.56063621696627,39.08207629253246,140 -77.56067079664976,39.08203486528334,140 -77.56072276534067,39.08197957525235,140 -77.56073718987862,39.08190906828553,140 -77.5607523010957,39.08185280143238,140 -77.56076723054834,39.08179652873594,140 -77.56076317273025,39.08171191913683,140 -77.5607604730257,39.08165571430409,140 -77.56075477982083,39.08154321998742,140 -77.56073508484623,39.0815010934765,140 -77.56069594660217,39.08143063475114,140 -77.5606759456639,39.08138834876387,140 -77.56065473124146,39.08131813617641,140 -77.56059846644743,39.08126174386282,140 -77.56054241310569,39.08117824091163,140 -77.56048595173515,39.08109454048459,140 -77.56044970732688,39.08105301808278,140 -77.56036072875429,39.08099816178541,140 -77.56028898267462,39.08094300604481,140 -77.56025269302516,39.08090159820522,140 -77.56018126773313,39.08086023403786,140 -77.56012688760345,39.08080496361193,140 -77.56007339494828,39.08077731036123,140 -77.56000201866256,39.08072228800954,140 -77.55993083570334,39.08068103641167,140 -77.55984122674056,39.08063947872747,140 -77.55976920723221,39.08059773590796,140 -77.55973340371712,39.08058380688205,140 -77.55964495212531,39.08055631877207,140 -77.55957368548162,39.08052860208605,140 -77.55950227206198,39.08050080511168,140 -77.55941326191105,39.08047298704349,140 -77.55934133147097,39.08044484487881,140 -77.5592512479183,39.08041635607589,140 -77.55916162113301,39.08037468791353,140 -77.55908997174686,39.08034675337966,140 -77.55903606397905,39.08031890092054,140 -77.55894639706845,39.08027710077793,140 -77.55885635961094,39.0802215269054,140 -77.55878486010609,39.08019363805225,140 -77.55869583157109,39.08016565712563,140 -77.55860620576829,39.08012393083275,140 -77.55851673187925,39.08008209523749,140 -77.55848081863438,39.08005462712563,140 -77.55840932733585,39.08001317320947,140 -77.55832051005233,39.07997183898663,140 -77.55824964934833,39.07994430001782,140 -77.55816045436465,39.07988920548522,140 -77.55809046971157,39.07986211487187,140 -77.55800346217904,39.07986244069996,140 -77.557915908279,39.07986242283202,140 -77.5578627252237,39.07984858763653,140 -77.55779187348486,39.07982096702112,140 -77.55775702423696,39.07982107522233,140 -77.55765133100655,39.07975312349387,140 -77.55758093175558,39.07972592061541,140 -77.55749172235711,39.07968415738382,140 -77.55745503600211,39.07962902688739,140 -77.55741812385254,39.07956035978779,140 -77.55738159163862,39.07950535324229,140 -77.55736333286673,39.07947786165042,140 -77.55734382661225,39.07939559638901,140 -77.55734257461468,39.07934083866427,140 -77.55735835865315,39.07925882136395,140 -77.55735641814644,39.07917685864785,140 -77.55735480309689,39.07912191403036,140 -77.55735322093096,39.0790670950101,140 -77.55736897166381,39.07901223896582,140 -77.55738423640987,39.07894370028205,140 -77.55741725876157,39.0788888512955,140 -77.55746718227552,39.07882031279165,140 -77.5575352604582,39.07877911988383,140 -77.55758554070083,39.07872432640138,140 -77.55765355038626,39.07868318197998,140 -77.5576877607415,39.07866944438204,140 -77.55779077665522,39.0786283572121,140 -77.55786055284712,39.07861517715462,140 -77.55792995491637,39.07860187178759,140 -77.55805226487809,39.07858827820837,140 -77.55810497648613,39.07858847164891,140 -77.55817526650783,39.07858872888573,140 -77.55826319918164,39.07858905752381,140 -77.5583339436526,39.07860294834666,140 -77.55844013213387,39.07863055688289,140 -77.55847564346172,39.07864428587826,140 -77.55858503884336,39.07865967119359,140 -77.55867715113425,39.07868857200889,140 -77.55875061951728,39.07870350434257,140 -77.55884012989664,39.07874484847135,140 -77.55889325519334,39.0787721828983,140 -77.55892928038922,39.07881293731464,140 -77.55900020876688,39.07886739416496,140 -77.55905422936966,39.07893553139818,140 -77.5590902485926,39.07899003887435,140 -77.55910915972693,39.07904458392912,140 -77.55914529855345,39.07908551307862,140 -77.55916460216093,39.07915387982141,140 -77.55918399628192,39.07922225895476,140 -77.55922080520516,39.07929063800555,140 -77.55924057092045,39.07935925026071,140 -77.55924269066681,39.07942781654285,140 -77.55924438669902,39.07948269691953,140 -77.5592459775485,39.07953757938867,140 -77.55924802105659,39.07961968395509,140 -77.55924938184411,39.07967444342445,140 -77.55925097388676,39.07974299795624,140 -77.55925279605749,39.07979800146607,140 -77.55923718513374,39.07986654469039,140 -77.55922095424161,39.07992147572719,140 -77.55920459114428,39.0799762793968,140 -77.55918856641676,39.08004483359657,140 -77.55915466421862,39.08011307587553,140 -77.55913740889739,39.08015407428465,140 -77.5590684508919,39.08022245594682,140 -77.55901711140832,39.0802774140069,140 -77.55898301749315,39.08031866390025,140 -77.55891396561744,39.08037357335683,140 -77.55886260489137,39.08042857458744,140 -77.55881119751159,39.08048359809021,140 -77.55875936534132,39.08052481542724,140 -77.55867222537702,39.08055237210348,140 -77.55862065576071,39.08058017270858,140 -77.55855156719574,39.08060807033712,140 -77.55848201490333,39.08062210879907,140 -77.55839494683306,39.08063619223045,140 -77.55835994032067,39.08063627886138,140 -77.5582198991479,39.08063662344389,140 -77.55813194020405,39.08062296859411,140 -77.55809692637652,39.08062305477951,140 -77.55802647833771,39.08060935676878,140 -77.5579557881193,39.08059547334758,140 -77.55783134904974,39.08056726507211,140 -77.55772498576594,39.08055297540908,140 -77.5576359640586,39.08052493682712,140 -77.557564957046,39.08049725400822,140 -77.55749404064103,39.0804696088199,140 -77.55740520615514,39.08042811022472,140 -77.55731648698324,39.08038670228583,140 -77.5572455027922,39.08034543945272,140 -77.55719193972264,39.08030406712225,140 -77.55713807255484,39.08024891505815,140 -77.55708454354958,39.0802075729623,140 -77.55706575525222,39.08015244308002,140 -77.55699442875769,39.08009736274565,140 -77.55694070507045,39.08004233193655,140 -77.55686972186335,39.08000100298911,140 -77.55678181910936,39.07998729730551,140 -77.55671152766837,39.07997358808212,140 -77.55664097154508,39.07994614015727,140 -77.55653581852292,39.07993249718617,140 -77.55643092364075,39.07993273967235,140 -77.55636160042977,39.07993331041763,140 -77.55627312670983,39.07994622932748,140 -77.55620314178556,39.07997338305591,140 -77.55613391556325,39.08001471808009,140 -77.55608257755173,39.08006994809807,140 -77.55603080239898,39.08009770681244,140 -77.55597816593995,39.08008411898714,140 -77.55594240403474,39.08004285441737,140 -77.55587119640941,39.07997415469461,140 -77.55581706164605,39.07990508349891,140 -77.55576264773313,39.07984915806806,140 -77.55570812997732,39.07979326785962,140 -77.55567197846563,39.0797516791188,140 -77.55561823904596,39.07969655804513,140 -77.55556495795393,39.07965529515668,140 -77.55551169595554,39.07961404756066,140 -77.55544098361351,39.07957282487155,140 -77.55542296778123,39.07954533395031,140 -77.55535202574472,39.07949039136031,140 -77.55526314520101,39.0794081068342,140 -77.55520970988815,39.07933969056022,140 -77.55515711825667,39.07931238082799,140 -77.55512153582755,39.07927135999144,140 -77.55508566721117,39.07921671018958,140 -77.55506739019614,39.07916189348969,140 -77.55504908590632,39.07910726779205,140 -77.55501331141488,39.07903910108504,140 -77.55499513325319,39.07898455913882,140 -77.55497636414965,39.07890262110197,140 -77.5549753710546,39.07884791624775,140 -77.55497423556172,39.07877966043797,140 -77.55497351087199,39.07873868898729,140 -77.55499001663105,39.07868393867014,140 -77.55504114430883,39.07862925278323,140 -77.55509218178041,39.07857463503377,140 -77.55516094326453,39.07853357821991,140 -77.55522987547319,39.07850620597493,140 -77.55529833324672,39.07849207614552,140 -77.55538400650438,39.07846441785459,140 -77.5554532440412,39.07845076121154,140 -77.555505072961,39.07843710397411,140 -77.55557454700991,39.078437084209,140 -77.55567865624293,39.07842339892846,140 -77.55574826419608,39.07842341652577,140 -77.55581811394238,39.07843706372398,140 -77.55588778221633,39.07845055926995,140 -77.55594049418291,39.07847774372338,140 -77.556011114344,39.07851866458438,140 -77.55606433536458,39.07855958787441,140 -77.55613565523703,39.07862781551239,140 -77.55617149498475,39.07866876873879,140 -77.55619023242566,39.07872336892229,140 -77.55620898182842,39.07877799802631,140 -77.55621061309995,39.07884630384395,140 -77.5561941360925,39.07888728389719,140 -77.55617828769113,39.07895563259541,140 -77.55614464652145,39.07901032410555,140 -77.55611099069519,39.07906504094581,140 -77.55605984600591,39.07911976791629,140 -77.55600836698888,39.07916081716631,140 -77.5559568705214,39.07920188171131,140 -77.55590538953457,39.07924295938549,140 -77.555853952777,39.07928412461253,140 -77.55580249910018,39.07932532073117,140 -77.55575103141086,39.07936652575026,140 -77.55569954463385,39.07940774612624,140 -77.55569954463385,39.07940774612624,116 + -77.55585786189934,39.07946259742567,116 -77.55585786189934,39.07946259742567,140 -77.55590933873647,39.07942136290154,140 -77.55640842624625,39.07909259546974,140 -77.55649533040686,39.07907891407359,140 -77.55656511299719,39.07907891839287,140 -77.55665246960693,39.07907899099997,140 -77.55670489443796,39.07907904512823,140 -77.55680975038514,39.07907915754353,140 -77.55689713574773,39.07907925110334,140 -77.55696739403606,39.07909300702381,140 -77.55703851497563,39.07912071630042,140 -77.55707472267461,39.07916199005058,140 -77.55712912144568,39.0792305884167,140 -77.55716620171791,39.07928583292331,140 -77.55720330828555,39.07934111689317,140 -77.55722227978741,39.07939600625936,140 -77.55725765375473,39.0794501116203,140 -77.55727627514482,39.07950482351895,140 -77.55729490448188,39.07955955731004,140 -77.5573137222033,39.07961467471912,140 -77.55733256972054,39.0796695953988,140 -77.55733383683219,39.07972447970517,140 -77.55733483647411,39.07976568977167,140 -77.55733655271884,39.07983444497697,140 -77.55733835028354,39.07990323030949,140 -77.55734066746388,39.07997247920742,140 -77.55734188188015,39.08002742894315,140 -77.5573429671918,39.08006880680381,140 -77.55734477441732,39.08013780308103,140 -77.55734661460282,39.08020683233811,140 -77.55734812343894,39.08026213198261,140 -77.55734925527744,39.08030362198904,140 -77.55735189603955,39.08040049472836,140 -77.55735473821215,39.08049758267686,140 -77.55733867260282,39.0805529976702,140 -77.55732254553646,39.08060840544565,140 -77.55730641159623,39.08066383949771,140 -77.55729063887122,39.08073317882214,140 -77.55727411914556,39.08077478496765,140 -77.55727596510425,39.08084423278386,140 -77.55727744481189,39.08089981792946,140 -77.55726127062646,39.0809553416028,140 -77.55724467575784,39.08099696856388,140 -77.5572108168311,39.08105249752664,140 -77.55721285835257,39.08113590549704,140 -77.55721431069817,39.08119160561851,140 -77.55721605619564,39.08126121254306,140 -77.55721780195692,39.08133085977851,140 -77.55721920016256,39.08138660663652,140 -77.5572209478924,39.08145632573775,140 -77.55723964351446,39.08149819361299,140 -77.55725940226051,39.08158195475191,140 -77.55727882094916,39.08165180407102,140 -77.5573155596023,39.08170773336774,140 -77.5573537218297,39.08181964183621,140 -77.55739126508709,39.08190366249713,140 -77.55741075481436,39.08197370309114,140 -77.55742989236764,39.08202976825081,140 -77.55746673091821,39.08208588214432,140 -77.55750395416608,39.08215605018171,140 -77.55752312921487,39.08221220220258,140 -77.55756039027766,39.08228244492017,140 -77.55761650863666,39.08239491284143,140 -77.55763553674896,39.0824786445446,140 -77.55765220724287,39.08257287805522,140 -77.55770716486758,39.08264328515591,140 -77.55772673427646,39.08271364984513,140 -77.5577637001733,39.08277002027143,140 -77.55781996414343,39.08288284089186,140 -77.55787509592827,39.08295340690584,140 -77.55791213411052,39.08300988936742,140 -77.55796691926469,39.08306640047869,140 -77.55798554733651,39.08309458476205,140 -77.55807731118985,39.08318019431032,140 -77.5581326954467,39.083251175298,140 -77.55820598238988,39.08332220106428,140 -77.55827893286441,39.08337912435288,140 -77.55831542456814,39.08340760827233,140 -77.55840667644598,39.08347884831436,140 -77.5584803097011,39.08352275600644,140 -77.55855590040765,39.08358247810339,140 -77.55864963114301,39.0836288491478,140 -77.5586872996325,39.08365845244096,140 -77.55877561975136,39.0836852110051,140 -77.55881196800674,39.08372724389021,140 -77.55888207276951,39.08376739354381,140 -77.55895378958176,39.08380911273427,140 -77.55900829665589,39.08386540539416,140 -77.55908192268197,39.0839227365851,140 -77.55917268182804,39.08397958871022,140 -77.55922732769311,39.08402208783372,140 -77.55928314352212,39.08406582591044,140 -77.55937618311421,39.08413867385124,140 -77.55948659472193,39.084183516959,140 -77.55957735237219,39.08424021441549,140 -77.55966811011146,39.08429691422753,140 -77.55974117773606,39.08435375675585,140 -77.55977827144832,39.08439652340423,140 -77.55983361495983,39.08445348227293,140 -77.5598893651937,39.0845246470446,140 -77.55992736101369,39.08459592652191,140 -77.55998376756895,39.08468158694628,140 -77.56002194530954,39.08475308344101,140 -77.56004232576443,39.08482467974318,140 -77.56004387014573,39.08486769511959,140 -77.56006530410035,39.08496805910059,140 -77.56008571439023,39.08503977634307,140 -77.56010614006799,39.08511153483582,140 -77.56010821598646,39.08516902168956,140 -77.56014600711254,39.08522644399622,140 -77.56018473452748,39.08528484469436,140 -77.56020479897869,39.0853286325069,140 -77.56029856309361,39.08542977811321,140 -77.56037170191239,39.08547297341944,140 -77.5604446427199,39.08551590589044,140 -77.56053492323277,39.08554426573667,140 -77.56058898017126,39.08555839535935,140 -77.56067930866311,39.08558678686381,140 -77.56076938095575,39.08560105295813,140 -77.56087742645325,39.08560149734588,140 -77.56094943309608,39.08560178104684,140 -77.56102147979149,39.08560208575253,140 -77.5611657999616,39.08560294261057,140 -77.56123745722293,39.08558899506006,140 -77.56132641633209,39.08556059300776,140 -77.56139731872591,39.08553208725606,140 -77.56146761317322,39.08548917173798,140 -77.56151983525353,39.08544617815146,140 -77.56157119201157,39.08538849057368,140 -77.56162086987578,39.08531560725678,140 -77.56163581899976,39.08525755341633,140 -77.56166813286508,39.08518505305642,140 -77.56166503858519,39.08509930865799,140 -77.56166252346775,39.08502799350562,140 -77.56166057971002,39.08497100733103,140 -77.56164118022734,39.08492823170229,140 -77.56160288184191,39.08485695449321,140 -77.56156508122687,39.0847999328137,140 -77.56150888477002,39.08472865318019,140 -77.56145332674336,39.08467163578867,140 -77.5614162046886,39.08462918774698,140 -77.56134201072645,39.08457142962737,140 -77.5612862683198,39.08452822583025,140 -77.56121264180871,39.08448492324413,140 -77.56113902267745,39.08444163314086,140 -77.56106543462494,39.08439836238011,140 -77.56099187827621,39.08435510991445,140 -77.56093736388794,39.08434047557984,140 -77.56086486201541,39.0843257108371,140 -77.56077388493226,39.08429658876273,140 -77.56070126838328,39.08426784861782,140 -77.56061157230064,39.08425362890086,140 -77.56053917898473,39.0842251316778,140 -77.56044918833256,39.08419673128364,140 -77.56037667807952,39.08418199482239,140 -77.56030303785046,39.08413855599943,140 -77.5602670661336,39.08413823125802,140 -77.56013874829463,39.0840798058203,140 -77.56008279586669,39.08402250957319,140 -77.56000931309487,39.08396548957539,140 -77.55997269843466,39.08393704567608,140 -77.55989922834354,39.08386610087945,140 -77.55984444399296,39.08380973545334,140 -77.5598249666531,39.08375317749219,140 -77.5598054415824,39.08369658699363,140 -77.55976775013441,39.08362598083686,140 -77.55976587662194,39.08356930729322,140 -77.55974587377055,39.08349860887009,140 -77.55974353973761,39.0834278374239,140 -77.55974209622167,39.08338521980384,140 -77.55973920018414,39.08331414496942,140 -77.55973698929522,39.08325729971704,140 -77.55975256028826,39.08320041950684,140 -77.55974960303938,39.08312944285297,140 -77.55974747185472,39.08307264035541,140 -77.55977999655281,39.08300160981339,140 -77.55981311761245,39.08294481574898,140 -77.55982866375261,39.08288817558021,140 -77.55986243939915,39.08283200825699,140 -77.55989638450897,39.0827760439398,140 -77.55992973133668,39.08270585574268,140 -77.55998143947886,39.0826499589561,140 -77.56003312771981,39.0825940775678,140 -77.56008523575989,39.0825385728109,140 -77.560137240923,39.08249670597641,140 -77.56018961532554,39.08245514794299,140 -77.56026039126324,39.08241376873489,140 -77.5603132034641,39.08237253364082,140 -77.5603641317674,39.0823163397931,140 -77.56041602136153,39.08228811174298,140 -77.56046719809441,39.08224572793074,140 -77.56053540009121,39.08218924282025,140 -77.56058582248266,39.08213275215235,140 -77.56063621696627,39.08207629253246,140 -77.56067079664976,39.08203486528334,140 -77.56072276534067,39.08197957525235,140 -77.56073718987862,39.08190906828553,140 -77.5607523010957,39.08185280143238,140 -77.56076723054834,39.08179652873594,140 -77.56076317273025,39.08171191913683,140 -77.5607604730257,39.08165571430409,140 -77.56075477982083,39.08154321998742,140 -77.56073508484623,39.0815010934765,140 -77.56069594660217,39.08143063475114,140 -77.5606759456639,39.08138834876387,140 -77.56065473124146,39.08131813617641,140 -77.56059846644743,39.08126174386282,140 -77.56054241310569,39.08117824091163,140 -77.56048595173515,39.08109454048459,140 -77.56044970732688,39.08105301808278,140 -77.56036072875429,39.08099816178541,140 -77.56028898267462,39.08094300604481,140 -77.56025269302516,39.08090159820522,140 -77.56018126773313,39.08086023403786,140 -77.56012688760345,39.08080496361193,140 -77.56007339494828,39.08077731036123,140 -77.56000201866256,39.08072228800954,140 -77.55993083570334,39.08068103641167,140 -77.55984122674056,39.08063947872747,140 -77.55976920723221,39.08059773590796,140 -77.55973340371712,39.08058380688205,140 -77.55964495212531,39.08055631877207,140 -77.55957368548162,39.08052860208605,140 -77.55950227206198,39.08050080511168,140 -77.55941326191105,39.08047298704349,140 -77.55934133147097,39.08044484487881,140 -77.5592512479183,39.08041635607589,140 -77.55916162113301,39.08037468791353,140 -77.55908997174686,39.08034675337966,140 -77.55903606397905,39.08031890092054,140 -77.55894639706845,39.08027710077793,140 -77.55885635961094,39.0802215269054,140 -77.55878486010609,39.08019363805225,140 -77.55869583157109,39.08016565712563,140 -77.55860620576829,39.08012393083275,140 -77.55851673187925,39.08008209523749,140 -77.55848081863438,39.08005462712563,140 -77.55840932733585,39.08001317320947,140 -77.55832051005233,39.07997183898663,140 -77.55824964934833,39.07994430001782,140 -77.55816045436465,39.07988920548522,140 -77.55809046971157,39.07986211487187,140 -77.55800346217904,39.07986244069996,140 -77.557915908279,39.07986242283202,140 -77.5578627252237,39.07984858763653,140 -77.55779187348486,39.07982096702112,140 -77.55775702423696,39.07982107522233,140 -77.55765133100655,39.07975312349387,140 -77.55758093175558,39.07972592061541,140 -77.55749172235711,39.07968415738382,140 -77.55745503600211,39.07962902688739,140 -77.55741812385254,39.07956035978779,140 -77.55738159163862,39.07950535324229,140 -77.55736333286673,39.07947786165042,140 -77.55734382661225,39.07939559638901,140 -77.55734257461468,39.07934083866427,140 -77.55735835865315,39.07925882136395,140 -77.55735641814644,39.07917685864785,140 -77.55735480309689,39.07912191403036,140 -77.55735322093096,39.0790670950101,140 -77.55736897166381,39.07901223896582,140 -77.55738423640987,39.07894370028205,140 -77.55741725876157,39.0788888512955,140 -77.55746718227552,39.07882031279165,140 -77.5575352604582,39.07877911988383,140 -77.55758554070083,39.07872432640138,140 -77.55765355038626,39.07868318197998,140 -77.5576877607415,39.07866944438204,140 -77.55779077665522,39.0786283572121,140 -77.55786055284712,39.07861517715462,140 -77.55792995491637,39.07860187178759,140 -77.55805226487809,39.07858827820837,140 -77.55810497648613,39.07858847164891,140 -77.55817526650783,39.07858872888573,140 -77.55826319918164,39.07858905752381,140 -77.5583339436526,39.07860294834666,140 -77.55844013213387,39.07863055688289,140 -77.55847564346172,39.07864428587826,140 -77.55858503884336,39.07865967119359,140 -77.55867715113425,39.07868857200889,140 -77.55875061951728,39.07870350434257,140 -77.55884012989664,39.07874484847135,140 -77.55889325519334,39.0787721828983,140 -77.55892928038922,39.07881293731464,140 -77.55900020876688,39.07886739416496,140 -77.55905422936966,39.07893553139818,140 -77.5590902485926,39.07899003887435,140 -77.55910915972693,39.07904458392912,140 -77.55914529855345,39.07908551307862,140 -77.55916460216093,39.07915387982141,140 -77.55918399628192,39.07922225895476,140 -77.55922080520516,39.07929063800555,140 -77.55924057092045,39.07935925026071,140 -77.55924269066681,39.07942781654285,140 -77.55924438669902,39.07948269691953,140 -77.5592459775485,39.07953757938867,140 -77.55924802105659,39.07961968395509,140 -77.55924938184411,39.07967444342445,140 -77.55925097388676,39.07974299795624,140 -77.55925279605749,39.07979800146607,140 -77.55923718513374,39.07986654469039,140 -77.55922095424161,39.07992147572719,140 -77.55920459114428,39.0799762793968,140 -77.55918856641676,39.08004483359657,140 -77.55915466421862,39.08011307587553,140 -77.55913740889739,39.08015407428465,140 -77.5590684508919,39.08022245594682,140 -77.55901711140832,39.0802774140069,140 -77.55898301749315,39.08031866390025,140 -77.55891396561744,39.08037357335683,140 -77.55886260489137,39.08042857458744,140 -77.55881119751159,39.08048359809021,140 -77.55875936534132,39.08052481542724,140 -77.55867222537702,39.08055237210348,140 -77.55862065576071,39.08058017270858,140 -77.55855156719574,39.08060807033712,140 -77.55848201490333,39.08062210879907,140 -77.55839494683306,39.08063619223045,140 -77.55835994032067,39.08063627886138,140 -77.5582198991479,39.08063662344389,140 -77.55813194020405,39.08062296859411,140 -77.55809692637652,39.08062305477951,140 -77.55802647833771,39.08060935676878,140 -77.5579557881193,39.08059547334758,140 -77.55783134904974,39.08056726507211,140 -77.55772498576594,39.08055297540908,140 -77.5576359640586,39.08052493682712,140 -77.557564957046,39.08049725400822,140 -77.55749404064103,39.0804696088199,140 -77.55740520615514,39.08042811022472,140 -77.55731648698324,39.08038670228583,140 -77.5572455027922,39.08034543945272,140 -77.55719193972264,39.08030406712225,140 -77.55713807255484,39.08024891505815,140 -77.55708454354958,39.0802075729623,140 -77.55706575525222,39.08015244308002,140 -77.55699442875769,39.08009736274565,140 -77.55694070507045,39.08004233193655,140 -77.55686972186335,39.08000100298911,140 -77.55678181910936,39.07998729730551,140 -77.55671152766837,39.07997358808212,140 -77.55664097154508,39.07994614015727,140 -77.55653581852292,39.07993249718617,140 -77.55643092364075,39.07993273967235,140 -77.55636160042977,39.07993331041763,140 -77.55627312670983,39.07994622932748,140 -77.55620314178556,39.07997338305591,140 -77.55613391556325,39.08001471808009,140 -77.55608257755173,39.08006994809807,140 -77.55603080239898,39.08009770681244,140 -77.55597816593995,39.08008411898714,140 -77.55594240403474,39.08004285441737,140 -77.55587119640941,39.07997415469461,140 -77.55581706164605,39.07990508349891,140 -77.55576264773313,39.07984915806806,140 -77.55570812997732,39.07979326785962,140 -77.55567197846563,39.0797516791188,140 -77.55561823904596,39.07969655804513,140 -77.55556495795393,39.07965529515668,140 -77.55551169595554,39.07961404756066,140 -77.55544098361351,39.07957282487155,140 -77.55542296778123,39.07954533395031,140 -77.55535202574472,39.07949039136031,140 -77.55526314520101,39.0794081068342,140 -77.55520970988815,39.07933969056022,140 -77.55515711825667,39.07931238082799,140 -77.55512153582755,39.07927135999144,140 -77.55508566721117,39.07921671018958,140 -77.55506739019614,39.07916189348969,140 -77.55504908590632,39.07910726779205,140 -77.55501331141488,39.07903910108504,140 -77.55499513325319,39.07898455913882,140 -77.55497636414965,39.07890262110197,140 -77.5549753710546,39.07884791624775,140 -77.55497423556172,39.07877966043797,140 -77.55497351087199,39.07873868898729,140 -77.55499001663105,39.07868393867014,140 -77.55504114430883,39.07862925278323,140 -77.55509218178041,39.07857463503377,140 -77.55516094326453,39.07853357821991,140 -77.55522987547319,39.07850620597493,140 -77.55529833324672,39.07849207614552,140 -77.55538400650438,39.07846441785459,140 -77.5554532440412,39.07845076121154,140 -77.555505072961,39.07843710397411,140 -77.55557454700991,39.078437084209,140 -77.55567865624293,39.07842339892846,140 -77.55574826419608,39.07842341652577,140 -77.55581811394238,39.07843706372398,140 -77.55588778221633,39.07845055926995,140 -77.55594049418291,39.07847774372338,140 -77.556011114344,39.07851866458438,140 -77.55606433536458,39.07855958787441,140 -77.55613565523703,39.07862781551239,140 -77.55617149498475,39.07866876873879,140 -77.55619023242566,39.07872336892229,140 -77.55620898182842,39.07877799802631,140 -77.55621061309995,39.07884630384395,140 -77.5561941360925,39.07888728389719,140 -77.55617828769113,39.07895563259541,140 -77.55614464652145,39.07901032410555,140 -77.55611099069519,39.07906504094581,140 -77.55605984600591,39.07911976791629,140 -77.55600836698888,39.07916081716631,140 -77.5559568705214,39.07920188171131,140 -77.55590538953457,39.07924295938549,140 -77.555853952777,39.07928412461253,140 -77.55580249910018,39.07932532073117,140 -77.55575103141086,39.07936652575026,140 -77.55569954463385,39.07940774612624,140 -77.55569954463385,39.07940774612624,116 @@ -133,7 +133,8 @@ accuracy_v: VAUnknown #m_ylw-pushpin 1 - -77.55542883349777,39.07891533092457,0 + -77.55542883349777,39.07891533092457,115.5 + absolute @@ -145,7 +146,7 @@ accuracy_v: VAUnknown - -77.5558283715159,39.07973840541715,116 -77.5561187315021,39.07959656186298,116 -77.55593905968595,39.07946189816531,116 -77.5556215152629,39.07934308891033,116 -77.55543157529834,39.07929677790639,116 -77.55553650406897,39.07955968468792,116 -77.5558283715159,39.07973840541715,116 + -77.5558283715159,39.07973840541715,116 -77.5561187315021,39.07959656186298,116 -77.55593905968595,39.07946189816531,116 -77.5556215152629,39.07934308891033,116 -77.55543157529834,39.07929677790639,116 -77.55553650406897,39.07955968468792,116 -77.5558283715159,39.07973840541715,116 @@ -160,7 +161,7 @@ accuracy_v: VAUnknown - -77.56049679789065,39.08327806207254,140 -77.56134156187262,39.0820706502541,140 -77.56046000142101,39.08067015912076,140 -77.55929287311066,39.08015776011828,140 -77.55901989214918,39.08052017376787,140 -77.5596809524795,39.08260149311663,140 -77.56049679789065,39.08327806207254,140 + -77.56049679789065,39.08327806207254,140 -77.56134156187262,39.0820706502541,140 -77.56046000142101,39.08067015912076,140 -77.55929287311066,39.08015776011828,140 -77.55901989214918,39.08052017376787,140 -77.5596809524795,39.08260149311663,140 -77.56049679789065,39.08327806207254,140 @@ -174,7 +175,7 @@ accuracy_v: VAUnknown - -77.55687487902752,39.07992418891836,0 -77.5575320640223,39.08001226389369,0 -77.55709856858586,39.07897003133505,0 -77.5556603366207,39.07813285204369,0 -77.55425227890363,39.07869620337616,0 -77.55581663372355,39.08045024366912,0 -77.55687487902752,39.07992418891836,0 + -77.55687487902752,39.07992418891836,0 -77.5575320640223,39.08001226389369,0 -77.55709856858586,39.07897003133505,0 -77.5556603366207,39.07813285204369,0 -77.55425227890363,39.07869620337616,0 -77.55581663372355,39.08045024366912,0 -77.55687487902752,39.07992418891836,0 @@ -189,7 +190,7 @@ accuracy_v: VAUnknown - -77.55668646536013,39.07887408917422,125 -77.55589219976699,39.07813072531818,125 -77.55469006975717,39.07862009333341,125 -77.55461007172796,39.0791809620897,125 -77.5556952318509,39.08049974077765,125 -77.5570039355796,39.08060907752414,125 -77.55719671613937,39.08012057463863,125 -77.55666155209093,39.07971088631494,125 -77.55591182856203,39.07987266197577,125 -77.55549683599698,39.07955505501661,125 -77.55528262004611,39.0792612188007,125 -77.55606787807815,39.07889315703223,125 -77.55647125560553,39.07947958980667,125 -77.55670458678823,39.0795477694432,125 -77.55668646536013,39.07887408917422,125 + -77.55668646536013,39.07887408917422,125 -77.55589219976699,39.07813072531818,125 -77.55469006975717,39.07862009333341,125 -77.55461007172796,39.0791809620897,125 -77.5556952318509,39.08049974077765,125 -77.5570039355796,39.08060907752414,125 -77.55719671613937,39.08012057463863,125 -77.55666155209093,39.07971088631494,125 -77.55591182856203,39.07987266197577,125 -77.55549683599698,39.07955505501661,125 -77.55528262004611,39.0792612188007,125 -77.55606787807815,39.07889315703223,125 -77.55647125560553,39.07947958980667,125 -77.55670458678823,39.0795477694432,125 -77.55668646536013,39.07887408917422,125 @@ -203,7 +204,7 @@ accuracy_v: VAUnknown - -77.55639036688112,39.08132039918532,0 -77.55868654177171,39.08628231055433,0 -77.56223973860726,39.08670418368468,0 -77.5624522606835,39.08118346393734,0 -77.55858970655063,39.07750669834476,0 -77.55707749600087,39.07884318934741,0 -77.55745644221814,39.0797935861836,0 -77.55917169147705,39.08052179337327,0 -77.55639036688112,39.08132039918532,0 + -77.55639036688112,39.08132039918532,0 -77.55868654177171,39.08628231055433,0 -77.56223973860726,39.08670418368468,0 -77.5624522606835,39.08118346393734,0 -77.55858970655063,39.07750669834476,0 -77.55707749600087,39.07884318934741,0 -77.55745644221814,39.0797935861836,0 -77.55917169147705,39.08052179337327,0 -77.55639036688112,39.08132039918532,0 @@ -214,7 +215,7 @@ accuracy_v: VAUnknown flight: test_flight_tights 0 1 - serial_number: EXMPLjNb2D9Eya9Q + serial_number: 00E81K operation_description: Tight S's operator_id: OP-PBuCCDRp registration_number: FA.KPJN7GFS2DZ @@ -232,7 +233,7 @@ accuracy_v: VAUnknown 1 - -77.55582203783959,39.07942140173426,116 -77.55582203783959,39.07942140173426,130 -77.55580072354547,39.07924302421763,130 -77.55579924621459,39.0791744904452,130 -77.5557977265713,39.07910610377936,130 -77.55579679210926,39.07905139649237,130 -77.55583042707002,39.07899663875445,130 -77.55586398846832,39.07894184749336,130 -77.55591551814317,39.07891439467294,130 -77.55600182133786,39.07887342850794,130 -77.55607099833354,39.07884616607952,130 -77.55614015990335,39.07881890742463,130 -77.55620963448429,39.07880531481843,130 -77.55627943855004,39.07880537863051,130 -77.55634889643446,39.07879178074756,130 -77.55645354654631,39.07879187115488,130 -77.55652323934824,39.07879187597376,130 -77.55659225394994,39.07875119894857,130 -77.55664370803291,39.07871041664556,130 -77.55669491930364,39.07866940157159,130 -77.55672848399598,39.07861501731466,130 -77.55677949505511,39.07856047018621,130 -77.55677781710288,39.07849239787434,130 -77.55677614117204,39.07842436161125,130 -77.55677486952965,39.07836994482099,130 -77.55673864600003,39.07831555743201,130 -77.55672004786963,39.07823443608537,130 -77.55668366756755,39.07817999460666,130 -77.55664681471022,39.07809852271344,130 -77.55664538537869,39.07803086352594,130 -77.55662578198013,39.07796279929769,130 -77.55662469280134,39.07790857603493,130 -77.55664099864262,39.07782782334399,130 -77.55667542100386,39.07777408362207,130 -77.55669112644971,39.07771984850919,130 -77.55674170602174,39.07767913070661,130 -77.55679204376315,39.07762493921624,130 -77.55687706097058,39.07758417851471,130 -77.55694564994269,39.07757054597487,130 -77.55703108184174,39.07754338913067,130 -77.55710007122214,39.07754328847737,130 -77.55716870163356,39.07752969897152,130 -77.55720320840673,39.07752965593884,130 -77.55730691701689,39.07751631623401,130 -77.5573937138753,39.07750283316335,130 -77.55742848644481,39.07750286984638,130 -77.55751594197551,39.07751654564433,130 -77.55767372946494,39.07754375019385,130 -77.55774423320338,39.07757091400959,130 -77.55781486771602,39.07759807087082,130 -77.55788572517943,39.07763874549509,130 -77.55793884912332,39.07766585659583,130 -77.55802809577064,39.07772019001918,130 -77.55808226056374,39.07777442952948,130 -77.55811884386149,39.07782861484684,130 -77.55812078173669,39.077896293301,130 -77.55808686889482,39.07793680931807,130 -77.55807087342482,39.07799093963674,130 -77.55803721096733,39.07804498780556,130 -77.55800354259023,39.07809906823454,130 -77.55797009614682,39.078153282109,130 -77.55791869956821,39.07819380325763,130 -77.55786782036573,39.07824800111277,130 -77.55779863242363,39.07828842256596,130 -77.5576937672823,39.07828831754295,130 -77.5575364076828,39.07826102975056,130 -77.55743176013115,39.07823405418915,130 -77.55734552227457,39.07823432323085,130 -77.55731048078832,39.07822077872476,130 -77.55722326333006,39.07819373972681,130 -77.55717093968985,39.07818028440151,130 -77.5570663396186,39.07815329785669,130 -77.55699634852385,39.07812624540112,130 -77.55690877774703,39.07808565592063,130 -77.55683850924393,39.07804506400016,130 -77.55676863116969,39.07801806050121,130 -77.55666239340452,39.07799040624271,130 -77.55659085792247,39.07797611259199,130 -77.55650376367207,39.07796257210111,130 -77.55641668106634,39.07794903267279,130 -77.55632988869745,39.07794902577727,130 -77.55624296842763,39.07794897060581,130 -77.55615605817408,39.07796232521968,130 -77.55610403499058,39.07797575441136,130 -77.55601742650943,39.07800265528741,130 -77.55596598293532,39.07804319250445,130 -77.55589805437972,39.07812438346556,130 -77.5558644450167,39.07817864843481,130 -77.55584820376573,39.07823294492859,130 -77.5558496262399,39.07830085596753,130 -77.55585048041503,39.07834162239234,130 -77.5558521934653,39.07840978433945,130 -77.55588869373149,39.07849140909451,130 -77.55590725944286,39.07854586722578,130 -77.55596132527324,39.07862765347127,130 -77.55601550465619,39.07870955404155,130 -77.55606909162509,39.07876420747042,130 -77.55610526154034,39.07881887269235,130 -77.55617603276792,39.07885993529022,130 -77.55622936600429,39.07890098501589,130 -77.55628271376425,39.07894205041889,130 -77.55638838664451,39.07898314528819,130 -77.5564587952465,39.07901050156125,130 -77.55652888978921,39.07902418480753,130 -77.55661616097818,39.07902418670573,130 -77.55672103827135,39.07902435883155,130 -77.55679092565971,39.07902443352061,130 -77.55687794743812,39.07901085060322,130 -77.55694714584897,39.07898358177135,130 -77.55699941116369,39.07895658782059,130 -77.55706934563287,39.07892974145965,130 -77.55712091042636,39.07887545573669,130 -77.55717267453019,39.07883474350491,130 -77.5572059269038,39.07878005425712,130 -77.55722124265036,39.07871166870606,130 -77.55721922665141,39.07864337885884,130 -77.55718306326416,39.07858865651152,130 -77.55714681542345,39.07853445638826,130 -77.55709370315266,39.07849369408456,130 -77.55704024511884,39.07843939237169,130 -77.55698674779551,39.07838502935675,130 -77.55689814925594,39.07833032648725,130 -77.5568625927756,39.07830304819576,130 -77.55679094747489,39.07824848698481,130 -77.5567018440365,39.07820720800013,130 -77.5566129749144,39.07817971711609,130 -77.5565601176703,39.07815227990842,130 -77.55649003142884,39.07812514312991,130 -77.55641965831217,39.07808447741427,130 -77.55636666752329,39.07804382501522,130 -77.55631340193737,39.07798964302215,130 -77.55627748738881,39.07793548482013,130 -77.55622398235607,39.0778812248731,130 -77.55617011589855,39.07781342561473,130 -77.55616885250498,39.07775929578153,130 -77.55613276302441,39.07770513312399,130 -77.55613119539176,39.07763754418078,130 -77.55612994128067,39.07758350300609,130 -77.55612868950168,39.07752948898877,130 -77.55612743787584,39.07747550052245,130 -77.55614326242397,39.07740808084069,130 -77.55619416431077,39.07735422836507,130 -77.55622798053516,39.0773138456882,130 -77.55627879371171,39.07726000980147,130 -77.55634767515208,39.07723317063492,130 -77.55641652818811,39.07720631871571,130 -77.55645126757179,39.07720636098451,130 -77.5565895759495,39.07717960693177,130 -77.55667556266343,39.07716614265628,130 -77.55676173078909,39.07716605554621,130 -77.55684788844424,39.07716596728393,130 -77.55695126328527,39.07716586180079,130 -77.55702017265114,39.07716579178564,130 -77.55707184212275,39.07716564199148,130 -77.55719371133814,39.07719258794874,130 -77.55728068504368,39.07719281353238,130 -77.55733299806603,39.07719294103393,130 -77.55745608765756,39.07723332263193,130 -77.55752599950615,39.07724696566937,130 -77.55766580626718,39.07727410557872,130 -77.5577192171542,39.0773145918772,130 -77.55780629117987,39.07731471467859,130 -77.55789467184492,39.07735522327182,130 -77.55800042206275,39.07739582772851,130 -77.55803606932486,39.07742285027305,130 -77.55812523006064,39.07749043776365,130 -77.55816178986362,39.07754450536058,130 -77.55816511799269,39.07761239238052,130 -77.55818526994572,39.07766678522982,130 -77.55818803663112,39.07774800786289,130 -77.55818876211458,39.07780190993402,130 -77.5581722206332,39.07785583997083,130 -77.55815585421772,39.07789640043942,130 -77.55815780377847,39.07796411909523,130 -77.55815975452643,39.07803187674035,130 -77.55814408216914,39.07809958479273,130 -77.55812846203493,39.07816734184508,130 -77.55811273774844,39.078235124595,130 -77.55802639596918,39.07828906497969,130 -77.5579571791725,39.07831609676271,130 -77.55786982959611,39.07832936609376,130 -77.55778296146298,39.07835639235076,130 -77.55771303691051,39.07835614375615,130 -77.55759131717556,39.07835615131791,130 -77.55750475832849,39.07835644543248,130 -77.55741839301913,39.07835660315515,130 -77.55733113126925,39.07832962358768,130 -77.55727880054476,39.07831590510543,130 -77.55715650682849,39.07827550650102,130 -77.55706915285528,39.07824850777887,130 -77.55699952393086,39.07823504014695,130 -77.55691221807488,39.07820799121833,130 -77.55684190744272,39.07816735712336,130 -77.55677064211555,39.07811290092264,130 -77.55668185075449,39.07808522758795,130 -77.55662803282769,39.07805774921969,130 -77.55655733729675,39.07803026958912,130 -77.55648728438274,39.07800317917826,130 -77.55641754765395,39.07798963875426,130 -77.55631281111854,39.07796255848801,130 -77.55622552427963,39.07794893352732,130 -77.55613801037286,39.07793519589693,130 -77.55605052251796,39.07792146116214,130 -77.55596335160436,39.07792126892434,130 -77.55587667377311,39.07793462935594,130 -77.55579021082822,39.07794818991468,130 -77.55570425707104,39.07798881053494,130 -77.55565276308468,39.07801598385759,130 -77.55558402041198,39.07804316841432,130 -77.55553325472232,39.07811095271126,130 -77.55549954526468,39.07816519598588,130 -77.55548314087856,39.07821943341908,130 -77.55548434517132,39.07828722508933,130 -77.55550304425762,39.07835516631469,130 -77.55553913700774,39.07840978740513,130 -77.55557590448571,39.07847793479703,130 -77.55561167635778,39.07851910134831,130 -77.55564785543876,39.07858721418259,130 -77.55573670049741,39.07868252946123,130 -77.55578968322172,39.07872337446608,130 -77.55584298372956,39.07877788607533,130 -77.55589661295902,39.07884608771801,130 -77.55594977392732,39.07888704504684,130 -77.55600307488619,39.07892811404733,130 -77.55605608042147,39.07895552312547,130 -77.55614432755984,39.07899664076142,130 -77.55621449899954,39.07901038765341,130 -77.55628500963546,39.07903782597407,130 -77.55638968311817,39.07903785623834,130 -77.55647722452933,39.07905154480147,130 -77.5565993304846,39.07905155221601,130 -77.55666928471848,39.07905165228299,130 -77.55673917817558,39.07905172608015,130 -77.55682654889561,39.07905181955478,130 -77.55696601556473,39.07903827903588,130 -77.55705361614687,39.07901154154703,130 -77.55712341131478,39.07898462273391,130 -77.55717519724415,39.07894388514917,130 -77.55722689203463,39.07890311995835,130 -77.55725994921889,39.07884827976122,130 -77.55725832525967,39.07879357536291,130 -77.55725670271303,39.07873889840018,130 -77.5572200325886,39.07867068982601,130 -77.55718366520297,39.07861637607171,130 -77.55714760816785,39.07856173946528,130 -77.55707679617933,39.07850737968121,130 -77.55702367522014,39.07846666392084,130 -77.55695229255639,39.07841187436255,130 -77.55689854991854,39.07837082221801,130 -77.55684514062446,39.07832994956027,130 -77.55675605515887,39.07828871542665,130 -77.55666709662682,39.07824748114088,130 -77.55661354676668,39.07820647959748,130 -77.55654336714008,39.0781794002757,130 -77.5564726653167,39.0781251446447,130 -77.55643762749419,39.07811158743991,130 -77.55629718554248,39.07804379177177,130 -77.55621014067937,39.07804375432753,130 -77.55615757920509,39.0780300797576,130 -77.55605230795393,39.07800273605691,130 -77.55596422840446,39.07796189628587,130 -77.55589374750012,39.07792109014708,130 -77.55582351431889,39.07788049819512,130 -77.55580503976613,39.07782643615206,130 -77.55575157293624,39.07775887143663,130 -77.5557504441514,39.0777047656167,130 -77.55573190332328,39.07765068806393,130 -77.55573116080106,39.07761020309519,130 -77.55574620834128,39.07748882879552,130 -77.55576257703524,39.0774349255318,130 -77.55581357448538,39.07738107393045,130 -77.55586455229951,39.07732724427341,130 -77.55591575501606,39.07728688828364,130 -77.55594988064691,39.07725999091064,130 -77.55603542014525,39.07720621221715,130 -77.5560866385653,39.07717927589373,130 -77.55617247110395,39.07715233440715,130 -77.55624187455106,39.07715242752014,130 -77.5562941424628,39.07713926480527,130 -77.55641451271717,39.07712561760129,130 -77.55653603503913,39.07712572582915,130 -77.55658813513269,39.07712578606697,130 -77.55669167672653,39.07712575398688,130 -77.5568123036788,39.0771256223146,130 -77.55686398532464,39.0771255716953,130 -77.55698456205289,39.07712545266447,130 -77.55708794469386,39.07712526528447,130 -77.55712292969446,39.07713872355743,130 -77.55722732296745,39.07715226354838,130 -77.55736714671984,39.07716609620152,130 -77.55747201692088,39.07717952646819,130 -77.55750715667129,39.07719304787598,130 -77.55762949925794,39.07722013700484,130 -77.55780432266373,39.07724736183072,130 -77.55785695352087,39.07726089991599,130 -77.55794491732198,39.07728790932973,130 -77.55801574078957,39.07732842173137,130 -77.55806915330956,39.07736892644049,130 -77.55814004629373,39.07740947135188,130 -77.55819441309063,39.07747700329734,130 -77.55824795352174,39.077517575518,130 -77.5582666946668,39.07755812800395,130 -77.5583037611802,39.07761235720645,130 -77.55830662953161,39.07766675296266,130 -77.55831093757075,39.07774842981669,130 -77.55831453111294,39.07781656828808,130 -77.55831692787285,39.07789785414876,130 -77.55831792758616,39.07795188576699,130 -77.55830084626824,39.07800570699074,130 -77.55826624150689,39.0780728939632,130 -77.55823307745592,39.07814059190689,130 -77.55819949869804,39.07819473424438,130 -77.55814788658172,39.07823525837409,130 -77.55807912864391,39.07828930329471,130 -77.55802758817329,39.07832983311661,130 -77.55795796280223,39.07834327407502,130 -77.5578701632749,39.07834293303413,130 -77.55776511766523,39.07834277320723,130 -77.55769522559689,39.07834253626997,130 -77.55766040143905,39.07834252083447,130 -77.55753850771812,39.07832912268796,130 -77.55746845722824,39.07830204928021,130 -77.5573813568943,39.07827505507877,130 -77.55732862694195,39.07824795686201,130 -77.55725823565764,39.07820713545102,130 -77.55718825009298,39.07818021113963,130 -77.55713553034708,39.07815316213755,130 -77.5570655366015,39.07812611086121,130 -77.55697790535955,39.07808549945495,130 -77.55690839574288,39.07807207135955,130 -77.55676985085532,39.07807228575487,130 -77.55668133144765,39.07805816169056,130 -77.55662803282769,39.07805774921969,130 -77.5565235375303,39.07807091986869,130 -77.55650617479509,39.07807092089664,130 -77.55638492824794,39.07808447988375,130 -77.55628118848597,39.07811151696944,130 -77.55621206355164,39.07813858494086,130 -77.55616010866235,39.07815208133569,130 -77.55607393047717,39.07819260059603,130 -77.55600470315724,39.07821962435123,130 -77.55595350238308,39.07827380774287,130 -77.55590236944658,39.07832804496194,130 -77.55585149170297,39.07838245329101,130 -77.55580077294765,39.0784370928626,130 -77.55578462057976,39.07849158659877,130 -77.55576845907098,39.07854611018694,130 -77.55576964602434,39.0786006315848,130 -77.5557708333036,39.07865517986931,130 -77.5557546553013,39.07870978978494,130 -77.55575583938567,39.07876439536292,130 -77.55573964937835,39.07881906063011,130 -77.55572345104544,39.07887375609368,130 -77.55568985997577,39.07892851551908,130 -77.5556912544029,39.07899688448506,130 -77.55565733124428,39.07903796458408,130 -77.55565831380522,39.07909262464073,130 -77.55565945115707,39.07914728242375,130 -77.555642932057,39.07920201875781,130 -77.55564431890484,39.07927054449718,130 -77.55566291113693,39.07932540600013,130 -77.55569896727891,39.07938028519982,130 -77.55571729165908,39.079421467374,130 -77.55571729165908,39.079421467374,116 + -77.55582203783959,39.07942140173426,116 -77.55582203783959,39.07942140173426,130 -77.55580072354547,39.07924302421763,130 -77.55579924621459,39.0791744904452,130 -77.5557977265713,39.07910610377936,130 -77.55579679210926,39.07905139649237,130 -77.55583042707002,39.07899663875445,130 -77.55586398846832,39.07894184749336,130 -77.55591551814317,39.07891439467294,130 -77.55600182133786,39.07887342850794,130 -77.55607099833354,39.07884616607952,130 -77.55614015990335,39.07881890742463,130 -77.55620963448429,39.07880531481843,130 -77.55627943855004,39.07880537863051,130 -77.55634889643446,39.07879178074756,130 -77.55645354654631,39.07879187115488,130 -77.55652323934824,39.07879187597376,130 -77.55659225394994,39.07875119894857,130 -77.55664370803291,39.07871041664556,130 -77.55669491930364,39.07866940157159,130 -77.55672848399598,39.07861501731466,130 -77.55677949505511,39.07856047018621,130 -77.55677781710288,39.07849239787434,130 -77.55677614117204,39.07842436161125,130 -77.55677486952965,39.07836994482099,130 -77.55673864600003,39.07831555743201,130 -77.55672004786963,39.07823443608537,130 -77.55668366756755,39.07817999460666,130 -77.55664681471022,39.07809852271344,130 -77.55664538537869,39.07803086352594,130 -77.55662578198013,39.07796279929769,130 -77.55662469280134,39.07790857603493,130 -77.55664099864262,39.07782782334399,130 -77.55667542100386,39.07777408362207,130 -77.55669112644971,39.07771984850919,130 -77.55674170602174,39.07767913070661,130 -77.55679204376315,39.07762493921624,130 -77.55687706097058,39.07758417851471,130 -77.55694564994269,39.07757054597487,130 -77.55703108184174,39.07754338913067,130 -77.55710007122214,39.07754328847737,130 -77.55716870163356,39.07752969897152,130 -77.55720320840673,39.07752965593884,130 -77.55730691701689,39.07751631623401,130 -77.5573937138753,39.07750283316335,130 -77.55742848644481,39.07750286984638,130 -77.55751594197551,39.07751654564433,130 -77.55767372946494,39.07754375019385,130 -77.55774423320338,39.07757091400959,130 -77.55781486771602,39.07759807087082,130 -77.55788572517943,39.07763874549509,130 -77.55793884912332,39.07766585659583,130 -77.55802809577064,39.07772019001918,130 -77.55808226056374,39.07777442952948,130 -77.55811884386149,39.07782861484684,130 -77.55812078173669,39.077896293301,130 -77.55808686889482,39.07793680931807,130 -77.55807087342482,39.07799093963674,130 -77.55803721096733,39.07804498780556,130 -77.55800354259023,39.07809906823454,130 -77.55797009614682,39.078153282109,130 -77.55791869956821,39.07819380325763,130 -77.55786782036573,39.07824800111277,130 -77.55779863242363,39.07828842256596,130 -77.5576937672823,39.07828831754295,130 -77.5575364076828,39.07826102975056,130 -77.55743176013115,39.07823405418915,130 -77.55734552227457,39.07823432323085,130 -77.55731048078832,39.07822077872476,130 -77.55722326333006,39.07819373972681,130 -77.55717093968985,39.07818028440151,130 -77.5570663396186,39.07815329785669,130 -77.55699634852385,39.07812624540112,130 -77.55690877774703,39.07808565592063,130 -77.55683850924393,39.07804506400016,130 -77.55676863116969,39.07801806050121,130 -77.55666239340452,39.07799040624271,130 -77.55659085792247,39.07797611259199,130 -77.55650376367207,39.07796257210111,130 -77.55641668106634,39.07794903267279,130 -77.55632988869745,39.07794902577727,130 -77.55624296842763,39.07794897060581,130 -77.55615605817408,39.07796232521968,130 -77.55610403499058,39.07797575441136,130 -77.55601742650943,39.07800265528741,130 -77.55596598293532,39.07804319250445,130 -77.55589805437972,39.07812438346556,130 -77.5558644450167,39.07817864843481,130 -77.55584820376573,39.07823294492859,130 -77.5558496262399,39.07830085596753,130 -77.55585048041503,39.07834162239234,130 -77.5558521934653,39.07840978433945,130 -77.55588869373149,39.07849140909451,130 -77.55590725944286,39.07854586722578,130 -77.55596132527324,39.07862765347127,130 -77.55601550465619,39.07870955404155,130 -77.55606909162509,39.07876420747042,130 -77.55610526154034,39.07881887269235,130 -77.55617603276792,39.07885993529022,130 -77.55622936600429,39.07890098501589,130 -77.55628271376425,39.07894205041889,130 -77.55638838664451,39.07898314528819,130 -77.5564587952465,39.07901050156125,130 -77.55652888978921,39.07902418480753,130 -77.55661616097818,39.07902418670573,130 -77.55672103827135,39.07902435883155,130 -77.55679092565971,39.07902443352061,130 -77.55687794743812,39.07901085060322,130 -77.55694714584897,39.07898358177135,130 -77.55699941116369,39.07895658782059,130 -77.55706934563287,39.07892974145965,130 -77.55712091042636,39.07887545573669,130 -77.55717267453019,39.07883474350491,130 -77.5572059269038,39.07878005425712,130 -77.55722124265036,39.07871166870606,130 -77.55721922665141,39.07864337885884,130 -77.55718306326416,39.07858865651152,130 -77.55714681542345,39.07853445638826,130 -77.55709370315266,39.07849369408456,130 -77.55704024511884,39.07843939237169,130 -77.55698674779551,39.07838502935675,130 -77.55689814925594,39.07833032648725,130 -77.5568625927756,39.07830304819576,130 -77.55679094747489,39.07824848698481,130 -77.5567018440365,39.07820720800013,130 -77.5566129749144,39.07817971711609,130 -77.5565601176703,39.07815227990842,130 -77.55649003142884,39.07812514312991,130 -77.55641965831217,39.07808447741427,130 -77.55636666752329,39.07804382501522,130 -77.55631340193737,39.07798964302215,130 -77.55627748738881,39.07793548482013,130 -77.55622398235607,39.0778812248731,130 -77.55617011589855,39.07781342561473,130 -77.55616885250498,39.07775929578153,130 -77.55613276302441,39.07770513312399,130 -77.55613119539176,39.07763754418078,130 -77.55612994128067,39.07758350300609,130 -77.55612868950168,39.07752948898877,130 -77.55612743787584,39.07747550052245,130 -77.55614326242397,39.07740808084069,130 -77.55619416431077,39.07735422836507,130 -77.55622798053516,39.0773138456882,130 -77.55627879371171,39.07726000980147,130 -77.55634767515208,39.07723317063492,130 -77.55641652818811,39.07720631871571,130 -77.55645126757179,39.07720636098451,130 -77.5565895759495,39.07717960693177,130 -77.55667556266343,39.07716614265628,130 -77.55676173078909,39.07716605554621,130 -77.55684788844424,39.07716596728393,130 -77.55695126328527,39.07716586180079,130 -77.55702017265114,39.07716579178564,130 -77.55707184212275,39.07716564199148,130 -77.55719371133814,39.07719258794874,130 -77.55728068504368,39.07719281353238,130 -77.55733299806603,39.07719294103393,130 -77.55745608765756,39.07723332263193,130 -77.55752599950615,39.07724696566937,130 -77.55766580626718,39.07727410557872,130 -77.5577192171542,39.0773145918772,130 -77.55780629117987,39.07731471467859,130 -77.55789467184492,39.07735522327182,130 -77.55800042206275,39.07739582772851,130 -77.55803606932486,39.07742285027305,130 -77.55812523006064,39.07749043776365,130 -77.55816178986362,39.07754450536058,130 -77.55816511799269,39.07761239238052,130 -77.55818526994572,39.07766678522982,130 -77.55818803663112,39.07774800786289,130 -77.55818876211458,39.07780190993402,130 -77.5581722206332,39.07785583997083,130 -77.55815585421772,39.07789640043942,130 -77.55815780377847,39.07796411909523,130 -77.55815975452643,39.07803187674035,130 -77.55814408216914,39.07809958479273,130 -77.55812846203493,39.07816734184508,130 -77.55811273774844,39.078235124595,130 -77.55802639596918,39.07828906497969,130 -77.5579571791725,39.07831609676271,130 -77.55786982959611,39.07832936609376,130 -77.55778296146298,39.07835639235076,130 -77.55771303691051,39.07835614375615,130 -77.55759131717556,39.07835615131791,130 -77.55750475832849,39.07835644543248,130 -77.55741839301913,39.07835660315515,130 -77.55733113126925,39.07832962358768,130 -77.55727880054476,39.07831590510543,130 -77.55715650682849,39.07827550650102,130 -77.55706915285528,39.07824850777887,130 -77.55699952393086,39.07823504014695,130 -77.55691221807488,39.07820799121833,130 -77.55684190744272,39.07816735712336,130 -77.55677064211555,39.07811290092264,130 -77.55668185075449,39.07808522758795,130 -77.55662803282769,39.07805774921969,130 -77.55655733729675,39.07803026958912,130 -77.55648728438274,39.07800317917826,130 -77.55641754765395,39.07798963875426,130 -77.55631281111854,39.07796255848801,130 -77.55622552427963,39.07794893352732,130 -77.55613801037286,39.07793519589693,130 -77.55605052251796,39.07792146116214,130 -77.55596335160436,39.07792126892434,130 -77.55587667377311,39.07793462935594,130 -77.55579021082822,39.07794818991468,130 -77.55570425707104,39.07798881053494,130 -77.55565276308468,39.07801598385759,130 -77.55558402041198,39.07804316841432,130 -77.55553325472232,39.07811095271126,130 -77.55549954526468,39.07816519598588,130 -77.55548314087856,39.07821943341908,130 -77.55548434517132,39.07828722508933,130 -77.55550304425762,39.07835516631469,130 -77.55553913700774,39.07840978740513,130 -77.55557590448571,39.07847793479703,130 -77.55561167635778,39.07851910134831,130 -77.55564785543876,39.07858721418259,130 -77.55573670049741,39.07868252946123,130 -77.55578968322172,39.07872337446608,130 -77.55584298372956,39.07877788607533,130 -77.55589661295902,39.07884608771801,130 -77.55594977392732,39.07888704504684,130 -77.55600307488619,39.07892811404733,130 -77.55605608042147,39.07895552312547,130 -77.55614432755984,39.07899664076142,130 -77.55621449899954,39.07901038765341,130 -77.55628500963546,39.07903782597407,130 -77.55638968311817,39.07903785623834,130 -77.55647722452933,39.07905154480147,130 -77.5565993304846,39.07905155221601,130 -77.55666928471848,39.07905165228299,130 -77.55673917817558,39.07905172608015,130 -77.55682654889561,39.07905181955478,130 -77.55696601556473,39.07903827903588,130 -77.55705361614687,39.07901154154703,130 -77.55712341131478,39.07898462273391,130 -77.55717519724415,39.07894388514917,130 -77.55722689203463,39.07890311995835,130 -77.55725994921889,39.07884827976122,130 -77.55725832525967,39.07879357536291,130 -77.55725670271303,39.07873889840018,130 -77.5572200325886,39.07867068982601,130 -77.55718366520297,39.07861637607171,130 -77.55714760816785,39.07856173946528,130 -77.55707679617933,39.07850737968121,130 -77.55702367522014,39.07846666392084,130 -77.55695229255639,39.07841187436255,130 -77.55689854991854,39.07837082221801,130 -77.55684514062446,39.07832994956027,130 -77.55675605515887,39.07828871542665,130 -77.55666709662682,39.07824748114088,130 -77.55661354676668,39.07820647959748,130 -77.55654336714008,39.0781794002757,130 -77.5564726653167,39.0781251446447,130 -77.55643762749419,39.07811158743991,130 -77.55629718554248,39.07804379177177,130 -77.55621014067937,39.07804375432753,130 -77.55615757920509,39.0780300797576,130 -77.55605230795393,39.07800273605691,130 -77.55596422840446,39.07796189628587,130 -77.55589374750012,39.07792109014708,130 -77.55582351431889,39.07788049819512,130 -77.55580503976613,39.07782643615206,130 -77.55575157293624,39.07775887143663,130 -77.5557504441514,39.0777047656167,130 -77.55573190332328,39.07765068806393,130 -77.55573116080106,39.07761020309519,130 -77.55574620834128,39.07748882879552,130 -77.55576257703524,39.0774349255318,130 -77.55581357448538,39.07738107393045,130 -77.55586455229951,39.07732724427341,130 -77.55591575501606,39.07728688828364,130 -77.55594988064691,39.07725999091064,130 -77.55603542014525,39.07720621221715,130 -77.5560866385653,39.07717927589373,130 -77.55617247110395,39.07715233440715,130 -77.55624187455106,39.07715242752014,130 -77.5562941424628,39.07713926480527,130 -77.55641451271717,39.07712561760129,130 -77.55653603503913,39.07712572582915,130 -77.55658813513269,39.07712578606697,130 -77.55669167672653,39.07712575398688,130 -77.5568123036788,39.0771256223146,130 -77.55686398532464,39.0771255716953,130 -77.55698456205289,39.07712545266447,130 -77.55708794469386,39.07712526528447,130 -77.55712292969446,39.07713872355743,130 -77.55722732296745,39.07715226354838,130 -77.55736714671984,39.07716609620152,130 -77.55747201692088,39.07717952646819,130 -77.55750715667129,39.07719304787598,130 -77.55762949925794,39.07722013700484,130 -77.55780432266373,39.07724736183072,130 -77.55785695352087,39.07726089991599,130 -77.55794491732198,39.07728790932973,130 -77.55801574078957,39.07732842173137,130 -77.55806915330956,39.07736892644049,130 -77.55814004629373,39.07740947135188,130 -77.55819441309063,39.07747700329734,130 -77.55824795352174,39.077517575518,130 -77.5582666946668,39.07755812800395,130 -77.5583037611802,39.07761235720645,130 -77.55830662953161,39.07766675296266,130 -77.55831093757075,39.07774842981669,130 -77.55831453111294,39.07781656828808,130 -77.55831692787285,39.07789785414876,130 -77.55831792758616,39.07795188576699,130 -77.55830084626824,39.07800570699074,130 -77.55826624150689,39.0780728939632,130 -77.55823307745592,39.07814059190689,130 -77.55819949869804,39.07819473424438,130 -77.55814788658172,39.07823525837409,130 -77.55807912864391,39.07828930329471,130 -77.55802758817329,39.07832983311661,130 -77.55795796280223,39.07834327407502,130 -77.5578701632749,39.07834293303413,130 -77.55776511766523,39.07834277320723,130 -77.55769522559689,39.07834253626997,130 -77.55766040143905,39.07834252083447,130 -77.55753850771812,39.07832912268796,130 -77.55746845722824,39.07830204928021,130 -77.5573813568943,39.07827505507877,130 -77.55732862694195,39.07824795686201,130 -77.55725823565764,39.07820713545102,130 -77.55718825009298,39.07818021113963,130 -77.55713553034708,39.07815316213755,130 -77.5570655366015,39.07812611086121,130 -77.55697790535955,39.07808549945495,130 -77.55690839574288,39.07807207135955,130 -77.55676985085532,39.07807228575487,130 -77.55668133144765,39.07805816169056,130 -77.55662803282769,39.07805774921969,130 -77.5565235375303,39.07807091986869,130 -77.55650617479509,39.07807092089664,130 -77.55638492824794,39.07808447988375,130 -77.55628118848597,39.07811151696944,130 -77.55621206355164,39.07813858494086,130 -77.55616010866235,39.07815208133569,130 -77.55607393047717,39.07819260059603,130 -77.55600470315724,39.07821962435123,130 -77.55595350238308,39.07827380774287,130 -77.55590236944658,39.07832804496194,130 -77.55585149170297,39.07838245329101,130 -77.55580077294765,39.0784370928626,130 -77.55578462057976,39.07849158659877,130 -77.55576845907098,39.07854611018694,130 -77.55576964602434,39.0786006315848,130 -77.5557708333036,39.07865517986931,130 -77.5557546553013,39.07870978978494,130 -77.55575583938567,39.07876439536292,130 -77.55573964937835,39.07881906063011,130 -77.55572345104544,39.07887375609368,130 -77.55568985997577,39.07892851551908,130 -77.5556912544029,39.07899688448506,130 -77.55565733124428,39.07903796458408,130 -77.55565831380522,39.07909262464073,130 -77.55565945115707,39.07914728242375,130 -77.555642932057,39.07920201875781,130 -77.55564431890484,39.07927054449718,130 -77.55566291113693,39.07932540600013,130 -77.55569896727891,39.07938028519982,130 -77.55571729165908,39.079421467374,130 -77.55571729165908,39.079421467374,116 @@ -251,7 +252,8 @@ accuracy_v: VAUnknown #m_ylw-pushpin 1 - -77.55603567233382,39.07873049771908,0 + -77.55603567233382,39.07873049771908,116.2 + absolute @@ -264,7 +266,7 @@ accuracy_v: VAUnknown - -77.55597781746202,39.07936757565933,116 -77.55560329805407,39.07936682276524,116 -77.55560629395286,39.07957064621919,116 -77.55597550594716,39.07949680701524,116 -77.55597781746202,39.07936757565933,116 + -77.55597781746202,39.07936757565933,116 -77.55560329805407,39.07936682276524,116 -77.55560629395286,39.07957064621919,116 -77.55597550594716,39.07949680701524,116 -77.55597781746202,39.07936757565933,116 @@ -280,7 +282,7 @@ accuracy_v: VAUnknown - -77.55739240518497,39.07861126252124,150 -77.55552564243474,39.07797989084367,150 -77.55534033042699,39.0787516883203,150 -77.55715796832713,39.0793434364829,150 -77.55739240518497,39.07861126252124,150 + -77.55739240518497,39.07861126252124,150 -77.55552564243474,39.07797989084367,150 -77.55534033042699,39.0787516883203,150 -77.55715796832713,39.0793434364829,150 -77.55739240518497,39.07861126252124,150 @@ -296,7 +298,7 @@ accuracy_v: VAUnknown - -77.5588508803686,39.07803641409111,130 -77.55830448073627,39.07720051617602,130 -77.55584816730237,39.0768579316009,130 -77.55558960405277,39.07744607965823,130 -77.55834272013574,39.0784742545806,130 -77.5588508803686,39.07803641409111,130 + -77.5588508803686,39.07803641409111,130 -77.55830448073627,39.07720051617602,130 -77.55584816730237,39.0768579316009,130 -77.55558960405277,39.07744607965823,130 -77.55834272013574,39.0784742545806,130 -77.5588508803686,39.07803641409111,130 @@ -311,7 +313,7 @@ accuracy_v: VAUnknown - -77.5606699601652,39.07934433155774,0 -77.55775362816441,39.07576549638149,0 -77.55226383817052,39.07762037878245,0 -77.55533259494143,39.08111260133825,0 -77.5606699601652,39.07934433155774,0 + -77.5606699601652,39.07934433155774,0 -77.55775362816441,39.07576549638149,0 -77.55226383817052,39.07762037878245,0 -77.55533259494143,39.08111260133825,0 -77.5606699601652,39.07934433155774,0 @@ -322,7 +324,7 @@ accuracy_v: VAUnknown flight: test_flight_zigzagsouth 0 1 - serial_number: EXMPLS8YEC3L5dyn + serial_number: ZUHK5C973Q operation_description: Zig zags to the south operator_id: OP-vXgJtTPY registration_number: FA.7NYTKGWDM6V @@ -348,7 +350,8 @@ accuracy_v: VAUnknown #m_ylw-pushpin 1 - -77.55582470247435,39.07830669746138,0 + -77.55582470247435,39.07830669746138,117.0 + absolute @@ -358,7 +361,7 @@ accuracy_v: VAUnknown 1 - -77.55583982316382,39.079435151537,116 -77.55583982316382,39.079435151537,155 -77.55625936170486,39.07943524384587,155 -77.5563463824261,39.0794214940529,155 -77.55641589471819,39.07940779679775,155 -77.55648548336796,39.07939406916952,155 -77.55655522702286,39.0793804791512,155 -77.55659031402905,39.07938057751846,155 -77.55671221588041,39.07933987459803,155 -77.55678060226288,39.07931193908678,155 -77.55684986799875,39.07928457888146,155 -77.55691912297088,39.07925724009064,155 -77.55698804382784,39.07921622040164,155 -77.55705781353996,39.07918921296277,155 -77.55709236118824,39.07916202671488,155 -77.55716144401504,39.07910766174395,155 -77.55721324380197,39.07906689777509,155 -77.55726471587593,39.07901248522258,155 -77.55731567368373,39.07897129838053,155 -77.55736607254345,39.07891636325027,155 -77.55739909688879,39.0788615294327,155 -77.55744986609849,39.0788203601264,155 -77.5575006123796,39.07877921007228,155 -77.55755090280063,39.07872441694036,155 -77.55758426768803,39.07868335510864,155 -77.55763494211836,39.07864226773531,155 -77.5577028478146,39.07860103363738,155 -77.55775392858064,39.07856036622852,155 -77.55780512661869,39.07851949200526,155 -77.55787414178987,39.07847889117868,155 -77.55792561346526,39.07843826294049,155 -77.55797666402941,39.0783840405177,155 -77.5580280114479,39.07834341104237,155 -77.55806156119789,39.07828921836247,155 -77.55806002977883,39.07823488909376,155 -77.55802344867753,39.07818047349828,155 -77.55796971555039,39.07813966716193,155 -77.55791575669394,39.07808531697554,155 -77.55786256518122,39.07805809473255,155 -77.55779154676799,39.0780172954739,155 -77.55772092059283,39.07799005644021,155 -77.55765055467272,39.07797626033044,155 -77.55758001246863,39.07794905136333,155 -77.55749197387746,39.07792185205533,155 -77.55740501446573,39.07792189894712,155 -77.55731865720304,39.07792187233012,155 -77.55724879037773,39.07789494870792,155 -77.55721343484139,39.07786785959256,155 -77.55712592440689,39.0778273775921,155 -77.55707298733169,39.07778684833185,155 -77.55703679568038,39.07773276881985,155 -77.55698346155417,39.07767870215029,155 -77.55694738080666,39.07762464058171,155 -77.55692839328466,39.0775705220434,155 -77.55692678419211,39.07751644636669,155 -77.55694250223392,39.07746241953076,155 -77.5569753861965,39.07740835183327,155 -77.55699108713144,39.07735437766693,155 -77.557041254295,39.07730039607954,155 -77.55707415960131,39.07724646484606,155 -77.5571421268415,39.07720606382691,155 -77.55717569069559,39.07716586512294,155 -77.55720967422666,39.07713899487054,155 -77.55734679237135,39.07705839961917,155 -77.557397606683,39.07700473473612,155 -77.55744872785777,39.07696450590804,155 -77.55750020341826,39.07693771750005,155 -77.55755084787818,39.0768840703908,155 -77.55756662449907,39.07683039284147,155 -77.55756455241267,39.07676334131091,155 -77.55756310305671,39.07670973313215,155 -77.55756129529762,39.0766427536251,155 -77.55750816627672,39.07660254503576,155 -77.55745506186024,39.07656235741769,155 -77.5573846410598,39.07652217376091,155 -77.55733193215214,39.07649539232104,155 -77.55720990749181,39.07646857411486,155 -77.55712291587163,39.07645514054899,155 -77.55705292691766,39.07642837030821,155 -77.55696531070818,39.07638824098006,155 -77.55686109584221,39.07637483820383,155 -77.55679124899605,39.07634810089878,155 -77.55672124438665,39.0763081038459,155 -77.55663509533335,39.07629495075815,155 -77.55656604580095,39.07628134930888,155 -77.55648013253986,39.07628138004181,155 -77.55641069497381,39.07625472989677,155 -77.5563413231686,39.07622809001835,155 -77.55628881487358,39.07618808301039,155 -77.55627034324068,39.07613473549092,155 -77.55626902997079,39.07608142639128,155 -77.55625056038696,39.07602814696794,155 -77.55624892622572,39.07596158217248,155 -77.55626476713635,39.07590835692002,155 -77.55628027003877,39.07584186638434,155 -77.55629583021005,39.0757754394947,155 -77.55634609757696,39.07572230348674,155 -77.55636181632673,39.07566925207633,155 -77.55642875428474,39.07560277891598,155 -77.55646229036702,39.07556299740847,155 -77.55651269595886,39.0755099719016,155 -77.55658028194432,39.07545687657122,155 -77.5566314337236,39.07543055889112,155 -77.55668241563703,39.075390805086,155 -77.55673357072662,39.07536430911301,155 -77.55678387555156,39.07529810137275,155 -77.5568174473014,39.07525834428458,155 -77.55686792661653,39.07520541200274,155 -77.55686664705212,39.07515252221706,155 -77.55684844580472,39.07511288286268,155 -77.5567957742127,39.07507327963388,155 -77.55672589231423,39.07503370396105,155 -77.55667358060947,39.07500733520837,155 -77.55662064005024,39.07494132521197,155 -77.55655195148549,39.0749414122245,155 -77.55649951910223,39.07490184787206,155 -77.55641319300734,39.07487549848385,155 -77.55632684873027,39.07484915644052,155 -77.55624075370906,39.07483601108177,155 -77.55617183849741,39.07482285971802,155 -77.5560514694919,39.07480971742964,155 -77.55598240010478,39.07479668437745,155 -77.55589604064231,39.07477020017173,155 -77.55582753612724,39.07475711005762,155 -77.55575901496999,39.07474390848704,155 -77.55567316011825,39.07471753753484,155 -77.55560464769755,39.07470433825589,155 -77.5555188103086,39.07467797219564,155 -77.55545030618596,39.07466477382714,155 -77.555381608763,39.07463843063523,155 -77.5553290625578,39.07457267500943,155 -77.55531070248981,39.07450696725684,155 -77.55530993618594,39.07446756381773,155 -77.55530840436215,39.0743888012859,155 -77.55530722295616,39.07433648136412,155 -77.55534031824057,39.07428403647764,155 -77.55535630665116,39.07421850025493,155 -77.55537250461977,39.07417909242871,155 -77.55542240781676,39.07411364905043,155 -77.55547217078366,39.07404822846397,155 -77.55550473634842,39.07398294884229,155 -77.55555488628198,39.07393060574787,155 -77.55562214068232,39.07387829279495,155 -77.55567232523597,39.07382599941986,155 -77.55573952732014,39.07377372880429,155 -77.55578966641808,39.07372147913539,155 -77.55583978721094,39.07366925276456,155 -77.55588988658107,39.07361704518656,155 -77.55592321670024,39.07357769648011,155 -77.55597329876106,39.07351260332241,155 -77.55600676726684,39.07347340789786,155 -77.55605707840394,39.07342114945977,155 -77.55610737601145,39.07336890637622,155 -77.5561061047263,39.07330378667933,155 -77.55607115357101,39.07326481764433,155 -77.55598442830618,39.07319998083042,155 -77.55593265246432,39.07317411409274,155 -77.55588089483916,39.07314825704496,155 -77.55579474963423,39.07310951189354,155 -77.55574351020073,39.0731096635514,155 -77.55565742592363,39.07307093899414,155 -77.55560574985154,39.07304511656746,155 -77.55548629796223,39.07303233390557,155 -77.55538406690653,39.07301944083893,155 -77.55528207758758,39.07301948729953,155 -77.55521370913112,39.07299352919826,155 -77.55514556263711,39.07296752396775,155 -77.55509458660858,39.07295445112477,155 -77.55504360121827,39.07294138784815,155 -77.55497524416407,39.07290239118549,155 -77.55488948887007,39.07283746493867,155 -77.55480424803199,39.07279845031442,155 -77.55473586690844,39.07275953448868,155 -77.55466724259874,39.07270774793564,155 -77.5546155978211,39.07265598693682,155 -77.55458092250109,39.07260425337304,155 -77.55457976198092,39.07252671478939,155 -77.55457898919335,39.07247505161335,155 -77.55462901487543,39.07242342664367,155 -77.55469597070484,39.0723849184908,155 -77.55476276173938,39.07233344801308,155 -77.55479593707673,39.07229483338106,155 -77.55486267215731,39.07224340643762,155 -77.55492980222827,39.07221776341309,155 -77.55497981088554,39.07217922120712,155 -77.55504677358421,39.07214060170174,155 -77.55513069509799,39.07210197170252,155 -77.55519805967755,39.07206293798806,155 -77.55524839443783,39.07202416823111,155 -77.55531595037247,39.07199810996094,155 -77.55540011934066,39.07194620931208,155 -77.55545044568863,39.07190735703264,155 -77.55550068625885,39.07185550305653,155 -77.55550037744239,39.07182965354041,155 -77.55555056210889,39.0717517521411,155 -77.55556681883583,39.07168703736127,155 -77.55554931490535,39.07164842134348,155 -77.55542934855572,39.07158477011069,155 -77.55541192178038,39.07157222618477,155 -77.55529200839877,39.07156081931382,155 -77.55515628505219,39.07154851172476,155 -77.55500360469219,39.07153631760238,155 -77.55491887346658,39.0715367289333,155 -77.5548850359215,39.07153693946091,155 -77.55476661400404,39.07153712865194,155 -77.55471592111967,39.07153713618887,155 -77.55463167299612,39.07153715122711,155 -77.55453069856165,39.07153686584964,155 -77.5544297791994,39.07153653740981,155 -77.55432877569508,39.07153620331349,155 -77.55424456669243,39.07153596533659,155 -77.55410938996964,39.07151012782357,155 -77.55402520946635,39.07150986550136,155 -77.55395726120402,39.07147129831761,155 -77.5538725492688,39.07143281951804,155 -77.5538039086486,39.07138257737283,155 -77.55376972445343,39.07134424210825,155 -77.55375232065258,39.07129303942025,155 -77.55373479904557,39.07122905102998,155 -77.55373392305137,39.07113942150175,155 -77.55373313805653,39.07107587658399,155 -77.55376607345218,39.07102491662597,155 -77.55379926443703,39.07097371165892,155 -77.55383247157444,39.07090954057595,155 -77.55389944683672,39.07087112469486,155 -77.5539663988976,39.07083273546056,155 -77.55403335762172,39.07080729782273,155 -77.55411716459635,39.07079480499262,155 -77.55420095553039,39.07078231363862,155 -77.55433509082692,39.07076996516957,155 -77.55440226143577,39.07077014827345,155 -77.55450298868958,39.07077040745927,155 -77.554536716063,39.07078310615764,155 -77.55465500627929,39.07079543584041,155 -77.55473966503243,39.0708077449432,155 -77.55482445138017,39.07083284994673,155 -77.55487530380617,39.07084536296107,155 -77.55496014073313,39.07087048310082,155 -77.55504500827577,39.07089561102387,155 -77.55514700703854,39.07093344601503,155 -77.55519832065234,39.07097154796959,155 -77.55521545558101,39.07098429304313,155 -77.55531859627676,39.07102097521855,155 -77.5553702699023,39.07105881698023,155 -77.55543828071437,39.07109725432625,155 -77.55550714393675,39.07113484742812,155 -77.55557665422771,39.07118489269062,155 -77.55561141778013,39.0712099621397,155 -77.55569805254453,39.07127297432253,155 -77.55573313660423,39.07132391149485,155 -77.55580288806098,39.07138712365032,155 -77.55583800451321,39.07143824081088,155 -77.55589070906437,39.0715017209363,155 -77.5559609008197,39.07157801838709,155 -77.5560131902724,39.07161593012305,155 -77.55601492247612,39.07170602944812,155 -77.55603335873306,39.07177020429403,155 -77.55603460363938,39.07183464228142,155 -77.55603562101246,39.07188623262279,155 -77.55603663313593,39.07193782498356,155 -77.55600324240181,39.0719898241242,155 -77.5560042488834,39.07204145405622,155 -77.55598798720708,39.07209327604536,155 -77.5559716261027,39.07214525120187,155 -77.55593806035752,39.0721974715915,155 -77.55590319124954,39.07225067229431,155 -77.55586805943051,39.0723039442614,155 -77.55581599328131,39.07235711014008,155 -77.55576530087652,39.07240934961801,155 -77.55571486574227,39.07244834908205,155 -77.555664197984,39.07247442090027,155 -77.55559670874968,39.0725134952491,155 -77.55554657152523,39.07256541586111,155 -77.55549590438231,39.07259146280942,155 -77.55541185951569,39.07265645475727,155 -77.55532764896437,39.07270850079024,155 -77.55527746185749,39.0727604270781,155 -77.55522682993978,39.07279952580038,155 -77.55516022212136,39.07285107373701,155 -77.55511052157735,39.07290267325191,155 -77.5550609847968,39.07296733059464,155 -77.55501108098993,39.07301906741039,155 -77.55496085137922,39.07305794195631,155 -77.55492775966786,39.0731098451159,155 -77.55487789328228,39.07317474398052,155 -77.55484476489633,39.07322669901443,155 -77.55482839092451,39.0732656921151,155 -77.55482957260377,39.07334375642169,155 -77.55483075459294,39.07342187164019,155 -77.55483134602017,39.07346094958224,155 -77.55486610047136,39.07351310771325,155 -77.55486711730974,39.073578290428,155 -77.55486831753598,39.07364337954512,155 -77.55490323044987,39.07369555338931,155 -77.55497231590066,39.0737476805452,155 -77.55502413181125,39.07378681979181,155 -77.55509301480798,39.07382596758243,155 -77.55516192539022,39.07386512955034,155 -77.5552479186014,39.0739043129053,155 -77.55531658610887,39.07393046009314,155 -77.55538498064371,39.07394354605657,155 -77.55547043341294,39.07396982081654,155 -77.55553901744193,39.07399604490806,155 -77.55564176749577,39.07402220427436,155 -77.55572727478068,39.0740352930631,155 -77.55579597947029,39.07406147733408,155 -77.55588150783807,39.07407464115769,155 -77.55593321567368,39.07410078324232,155 -77.55601929709903,39.07412686321145,155 -77.55608845879642,39.07415299560336,155 -77.55615753329666,39.0741790788908,155 -77.55622643796855,39.07419207366919,155 -77.55629563338684,39.07421819879288,155 -77.55636484053809,39.07424439718398,155 -77.55643428542864,39.07428363793039,155 -77.55648632401078,39.0743098240216,155 -77.55650439396037,39.07434917290075,155 -77.55654083156826,39.07444104347965,155 -77.5565422911905,39.07450676653995,155 -77.55652665938601,39.07457250722523,155 -77.55649326935391,39.07461198459819,155 -77.55646017672085,39.07466463008746,155 -77.55640981158584,39.07471735011936,155 -77.55635947695566,39.07477006531037,155 -77.5563088431776,39.0748096206542,155 -77.55627535803437,39.07484917809823,155 -77.55620779205255,39.07490195184459,155 -77.55615743142359,39.07495474122557,155 -77.55610687797507,39.07500752024524,155 -77.55603961960244,39.07507354040083,155 -77.55600639055382,39.07511327235885,155 -77.55593881138435,39.0751527592534,155 -77.555889037307,39.07521882570342,155 -77.55583906346399,39.07527160446204,155 -77.55580610917214,39.07532451240416,155 -77.55579019931548,39.07537745521055,155 -77.55577399316701,39.07541718130632,155 -77.55579249853695,39.07548343740412,155 -77.55582780150429,39.07553647270942,155 -77.55586315228631,39.07558957131254,155 -77.55591570211514,39.07564264523014,155 -77.55596797981246,39.07568246993564,155 -77.55602000217175,39.07570902277393,155 -77.55607203480623,39.07573558176132,155 -77.55615803095007,39.07574885939719,155 -77.55621008323531,39.07577542547744,155 -77.55629642102235,39.0758019929856,155 -77.55636563449104,39.07582856599011,155 -77.5564170991068,39.07582857598152,155 -77.55652065070318,39.0758552024905,155 -77.55657246232337,39.07586849828371,155 -77.55669379476723,39.07588172553369,155 -77.55676285101325,39.07588173301574,155 -77.55683224892287,39.07589507324652,155 -77.55691931917566,39.07592169087134,155 -77.55700609246189,39.07593501348379,155 -77.5570930892949,39.0759483597884,155 -77.55718013415721,39.07596169787798,155 -77.55721514766743,39.07597502249723,155 -77.55731975936946,39.07598836578579,155 -77.55735601605161,39.07604167397848,155 -77.55737479997821,39.07609499804567,155 -77.55737609157907,39.07614832816279,155 -77.55737738433579,39.07620167928646,155 -77.55737887762956,39.07626839517224,155 -77.55734492611509,39.07632171193588,155 -77.55729383420513,39.07636172524404,155 -77.55724287380123,39.07640177301934,155 -77.55719190146023,39.07644183078342,155 -77.55710658914859,39.07649523077759,155 -77.55703824775036,39.0765352955673,155 -77.55698722623782,39.0765753818653,155 -77.55695288058624,39.07658873275953,155 -77.55683406202506,39.07666906874635,155 -77.55678344189826,39.07670927213186,155 -77.55671549597244,39.07674951504881,155 -77.55664818078473,39.07680324161257,155 -77.5565807311543,39.07685689356682,155 -77.55656485581206,39.07691054258982,155 -77.55656669189953,39.07696445147731,155 -77.55660256780592,39.07701815611604,155 -77.55663861707005,39.0770719260388,155 -77.55669167818145,39.07712568873453,155 -77.55676171428208,39.07716599358002,155 -77.55681490276943,39.07721979994174,155 -77.55688497505091,39.07726014033345,155 -77.55693744676324,39.07728703332126,155 -77.55702440538036,39.07731389335921,155 -77.55707689879439,39.07734079519227,155 -77.5571294047335,39.07736770399222,155 -77.55721697999736,39.07740809612752,155 -77.55726986051943,39.07743510421623,155 -77.55730512541587,39.07744875581837,155 -77.55746265933334,39.07747584381153,155 -77.55753260917318,39.0774895754819,155 -77.55760281754431,39.07750310640253,155 -77.55769036149147,39.07751677744778,155 -77.55774351529888,39.0775438824566,155 -77.5578145756044,39.07758451513264,155 -77.5578512703793,39.07765214947332,155 -77.55785260727228,39.07769270482378,155 -77.55785468327635,39.07776036980896,155 -77.55785616665364,39.07781447018433,155 -77.55784011908938,39.07786851464544,155 -77.55778963378754,39.07794951551884,155 -77.55773875304561,39.07800363692049,155 -77.55770517712827,39.07805765914385,155 -77.55765410851937,39.07811177718364,155 -77.5575856451336,39.07816576587037,155 -77.55753481954953,39.07820670711883,155 -77.55744908731107,39.07823395696809,155 -77.55738137911905,39.07827494217238,155 -77.55732990295733,39.07828865639222,155 -77.55726156142937,39.07831600201826,155 -77.55717590941266,39.0783435474639,155 -77.55710707096449,39.0783572563939,155 -77.55702138899055,39.078384835618,155 -77.55695235512211,39.07841187968155,155 -77.55688179955318,39.07842488052266,155 -77.55679426856136,39.07845156582567,155 -77.556725208857,39.07846483101778,155 -77.55665581027621,39.07849228561925,155 -77.55658687593314,39.07853300338208,155 -77.55653541282106,39.07857372195614,155 -77.55646664227744,39.07861444266769,155 -77.55639791293177,39.07865529755893,155 -77.5563469358097,39.07870982941636,155 -77.55631338238329,39.07876440440054,155 -77.5562623402915,39.07881900170867,155 -77.55622841833495,39.07885995732045,155 -77.55617767013348,39.07892825868684,155 -77.55616149360196,39.07898295206095,155 -77.55612784545347,39.07903765618298,155 -77.55609418008744,39.0790923872545,155 -77.55606049805479,39.07914714337816,155 -77.55600932385619,39.0792019106908,155 -77.55597560525381,39.07925672208376,155 -77.55592408026418,39.0792978196591,155 -77.5558903891669,39.07935274113207,155 -77.5558903891669,39.07935274113207,116 + -77.55583982316382,39.079435151537,116 -77.55583982316382,39.079435151537,155 -77.55625936170486,39.07943524384587,155 -77.5563463824261,39.0794214940529,155 -77.55641589471819,39.07940779679775,155 -77.55648548336796,39.07939406916952,155 -77.55655522702286,39.0793804791512,155 -77.55659031402905,39.07938057751846,155 -77.55671221588041,39.07933987459803,155 -77.55678060226288,39.07931193908678,155 -77.55684986799875,39.07928457888146,155 -77.55691912297088,39.07925724009064,155 -77.55698804382784,39.07921622040164,155 -77.55705781353996,39.07918921296277,155 -77.55709236118824,39.07916202671488,155 -77.55716144401504,39.07910766174395,155 -77.55721324380197,39.07906689777509,155 -77.55726471587593,39.07901248522258,155 -77.55731567368373,39.07897129838053,155 -77.55736607254345,39.07891636325027,155 -77.55739909688879,39.0788615294327,155 -77.55744986609849,39.0788203601264,155 -77.5575006123796,39.07877921007228,155 -77.55755090280063,39.07872441694036,155 -77.55758426768803,39.07868335510864,155 -77.55763494211836,39.07864226773531,155 -77.5577028478146,39.07860103363738,155 -77.55775392858064,39.07856036622852,155 -77.55780512661869,39.07851949200526,155 -77.55787414178987,39.07847889117868,155 -77.55792561346526,39.07843826294049,155 -77.55797666402941,39.0783840405177,155 -77.5580280114479,39.07834341104237,155 -77.55806156119789,39.07828921836247,155 -77.55806002977883,39.07823488909376,155 -77.55802344867753,39.07818047349828,155 -77.55796971555039,39.07813966716193,155 -77.55791575669394,39.07808531697554,155 -77.55786256518122,39.07805809473255,155 -77.55779154676799,39.0780172954739,155 -77.55772092059283,39.07799005644021,155 -77.55765055467272,39.07797626033044,155 -77.55758001246863,39.07794905136333,155 -77.55749197387746,39.07792185205533,155 -77.55740501446573,39.07792189894712,155 -77.55731865720304,39.07792187233012,155 -77.55724879037773,39.07789494870792,155 -77.55721343484139,39.07786785959256,155 -77.55712592440689,39.0778273775921,155 -77.55707298733169,39.07778684833185,155 -77.55703679568038,39.07773276881985,155 -77.55698346155417,39.07767870215029,155 -77.55694738080666,39.07762464058171,155 -77.55692839328466,39.0775705220434,155 -77.55692678419211,39.07751644636669,155 -77.55694250223392,39.07746241953076,155 -77.5569753861965,39.07740835183327,155 -77.55699108713144,39.07735437766693,155 -77.557041254295,39.07730039607954,155 -77.55707415960131,39.07724646484606,155 -77.5571421268415,39.07720606382691,155 -77.55717569069559,39.07716586512294,155 -77.55720967422666,39.07713899487054,155 -77.55734679237135,39.07705839961917,155 -77.557397606683,39.07700473473612,155 -77.55744872785777,39.07696450590804,155 -77.55750020341826,39.07693771750005,155 -77.55755084787818,39.0768840703908,155 -77.55756662449907,39.07683039284147,155 -77.55756455241267,39.07676334131091,155 -77.55756310305671,39.07670973313215,155 -77.55756129529762,39.0766427536251,155 -77.55750816627672,39.07660254503576,155 -77.55745506186024,39.07656235741769,155 -77.5573846410598,39.07652217376091,155 -77.55733193215214,39.07649539232104,155 -77.55720990749181,39.07646857411486,155 -77.55712291587163,39.07645514054899,155 -77.55705292691766,39.07642837030821,155 -77.55696531070818,39.07638824098006,155 -77.55686109584221,39.07637483820383,155 -77.55679124899605,39.07634810089878,155 -77.55672124438665,39.0763081038459,155 -77.55663509533335,39.07629495075815,155 -77.55656604580095,39.07628134930888,155 -77.55648013253986,39.07628138004181,155 -77.55641069497381,39.07625472989677,155 -77.5563413231686,39.07622809001835,155 -77.55628881487358,39.07618808301039,155 -77.55627034324068,39.07613473549092,155 -77.55626902997079,39.07608142639128,155 -77.55625056038696,39.07602814696794,155 -77.55624892622572,39.07596158217248,155 -77.55626476713635,39.07590835692002,155 -77.55628027003877,39.07584186638434,155 -77.55629583021005,39.0757754394947,155 -77.55634609757696,39.07572230348674,155 -77.55636181632673,39.07566925207633,155 -77.55642875428474,39.07560277891598,155 -77.55646229036702,39.07556299740847,155 -77.55651269595886,39.0755099719016,155 -77.55658028194432,39.07545687657122,155 -77.5566314337236,39.07543055889112,155 -77.55668241563703,39.075390805086,155 -77.55673357072662,39.07536430911301,155 -77.55678387555156,39.07529810137275,155 -77.5568174473014,39.07525834428458,155 -77.55686792661653,39.07520541200274,155 -77.55686664705212,39.07515252221706,155 -77.55684844580472,39.07511288286268,155 -77.5567957742127,39.07507327963388,155 -77.55672589231423,39.07503370396105,155 -77.55667358060947,39.07500733520837,155 -77.55662064005024,39.07494132521197,155 -77.55655195148549,39.0749414122245,155 -77.55649951910223,39.07490184787206,155 -77.55641319300734,39.07487549848385,155 -77.55632684873027,39.07484915644052,155 -77.55624075370906,39.07483601108177,155 -77.55617183849741,39.07482285971802,155 -77.5560514694919,39.07480971742964,155 -77.55598240010478,39.07479668437745,155 -77.55589604064231,39.07477020017173,155 -77.55582753612724,39.07475711005762,155 -77.55575901496999,39.07474390848704,155 -77.55567316011825,39.07471753753484,155 -77.55560464769755,39.07470433825589,155 -77.5555188103086,39.07467797219564,155 -77.55545030618596,39.07466477382714,155 -77.555381608763,39.07463843063523,155 -77.5553290625578,39.07457267500943,155 -77.55531070248981,39.07450696725684,155 -77.55530993618594,39.07446756381773,155 -77.55530840436215,39.0743888012859,155 -77.55530722295616,39.07433648136412,155 -77.55534031824057,39.07428403647764,155 -77.55535630665116,39.07421850025493,155 -77.55537250461977,39.07417909242871,155 -77.55542240781676,39.07411364905043,155 -77.55547217078366,39.07404822846397,155 -77.55550473634842,39.07398294884229,155 -77.55555488628198,39.07393060574787,155 -77.55562214068232,39.07387829279495,155 -77.55567232523597,39.07382599941986,155 -77.55573952732014,39.07377372880429,155 -77.55578966641808,39.07372147913539,155 -77.55583978721094,39.07366925276456,155 -77.55588988658107,39.07361704518656,155 -77.55592321670024,39.07357769648011,155 -77.55597329876106,39.07351260332241,155 -77.55600676726684,39.07347340789786,155 -77.55605707840394,39.07342114945977,155 -77.55610737601145,39.07336890637622,155 -77.5561061047263,39.07330378667933,155 -77.55607115357101,39.07326481764433,155 -77.55598442830618,39.07319998083042,155 -77.55593265246432,39.07317411409274,155 -77.55588089483916,39.07314825704496,155 -77.55579474963423,39.07310951189354,155 -77.55574351020073,39.0731096635514,155 -77.55565742592363,39.07307093899414,155 -77.55560574985154,39.07304511656746,155 -77.55548629796223,39.07303233390557,155 -77.55538406690653,39.07301944083893,155 -77.55528207758758,39.07301948729953,155 -77.55521370913112,39.07299352919826,155 -77.55514556263711,39.07296752396775,155 -77.55509458660858,39.07295445112477,155 -77.55504360121827,39.07294138784815,155 -77.55497524416407,39.07290239118549,155 -77.55488948887007,39.07283746493867,155 -77.55480424803199,39.07279845031442,155 -77.55473586690844,39.07275953448868,155 -77.55466724259874,39.07270774793564,155 -77.5546155978211,39.07265598693682,155 -77.55458092250109,39.07260425337304,155 -77.55457976198092,39.07252671478939,155 -77.55457898919335,39.07247505161335,155 -77.55462901487543,39.07242342664367,155 -77.55469597070484,39.0723849184908,155 -77.55476276173938,39.07233344801308,155 -77.55479593707673,39.07229483338106,155 -77.55486267215731,39.07224340643762,155 -77.55492980222827,39.07221776341309,155 -77.55497981088554,39.07217922120712,155 -77.55504677358421,39.07214060170174,155 -77.55513069509799,39.07210197170252,155 -77.55519805967755,39.07206293798806,155 -77.55524839443783,39.07202416823111,155 -77.55531595037247,39.07199810996094,155 -77.55540011934066,39.07194620931208,155 -77.55545044568863,39.07190735703264,155 -77.55550068625885,39.07185550305653,155 -77.55550037744239,39.07182965354041,155 -77.55555056210889,39.0717517521411,155 -77.55556681883583,39.07168703736127,155 -77.55554931490535,39.07164842134348,155 -77.55542934855572,39.07158477011069,155 -77.55541192178038,39.07157222618477,155 -77.55529200839877,39.07156081931382,155 -77.55515628505219,39.07154851172476,155 -77.55500360469219,39.07153631760238,155 -77.55491887346658,39.0715367289333,155 -77.5548850359215,39.07153693946091,155 -77.55476661400404,39.07153712865194,155 -77.55471592111967,39.07153713618887,155 -77.55463167299612,39.07153715122711,155 -77.55453069856165,39.07153686584964,155 -77.5544297791994,39.07153653740981,155 -77.55432877569508,39.07153620331349,155 -77.55424456669243,39.07153596533659,155 -77.55410938996964,39.07151012782357,155 -77.55402520946635,39.07150986550136,155 -77.55395726120402,39.07147129831761,155 -77.5538725492688,39.07143281951804,155 -77.5538039086486,39.07138257737283,155 -77.55376972445343,39.07134424210825,155 -77.55375232065258,39.07129303942025,155 -77.55373479904557,39.07122905102998,155 -77.55373392305137,39.07113942150175,155 -77.55373313805653,39.07107587658399,155 -77.55376607345218,39.07102491662597,155 -77.55379926443703,39.07097371165892,155 -77.55383247157444,39.07090954057595,155 -77.55389944683672,39.07087112469486,155 -77.5539663988976,39.07083273546056,155 -77.55403335762172,39.07080729782273,155 -77.55411716459635,39.07079480499262,155 -77.55420095553039,39.07078231363862,155 -77.55433509082692,39.07076996516957,155 -77.55440226143577,39.07077014827345,155 -77.55450298868958,39.07077040745927,155 -77.554536716063,39.07078310615764,155 -77.55465500627929,39.07079543584041,155 -77.55473966503243,39.0708077449432,155 -77.55482445138017,39.07083284994673,155 -77.55487530380617,39.07084536296107,155 -77.55496014073313,39.07087048310082,155 -77.55504500827577,39.07089561102387,155 -77.55514700703854,39.07093344601503,155 -77.55519832065234,39.07097154796959,155 -77.55521545558101,39.07098429304313,155 -77.55531859627676,39.07102097521855,155 -77.5553702699023,39.07105881698023,155 -77.55543828071437,39.07109725432625,155 -77.55550714393675,39.07113484742812,155 -77.55557665422771,39.07118489269062,155 -77.55561141778013,39.0712099621397,155 -77.55569805254453,39.07127297432253,155 -77.55573313660423,39.07132391149485,155 -77.55580288806098,39.07138712365032,155 -77.55583800451321,39.07143824081088,155 -77.55589070906437,39.0715017209363,155 -77.5559609008197,39.07157801838709,155 -77.5560131902724,39.07161593012305,155 -77.55601492247612,39.07170602944812,155 -77.55603335873306,39.07177020429403,155 -77.55603460363938,39.07183464228142,155 -77.55603562101246,39.07188623262279,155 -77.55603663313593,39.07193782498356,155 -77.55600324240181,39.0719898241242,155 -77.5560042488834,39.07204145405622,155 -77.55598798720708,39.07209327604536,155 -77.5559716261027,39.07214525120187,155 -77.55593806035752,39.0721974715915,155 -77.55590319124954,39.07225067229431,155 -77.55586805943051,39.0723039442614,155 -77.55581599328131,39.07235711014008,155 -77.55576530087652,39.07240934961801,155 -77.55571486574227,39.07244834908205,155 -77.555664197984,39.07247442090027,155 -77.55559670874968,39.0725134952491,155 -77.55554657152523,39.07256541586111,155 -77.55549590438231,39.07259146280942,155 -77.55541185951569,39.07265645475727,155 -77.55532764896437,39.07270850079024,155 -77.55527746185749,39.0727604270781,155 -77.55522682993978,39.07279952580038,155 -77.55516022212136,39.07285107373701,155 -77.55511052157735,39.07290267325191,155 -77.5550609847968,39.07296733059464,155 -77.55501108098993,39.07301906741039,155 -77.55496085137922,39.07305794195631,155 -77.55492775966786,39.0731098451159,155 -77.55487789328228,39.07317474398052,155 -77.55484476489633,39.07322669901443,155 -77.55482839092451,39.0732656921151,155 -77.55482957260377,39.07334375642169,155 -77.55483075459294,39.07342187164019,155 -77.55483134602017,39.07346094958224,155 -77.55486610047136,39.07351310771325,155 -77.55486711730974,39.073578290428,155 -77.55486831753598,39.07364337954512,155 -77.55490323044987,39.07369555338931,155 -77.55497231590066,39.0737476805452,155 -77.55502413181125,39.07378681979181,155 -77.55509301480798,39.07382596758243,155 -77.55516192539022,39.07386512955034,155 -77.5552479186014,39.0739043129053,155 -77.55531658610887,39.07393046009314,155 -77.55538498064371,39.07394354605657,155 -77.55547043341294,39.07396982081654,155 -77.55553901744193,39.07399604490806,155 -77.55564176749577,39.07402220427436,155 -77.55572727478068,39.0740352930631,155 -77.55579597947029,39.07406147733408,155 -77.55588150783807,39.07407464115769,155 -77.55593321567368,39.07410078324232,155 -77.55601929709903,39.07412686321145,155 -77.55608845879642,39.07415299560336,155 -77.55615753329666,39.0741790788908,155 -77.55622643796855,39.07419207366919,155 -77.55629563338684,39.07421819879288,155 -77.55636484053809,39.07424439718398,155 -77.55643428542864,39.07428363793039,155 -77.55648632401078,39.0743098240216,155 -77.55650439396037,39.07434917290075,155 -77.55654083156826,39.07444104347965,155 -77.5565422911905,39.07450676653995,155 -77.55652665938601,39.07457250722523,155 -77.55649326935391,39.07461198459819,155 -77.55646017672085,39.07466463008746,155 -77.55640981158584,39.07471735011936,155 -77.55635947695566,39.07477006531037,155 -77.5563088431776,39.0748096206542,155 -77.55627535803437,39.07484917809823,155 -77.55620779205255,39.07490195184459,155 -77.55615743142359,39.07495474122557,155 -77.55610687797507,39.07500752024524,155 -77.55603961960244,39.07507354040083,155 -77.55600639055382,39.07511327235885,155 -77.55593881138435,39.0751527592534,155 -77.555889037307,39.07521882570342,155 -77.55583906346399,39.07527160446204,155 -77.55580610917214,39.07532451240416,155 -77.55579019931548,39.07537745521055,155 -77.55577399316701,39.07541718130632,155 -77.55579249853695,39.07548343740412,155 -77.55582780150429,39.07553647270942,155 -77.55586315228631,39.07558957131254,155 -77.55591570211514,39.07564264523014,155 -77.55596797981246,39.07568246993564,155 -77.55602000217175,39.07570902277393,155 -77.55607203480623,39.07573558176132,155 -77.55615803095007,39.07574885939719,155 -77.55621008323531,39.07577542547744,155 -77.55629642102235,39.0758019929856,155 -77.55636563449104,39.07582856599011,155 -77.5564170991068,39.07582857598152,155 -77.55652065070318,39.0758552024905,155 -77.55657246232337,39.07586849828371,155 -77.55669379476723,39.07588172553369,155 -77.55676285101325,39.07588173301574,155 -77.55683224892287,39.07589507324652,155 -77.55691931917566,39.07592169087134,155 -77.55700609246189,39.07593501348379,155 -77.5570930892949,39.0759483597884,155 -77.55718013415721,39.07596169787798,155 -77.55721514766743,39.07597502249723,155 -77.55731975936946,39.07598836578579,155 -77.55735601605161,39.07604167397848,155 -77.55737479997821,39.07609499804567,155 -77.55737609157907,39.07614832816279,155 -77.55737738433579,39.07620167928646,155 -77.55737887762956,39.07626839517224,155 -77.55734492611509,39.07632171193588,155 -77.55729383420513,39.07636172524404,155 -77.55724287380123,39.07640177301934,155 -77.55719190146023,39.07644183078342,155 -77.55710658914859,39.07649523077759,155 -77.55703824775036,39.0765352955673,155 -77.55698722623782,39.0765753818653,155 -77.55695288058624,39.07658873275953,155 -77.55683406202506,39.07666906874635,155 -77.55678344189826,39.07670927213186,155 -77.55671549597244,39.07674951504881,155 -77.55664818078473,39.07680324161257,155 -77.5565807311543,39.07685689356682,155 -77.55656485581206,39.07691054258982,155 -77.55656669189953,39.07696445147731,155 -77.55660256780592,39.07701815611604,155 -77.55663861707005,39.0770719260388,155 -77.55669167818145,39.07712568873453,155 -77.55676171428208,39.07716599358002,155 -77.55681490276943,39.07721979994174,155 -77.55688497505091,39.07726014033345,155 -77.55693744676324,39.07728703332126,155 -77.55702440538036,39.07731389335921,155 -77.55707689879439,39.07734079519227,155 -77.5571294047335,39.07736770399222,155 -77.55721697999736,39.07740809612752,155 -77.55726986051943,39.07743510421623,155 -77.55730512541587,39.07744875581837,155 -77.55746265933334,39.07747584381153,155 -77.55753260917318,39.0774895754819,155 -77.55760281754431,39.07750310640253,155 -77.55769036149147,39.07751677744778,155 -77.55774351529888,39.0775438824566,155 -77.5578145756044,39.07758451513264,155 -77.5578512703793,39.07765214947332,155 -77.55785260727228,39.07769270482378,155 -77.55785468327635,39.07776036980896,155 -77.55785616665364,39.07781447018433,155 -77.55784011908938,39.07786851464544,155 -77.55778963378754,39.07794951551884,155 -77.55773875304561,39.07800363692049,155 -77.55770517712827,39.07805765914385,155 -77.55765410851937,39.07811177718364,155 -77.5575856451336,39.07816576587037,155 -77.55753481954953,39.07820670711883,155 -77.55744908731107,39.07823395696809,155 -77.55738137911905,39.07827494217238,155 -77.55732990295733,39.07828865639222,155 -77.55726156142937,39.07831600201826,155 -77.55717590941266,39.0783435474639,155 -77.55710707096449,39.0783572563939,155 -77.55702138899055,39.078384835618,155 -77.55695235512211,39.07841187968155,155 -77.55688179955318,39.07842488052266,155 -77.55679426856136,39.07845156582567,155 -77.556725208857,39.07846483101778,155 -77.55665581027621,39.07849228561925,155 -77.55658687593314,39.07853300338208,155 -77.55653541282106,39.07857372195614,155 -77.55646664227744,39.07861444266769,155 -77.55639791293177,39.07865529755893,155 -77.5563469358097,39.07870982941636,155 -77.55631338238329,39.07876440440054,155 -77.5562623402915,39.07881900170867,155 -77.55622841833495,39.07885995732045,155 -77.55617767013348,39.07892825868684,155 -77.55616149360196,39.07898295206095,155 -77.55612784545347,39.07903765618298,155 -77.55609418008744,39.0790923872545,155 -77.55606049805479,39.07914714337816,155 -77.55600932385619,39.0792019106908,155 -77.55597560525381,39.07925672208376,155 -77.55592408026418,39.0792978196591,155 -77.5558903891669,39.07935274113207,155 -77.5558903891669,39.07935274113207,116 @@ -372,7 +375,7 @@ accuracy_v: VAUnknown - -77.55592331007303,39.07947348482615,116 -77.55596431125197,39.07935950796164,116 -77.55583419944105,39.07927206371465,116 -77.55570922230844,39.07938762973074,116 -77.55577085203861,39.07948226858051,116 -77.55592331007303,39.07947348482615,116 + -77.55592331007303,39.07947348482615,116 -77.55596431125197,39.07935950796164,116 -77.55583419944105,39.07927206371465,116 -77.55570922230844,39.07938762973074,116 -77.55577085203861,39.07948226858051,116 -77.55592331007303,39.07947348482615,116 @@ -388,7 +391,7 @@ accuracy_v: VAUnknown - -77.55697242746655,39.07817292582563,136 -77.5584844204302,39.07827900063874,136 -77.55616277610642,39.07134681516164,136 -77.5545290907821,39.07012343732731,136 -77.55296745826514,39.07081688585026,136 -77.55697242746655,39.07817292582563,136 + -77.55697242746655,39.07817292582563,136 -77.5584844204302,39.07827900063874,136 -77.55616277610642,39.07134681516164,136 -77.5545290907821,39.07012343732731,136 -77.55296745826514,39.07081688585026,136 -77.55697242746655,39.07817292582563,136 @@ -403,7 +406,7 @@ accuracy_v: VAUnknown - -77.55360366279594,39.07257255806834,0 -77.55712030713669,39.07265392788114,0 -77.55597513314419,39.07001954666239,0 -77.55241786979286,39.06994688372433,0 -77.55360366279594,39.07257255806834,0 + -77.55360366279594,39.07257255806834,0 -77.55712030713669,39.07265392788114,0 -77.55597513314419,39.07001954666239,0 -77.55241786979286,39.06994688372433,0 -77.55360366279594,39.07257255806834,0 @@ -418,7 +421,7 @@ accuracy_v: VAUnknown - -77.55861572900298,39.07880102633224,0 -77.55641608057496,39.07837570546589,0 -77.5552382257237,39.0793750683633,0 -77.55620413703355,39.08051989616408,0 -77.55861572900298,39.07880102633224,0 + -77.55861572900298,39.07880102633224,0 -77.55641608057496,39.07837570546589,0 -77.5552382257237,39.0793750683633,0 -77.55620413703355,39.08051989616408,0 -77.55861572900298,39.07880102633224,0 diff --git a/pyproject.toml b/pyproject.toml index 2d3f8a7ff3..1ede3f9eba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,13 +3,13 @@ name = "monitoring" version = "0.1.0" description = "Monitoring framework to test UAS Service Suppliers" readme = "README.md" -requires-python = "==3.13.5" +requires-python = "==3.13.11" dependencies = [ "aiohttp", "arrow", - "basedpyright>=1.31.1", "bc-jsonpath-ng", "cryptography", + "deprecation", "faker", "flask", "flask-login", @@ -37,11 +37,8 @@ dependencies = [ "pykml", "pyopenssl", "pyproj", - "pytest", - "pytest-mock", "pyyaml", "requests", - "ruff", "s2sphere", "scipy", "shapely", @@ -52,20 +49,38 @@ dependencies = [ "uuid6", ] +[dependency-groups] +dev = [ + "basedpyright>=1.31.1", + "pytest", + "pytest-mock", + "ruff", + "types-lxml>=2025.8.25", +] + +[tool.uv] +default-groups = [] +exclude-newer = "P7D" + [tool.ruff] target-version = "py313" +extend-exclude = [ + "interfaces/*", + "schemas/ed318/*", + "monitoring/prober/output/*", + "monitoring/mock_uss/output/*", + "monitoring/uss_qualifier/output/*", +] +line-length = 88 +[tool.ruff.lint] # Default + isort + pyupgrade -lint.select = [ +select = [ "E4", "E7", "E9", "F", "I", "UP" ] -extend-exclude = [ - "interfaces/*", - "monitoring/prober/output/*", - "monitoring/mock_uss/output/*", - "monitoring/uss_qualifier/output/*", -] -line-length = 88 +# Explicitly ignore UP045 (Optional[Foo] -> Foo | None) +ignore = ["UP045"] +unfixable = ["UP045"] [tool.basedpyright] typeCheckingMode = "standard" @@ -75,6 +90,7 @@ exclude = [ "**/__pycache__", "**/.*", "interfaces/*", + "schemas/ed318/*", "monitoring/prober/output/*", "monitoring/mock_uss/output/*", "monitoring/uss_qualifier/output/*", diff --git a/schemas/ed318 b/schemas/ed318 new file mode 160000 index 0000000000..e98b292c56 --- /dev/null +++ b/schemas/ed318 @@ -0,0 +1 @@ +Subproject commit e98b292c5665a04989d62e32fd93829f161a89a9 diff --git a/schemas/manage_type_schemas.py b/schemas/manage_type_schemas.py index 29e531c15b..6768ec105a 100644 --- a/schemas/manage_type_schemas.py +++ b/schemas/manage_type_schemas.py @@ -4,7 +4,7 @@ import json import os import sys -from typing import get_args, get_origin, get_type_hints +from typing import Self, get_args, get_origin, get_type_hints import implicitdict import implicitdict.jsonschema @@ -24,7 +24,7 @@ from monitoring.uss_qualifier.resources.resource import Resource -class Action(str, enum.Enum): +class Action(enum.StrEnum): Check = "Check" Generate = "Generate" @@ -78,7 +78,8 @@ def _make_type_schemas( pending_types.extend(get_args(pending_type)) else: if ( - issubclass(pending_type, ImplicitDict) + pending_type != Self + and issubclass(pending_type, ImplicitDict) and fullname(pending_type) not in already_checked ): _make_type_schemas( @@ -175,7 +176,7 @@ def path_to(t_dest: type, t_src: type) -> str: changes = 0 # Check for non-current schemas that need to be removed - for dirpath, _, filenames in os.walk(os.path.join(repo_root, "schemas")): + for dirpath, _, filenames in os.walk(os.path.join(repo_root, "schemas/monitoring")): for filename in filenames: rel_filename = os.path.relpath( os.path.join(dirpath, filename), start=repo_root diff --git a/schemas/manage_type_schemas.sh b/schemas/manage_type_schemas.sh index 958e2b9427..9a867b803a 100755 --- a/schemas/manage_type_schemas.sh +++ b/schemas/manage_type_schemas.sh @@ -14,7 +14,7 @@ fi cd "${BASEDIR}/.." || exit 1 cd monitoring -make image +make image-dev cd .. action=${1:?The action must be specified as --check or --generate} @@ -24,5 +24,5 @@ docker run --name type_schema_manager \ --rm \ -u "$(id -u):$(id -g)" \ -v "$(pwd):/app" \ - interuss/monitoring \ + interuss/monitoring-dev \ uv run /app/schemas/manage_type_schemas.py "${action}" diff --git a/schemas/monitoring/monitorlib/fetch/Query.json b/schemas/monitoring/monitorlib/fetch/Query.json index 5674992f6f..647feee2a4 100644 --- a/schemas/monitoring/monitorlib/fetch/Query.json +++ b/schemas/monitoring/monitorlib/fetch/Query.json @@ -17,6 +17,7 @@ "query_type": { "description": "If specified, the recognized type of this query.", "enum": [ + "unknown", "astm.f3411.v19.dss.searchIdentificationServiceAreas", "astm.f3411.v22a.dss.searchIdentificationServiceAreas", "astm.f3411.v19.dss.getIdentificationServiceArea", @@ -79,6 +80,7 @@ "interuss.automated_testing.rid.v1.injection.createTest", "interuss.automated_testing.rid.v1.injection.deleteTest", "interuss.automated_testing.rid.v1.injection.UserNotifications", + "interuss.mock_uss.clock", "interuss.mock_uss.logging.interaction_logs", "interuss.mock_uss.locality.locality_get", "interuss.mock_uss.locality.locality_set", diff --git a/schemas/monitoring/monitorlib/fetch/RequestDescription.json b/schemas/monitoring/monitorlib/fetch/RequestDescription.json index 167ef825a4..2348ab67bc 100644 --- a/schemas/monitoring/monitorlib/fetch/RequestDescription.json +++ b/schemas/monitoring/monitorlib/fetch/RequestDescription.json @@ -7,6 +7,14 @@ "description": "Path to content that replaces the $ref", "type": "string" }, + "auth_dt": { + "description": "Amount of time required to obtain authorization before performing the primary query (de minimus or unknown by default).", + "format": "duration", + "type": [ + "string", + "null" + ] + }, "body": { "type": [ "string", diff --git a/schemas/monitoring/monitorlib/geo/Volume3D.json b/schemas/monitoring/monitorlib/geo/Volume3D.json deleted file mode 100644 index abbbc30120..0000000000 --- a/schemas/monitoring/monitorlib/geo/Volume3D.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/monitorlib/geo/Volume3D.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "monitoring.monitorlib.geo.Volume3D, as defined in monitoring/monitorlib/geo.py", - "properties": { - "$ref": { - "description": "Path to content that replaces the $ref", - "type": "string" - }, - "altitude_lower": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "Altitude.json" - } - ] - }, - "altitude_upper": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "Altitude.json" - } - ] - }, - "outline_circle": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "Circle.json" - } - ] - }, - "outline_polygon": { - "oneOf": [ - { - "type": "null" - }, - { - "$ref": "Polygon.json" - } - ] - } - }, - "type": "object" -} \ No newline at end of file diff --git a/schemas/monitoring/monitorlib/temporal/TestTime.json b/schemas/monitoring/monitorlib/temporal/TestTime.json index 9c2d533009..d92a3af82a 100644 --- a/schemas/monitoring/monitorlib/temporal/TestTime.json +++ b/schemas/monitoring/monitorlib/temporal/TestTime.json @@ -15,6 +15,13 @@ "null" ] }, + "name": { + "description": "If specified, update the TestTimeContext with the time computed for this TestTime as this name, which may then later be referenced by a different TestTime via time_during_test.", + "type": [ + "string", + "null" + ] + }, "next_day": { "description": "Time option field to use a timestamp equal to midnight beginning the next occurrence of any matching day following the specified reference timestamp.", "oneOf": [ @@ -50,11 +57,6 @@ }, "time_during_test": { "description": "Time option field to, if specified, use a timestamp relating to the current test run.", - "enum": [ - "StartOfTestRun", - "StartOfScenario", - "TimeOfEvaluation" - ], "type": [ "string", "null" diff --git a/schemas/monitoring/uss_qualifier/configurations/configuration/ArtifactsConfiguration.json b/schemas/monitoring/uss_qualifier/configurations/configuration/ArtifactsConfiguration.json index ba2f067fc9..27bf1339b8 100644 --- a/schemas/monitoring/uss_qualifier/configurations/configuration/ArtifactsConfiguration.json +++ b/schemas/monitoring/uss_qualifier/configurations/configuration/ArtifactsConfiguration.json @@ -70,6 +70,17 @@ "array", "null" ] + }, + "timing_report": { + "description": "If specified, configuration describing a desired report describing where and how time was spent during the test.", + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "TimingReportConfiguration.json" + } + ] } }, "type": "object" diff --git a/schemas/monitoring/uss_qualifier/configurations/configuration/ExecutionConfiguration.json b/schemas/monitoring/uss_qualifier/configurations/configuration/ExecutionConfiguration.json index 67b97af811..f06fce3833 100644 --- a/schemas/monitoring/uss_qualifier/configurations/configuration/ExecutionConfiguration.json +++ b/schemas/monitoring/uss_qualifier/configurations/configuration/ExecutionConfiguration.json @@ -7,6 +7,13 @@ "description": "Path to content that replaces the $ref", "type": "string" }, + "do_not_stop_fast_for_acceptable_findings": { + "description": "If true, make an exception for stop_fast above when the failed check is identified as an acceptable_finding in one of the tested_requirements artifact descriptions.", + "type": [ + "boolean", + "null" + ] + }, "include_action_when": { "description": "If specified, only execute test actions if they are selected by ANY of these conditions (and not selected by any of the `skip_when` conditions).", "items": { @@ -17,6 +24,13 @@ "null" ] }, + "scenarios_filter": { + "description": "Filter test scenarios by scenario type using a regex. If the filter regex does not match within the scenario type, the scenario is skipped. When empty, all scenarios are executed. Useful for targeted debugging. Overridden by --filter", + "type": [ + "string", + "null" + ] + }, "skip_action_when": { "description": "If specified, do not execute test actions if they are selected by ANY of these conditions.", "items": { @@ -27,6 +41,14 @@ "null" ] }, + "stop_after": { + "description": "If specified, stop the test run at the next earliest convenience (generally just after completion of the current test scenario) if it has been running at least this long.", + "format": "duration", + "type": [ + "string", + "null" + ] + }, "stop_fast": { "description": "If true, escalate the Severity of any failed check to Critical in order to end the test run early.", "type": [ diff --git a/schemas/monitoring/uss_qualifier/configurations/configuration/FullyQualifiedCheck.json b/schemas/monitoring/uss_qualifier/configurations/configuration/FullyQualifiedCheck.json new file mode 100644 index 0000000000..d2edbbbe75 --- /dev/null +++ b/schemas/monitoring/uss_qualifier/configurations/configuration/FullyQualifiedCheck.json @@ -0,0 +1,34 @@ +{ + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/configurations/configuration/FullyQualifiedCheck.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "monitoring.uss_qualifier.configurations.configuration.FullyQualifiedCheck, as defined in monitoring/uss_qualifier/configurations/configuration.py", + "properties": { + "$ref": { + "description": "Path to content that replaces the $ref", + "type": "string" + }, + "check_name": { + "description": "Name of the check, omitting the ' check' suffix. Must be an exact match to documentation; sensitive to case and spacing.", + "type": "string" + }, + "scenario_type": { + "description": "Scenario in which the check occurs.", + "type": "string" + }, + "test_case_name": { + "description": "Test case in which the check occurs, omitting the ' test case' suffix. Must be an exact match to documentation; sensitive to case and spacing.", + "type": "string" + }, + "test_step_name": { + "description": "Test step in which the check occurs, omitting the ' test step' suffix. Must be an exact match to documentation; sensitive to case and spacing.", + "type": "string" + } + }, + "required": [ + "check_name", + "scenario_type", + "test_case_name", + "test_step_name" + ], + "type": "object" +} \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/configurations/configuration/TestedRequirementsConfiguration.json b/schemas/monitoring/uss_qualifier/configurations/configuration/TestedRequirementsConfiguration.json index 23dee344c9..0d9770cb3e 100644 --- a/schemas/monitoring/uss_qualifier/configurations/configuration/TestedRequirementsConfiguration.json +++ b/schemas/monitoring/uss_qualifier/configurations/configuration/TestedRequirementsConfiguration.json @@ -7,6 +7,16 @@ "description": "Path to content that replaces the $ref", "type": "string" }, + "acceptable_findings": { + "description": "If any check identified in this field fails, ignore the failure when determining Tested Requirements outcomes.", + "items": { + "$ref": "FullyQualifiedCheck.json" + }, + "type": [ + "array", + "null" + ] + }, "aggregate_participants": { "additionalProperties": { "items": { diff --git a/schemas/monitoring/uss_qualifier/configurations/configuration/TimingReportConfiguration.json b/schemas/monitoring/uss_qualifier/configurations/configuration/TimingReportConfiguration.json new file mode 100644 index 0000000000..cb24ca28c9 --- /dev/null +++ b/schemas/monitoring/uss_qualifier/configurations/configuration/TimingReportConfiguration.json @@ -0,0 +1,16 @@ +{ + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/configurations/configuration/TimingReportConfiguration.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "monitoring.uss_qualifier.configurations.configuration.TimingReportConfiguration, as defined in monitoring/uss_qualifier/configurations/configuration.py", + "properties": { + "$ref": { + "description": "Path to content that replaces the $ref", + "type": "string" + }, + "percentage_of_time_to_break_down": { + "description": "Percentage of test time to break down in the timing report (smaller contributions are not reported)", + "type": "number" + } + }, + "type": "object" +} \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/reports/report/ActionGeneratorReport.json b/schemas/monitoring/uss_qualifier/reports/report/ActionGeneratorReport.json index 83de506305..1d2f8048e5 100644 --- a/schemas/monitoring/uss_qualifier/reports/report/ActionGeneratorReport.json +++ b/schemas/monitoring/uss_qualifier/reports/report/ActionGeneratorReport.json @@ -32,7 +32,7 @@ "type": "string" }, "successful": { - "description": "True iff all actions completed normally with no failed checks", + "description": "DEPRECATED. True iff all actions completed normally with no failed checks", "type": "boolean" } }, diff --git a/schemas/monitoring/uss_qualifier/reports/report/IntentionalDelay.json b/schemas/monitoring/uss_qualifier/reports/report/IntentionalDelay.json new file mode 100644 index 0000000000..a3c629f2e4 --- /dev/null +++ b/schemas/monitoring/uss_qualifier/reports/report/IntentionalDelay.json @@ -0,0 +1,31 @@ +{ + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/reports/report/IntentionalDelay.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "monitoring.uss_qualifier.reports.report.IntentionalDelay, as defined in monitoring/uss_qualifier/reports/report.py", + "properties": { + "$ref": { + "description": "Path to content that replaces the $ref", + "type": "string" + }, + "duration": { + "description": "Duration of the delay", + "format": "duration", + "type": "string" + }, + "reason": { + "description": "Reason given for this delay", + "type": "string" + }, + "start_time": { + "description": "When the delay started", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "duration", + "reason", + "start_time" + ], + "type": "object" +} \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/reports/report/TestScenarioReport.json b/schemas/monitoring/uss_qualifier/reports/report/TestScenarioReport.json index 3d30dd679f..074e8c7137 100644 --- a/schemas/monitoring/uss_qualifier/reports/report/TestScenarioReport.json +++ b/schemas/monitoring/uss_qualifier/reports/report/TestScenarioReport.json @@ -25,6 +25,16 @@ } ] }, + "delays": { + "description": "Delays intentionally introduced during this test scenario, but not during any test step", + "items": { + "$ref": "IntentionalDelay.json" + }, + "type": [ + "array", + "null" + ] + }, "documentation_url": { "description": "URL at which this test scenario is described", "type": "string" @@ -94,7 +104,7 @@ "type": "string" }, "successful": { - "description": "True iff test scenario completed normally with no failed checks", + "description": "DEPRECATED. True iff test scenario completed normally with no failed checks.", "type": "boolean" } }, diff --git a/schemas/monitoring/uss_qualifier/reports/report/TestStepReport.json b/schemas/monitoring/uss_qualifier/reports/report/TestStepReport.json index 7f48438c72..e8ddd4515c 100644 --- a/schemas/monitoring/uss_qualifier/reports/report/TestStepReport.json +++ b/schemas/monitoring/uss_qualifier/reports/report/TestStepReport.json @@ -7,6 +7,16 @@ "description": "Path to content that replaces the $ref", "type": "string" }, + "delays": { + "description": "Delays intentionally introduced during this test step", + "items": { + "$ref": "IntentionalDelay.json" + }, + "type": [ + "array", + "null" + ] + }, "documentation_url": { "description": "URL at which this test step is described", "type": "string" diff --git a/schemas/monitoring/uss_qualifier/reports/report/TestSuiteReport.json b/schemas/monitoring/uss_qualifier/reports/report/TestSuiteReport.json index 9e27dcd742..449e40fe3c 100644 --- a/schemas/monitoring/uss_qualifier/reports/report/TestSuiteReport.json +++ b/schemas/monitoring/uss_qualifier/reports/report/TestSuiteReport.json @@ -43,7 +43,7 @@ "type": "string" }, "successful": { - "description": "True iff test suite completed normally with no failed checks", + "description": "DEPRECATED. True iff test suite completed normally with no failed checks", "type": "boolean" }, "suite_type": { diff --git a/schemas/monitoring/uss_qualifier/resources/astm/f3548/v21/dss/DSSInstanceSpecification.json b/schemas/monitoring/uss_qualifier/resources/astm/f3548/v21/dss/DSSInstanceSpecification.json index a1ae7aaa8e..6712a3a77c 100644 --- a/schemas/monitoring/uss_qualifier/resources/astm/f3548/v21/dss/DSSInstanceSpecification.json +++ b/schemas/monitoring/uss_qualifier/resources/astm/f3548/v21/dss/DSSInstanceSpecification.json @@ -22,6 +22,13 @@ "null" ] }, + "timeout_seconds": { + "description": "If specified, number of seconds to allow before timing out requests to this DSS instance.", + "type": [ + "number", + "null" + ] + }, "user_participant_ids": { "description": "IDs of any participants using this DSS instance, apart from the USS responsible for this DSS instance.", "items": { diff --git a/schemas/monitoring/uss_qualifier/resources/eurocae/ed318/source_document/SourceDocumentSpecification.json b/schemas/monitoring/uss_qualifier/resources/eurocae/ed318/source_document/SourceDocumentSpecification.json new file mode 100644 index 0000000000..ce138f657d --- /dev/null +++ b/schemas/monitoring/uss_qualifier/resources/eurocae/ed318/source_document/SourceDocumentSpecification.json @@ -0,0 +1,19 @@ +{ + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/resources/eurocae/ed318/source_document/SourceDocumentSpecification.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "monitoring.uss_qualifier.resources.eurocae.ed318.source_document.SourceDocumentSpecification, as defined in monitoring/uss_qualifier/resources/eurocae/ed318/source_document.py", + "properties": { + "$ref": { + "description": "Path to content that replaces the $ref", + "type": "string" + }, + "url": { + "description": "Url of the ED-318 document to verify", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" +} \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/resources/interuss/datastore/datastore/DatastoreDBNodeSpecification.json b/schemas/monitoring/uss_qualifier/resources/interuss/datastore/datastore/DatastoreDBNodeSpecification.json index 3628ad0a3e..8c76901227 100644 --- a/schemas/monitoring/uss_qualifier/resources/interuss/datastore/datastore/DatastoreDBNodeSpecification.json +++ b/schemas/monitoring/uss_qualifier/resources/interuss/datastore/datastore/DatastoreDBNodeSpecification.json @@ -11,6 +11,10 @@ "description": "Host where the DatastoreDB node is reachable.", "type": "string" }, + "is_yugabyte": { + "description": "True if DatastoreDB node is a YugabyteDB node.", + "type": "boolean" + }, "participant_id": { "description": "ID of the USS responsible for this DatastoreDB node", "type": "string" diff --git a/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableGenerationSpecification.json b/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableGenerationSpecification.json new file mode 100644 index 0000000000..ea7d134118 --- /dev/null +++ b/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableGenerationSpecification.json @@ -0,0 +1,35 @@ +{ + "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableGenerationSpecification.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "monitoring.uss_qualifier.resources.interuss.geospatial_map.feature_check_table.FeatureCheckTableGenerationSpecification, as defined in monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table.py", + "properties": { + "$ref": { + "description": "Path to content that replaces the $ref", + "type": "string" + }, + "dict_resources": { + "additionalProperties": { + "$ref": "../../../files/ExternalFile.json" + }, + "description": "External dict-like content (.json, .yaml, .jsonnet) to load and make available to the script.\nKey defines the name of the resource accessible to the script.", + "properties": { + "$ref": { + "description": "Path to content that replaces the $ref", + "type": "string" + } + }, + "type": [ + "object", + "null" + ] + }, + "jsonnet_script": { + "$ref": "../../../files/ExternalFile.json", + "description": "Source of Jsonnet \"script\" that produces a FeatureCheckTable.\n\nThis converter may access resources with std.extVars(\"resource_name\"). Also, std.extVars(\"now\") returns the current\ndatetime as an ISO string. The return value of the Jsonnet must be an object following the FeatureCheckTable\nschema. `import` is not allowed. The following additional functions are available:\n * std.native(\"timestamp_of\")(s: str) -> float\n * s: Datetime string\n * Returns: Seconds past epoch" + } + }, + "required": [ + "jsonnet_script" + ], + "type": "object" +} \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableSpecification.json b/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableSpecification.json index 051a9dd672..6718b388f4 100644 --- a/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableSpecification.json +++ b/schemas/monitoring/uss_qualifier/resources/interuss/geospatial_map/feature_check_table/FeatureCheckTableSpecification.json @@ -7,12 +7,28 @@ "description": "Path to content that replaces the $ref", "type": "string" }, + "generate_at_runtime": { + "description": "Generate feature check table at runtime", + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "FeatureCheckTableGenerationSpecification.json" + } + ] + }, "table": { - "$ref": "../definitions/FeatureCheckTable.json" + "description": "Statically-defined feature check table", + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "../definitions/FeatureCheckTable.json" + } + ] } }, - "required": [ - "table" - ], "type": "object" } \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json b/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json index 2be17a27ec..36c3d4b25f 100644 --- a/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json +++ b/schemas/monitoring/uss_qualifier/resources/interuss/uss_identification/USSIdentifiers.json @@ -17,8 +17,8 @@ "null" ] }, - "astm_url_regexes": { - "description": "If a URL to an ASTM (F3411, F3548, etc) endpoint matches one of these regular expressions, assume the participant is responsible for that server", + "server_url_regexes": { + "description": "If a URL to an endpoint matches one of these regular expressions, assume the participant is responsible for that server", "items": { "type": "string" }, diff --git a/schemas/monitoring/uss_qualifier/resources/netrid/service_area/ServiceAreaSpecification.json b/schemas/monitoring/uss_qualifier/resources/netrid/service_area/ServiceAreaSpecification.json index 3b83058ed6..01d20d151f 100644 --- a/schemas/monitoring/uss_qualifier/resources/netrid/service_area/ServiceAreaSpecification.json +++ b/schemas/monitoring/uss_qualifier/resources/netrid/service_area/ServiceAreaSpecification.json @@ -7,47 +7,13 @@ "description": "Path to content that replaces the $ref", "type": "string" }, - "altitude_max": { - "description": "Upper altitude bound of service area, meters above WGS84 ellipsoid", - "type": "number" - }, - "altitude_min": { - "description": "Lower altitude bound of service area, meters above WGS84 ellipsoid", - "type": "number" - }, "base_url": { "description": "Base URL to use for the Identification Service Area.\n\nNote that this is the API base URL, not the flights URL (as specified in F3411-19).\n\nThis URL will probably not identify a real resource in tests.", "type": "string" - }, - "footprint": { - "description": "2D outline of service area", - "items": { - "$ref": "../../../../monitorlib/geo/LatLngPoint.json" - }, - "type": "array" - }, - "reference_time": { - "description": "Reference time used to adjust start and end times at runtime", - "format": "date-time", - "type": "string" - }, - "time_end": { - "description": "End time of service area (relative to reference_time)", - "format": "date-time", - "type": "string" - }, - "time_start": { - "description": "Start time of service area (relative to reference_time)", - "format": "date-time", - "type": "string" } }, "required": [ - "base_url", - "footprint", - "reference_time", - "time_end", - "time_start" + "base_url" ], "type": "object" } \ No newline at end of file diff --git a/schemas/monitoring/uss_qualifier/resources/planning_area/PlanningAreaSpecification.json b/schemas/monitoring/uss_qualifier/resources/planning_area/PlanningAreaSpecification.json index 4a6b1946eb..d48840b772 100644 --- a/schemas/monitoring/uss_qualifier/resources/planning_area/PlanningAreaSpecification.json +++ b/schemas/monitoring/uss_qualifier/resources/planning_area/PlanningAreaSpecification.json @@ -1,7 +1,7 @@ { "$id": "https://github.com/interuss/monitoring/blob/main/schemas/monitoring/uss_qualifier/resources/planning_area/PlanningAreaSpecification.json", "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "Specifies a 2D or 3D volume along with USS related information to create test resources that require them.\n\nmonitoring.uss_qualifier.resources.planning_area.PlanningAreaSpecification, as defined in monitoring/uss_qualifier/resources/planning_area.py", + "description": "Specifies a 2D, 3D or 4D volume to be used in flight planning activities.\n- The base_url is directly declared in this specification\n- The volume itself is declared separately and passed as a dependency: see resource.VolumeResource for details.\n\nmonitoring.uss_qualifier.resources.planning_area.PlanningAreaSpecification, as defined in monitoring/uss_qualifier/resources/planning_area.py", "properties": { "$ref": { "description": "Path to content that replaces the $ref", @@ -13,14 +13,7 @@ "string", "null" ] - }, - "volume": { - "$ref": "../../../monitorlib/geo/Volume3D.json", - "description": "3D volume of service area" } }, - "required": [ - "volume" - ], "type": "object" } \ No newline at end of file diff --git a/scripts/git/commit.sh b/scripts/git/commit.sh index 5d09657381..de3af8b644 100755 --- a/scripts/git/commit.sh +++ b/scripts/git/commit.sh @@ -1,5 +1,7 @@ #!/usr/bin/env sh +set -e + COMMIT=$(git rev-parse --short HEAD) if test -n "$(git status -s)"; then diff --git a/scripts/git/upstream_owner.sh b/scripts/git/upstream_owner.sh index 203735caa9..50ca15f52d 100755 --- a/scripts/git/upstream_owner.sh +++ b/scripts/git/upstream_owner.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # This script attempts to print the organization of the upstream repository. # The following strategies will be used to determine the organization name: diff --git a/scripts/git/version.sh b/scripts/git/version.sh index 61fc0f8b2f..68dbbbc221 100755 --- a/scripts/git/version.sh +++ b/scripts/git/version.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +set -eo pipefail + +set -o xtrace + # This script prints the current version of a component in the repository based on the tags # of the upstream repository (remote origin) matching the following convention: # owner/component/version. Examples of values: diff --git a/test/repo_hygiene/pyproject.toml b/test/repo_hygiene/pyproject.toml index bbe708fbb7..430c85cdcf 100644 --- a/test/repo_hygiene/pyproject.toml +++ b/test/repo_hygiene/pyproject.toml @@ -6,3 +6,6 @@ requires-python = "==3.13.*" dependencies = [ "marko", ] + +[tool.uv] +exclude-newer = "P7D" diff --git a/test/repo_hygiene/repo_hygiene.sh b/test/repo_hygiene/repo_hygiene.sh index af37c47d54..1a231b9a17 100755 --- a/test/repo_hygiene/repo_hygiene.sh +++ b/test/repo_hygiene/repo_hygiene.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + OS=$(uname) if [[ $OS == "Darwin" ]]; then # OSX uses BSD readlink diff --git a/test/repo_hygiene/uv.lock b/test/repo_hygiene/uv.lock index 9bae9bcfc7..62b9c82b63 100644 --- a/test/repo_hygiene/uv.lock +++ b/test/repo_hygiene/uv.lock @@ -1,14 +1,18 @@ version = 1 -revision = 2 +revision = 3 requires-python = "==3.13.*" +[options] +exclude-newer = "2026-05-05T08:22:04.2443125Z" +exclude-newer-span = "P7D" + [[package]] name = "marko" -version = "2.1.4" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/dc/c8cadbd83de1b38d95a48568b445a5553005ebdd32e00a333ca940113db4/marko-2.1.4.tar.gz", hash = "sha256:dd7d66f3706732bf8f994790e674649a4fd0a6c67f16b80246f30de8e16a1eac", size = 142795, upload-time = "2025-06-13T03:25:50.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/2f/050b6d485f052ddf17d76a41f9334d6fb2a8a85df35347a12d97ed3bc5c1/marko-2.2.2.tar.gz", hash = "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", size = 143641, upload-time = "2026-01-05T11:04:41.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/66/49e3691d14898fb6e34ccb337c7677dfb7e18269ed170f12e4b85315eae6/marko-2.1.4-py3-none-any.whl", hash = "sha256:81c2b9f570ca485bc356678d9ba1a1b3eb78b4a315d01f3ded25442fdc796990", size = 42186, upload-time = "2025-06-13T03:25:49.858Z" }, + { url = "https://files.pythonhosted.org/packages/83/f8/36d79bac5701e6786f9880c61bbe57574760a13c1af84ab71e5ed21faecc/marko-2.2.2-py3-none-any.whl", hash = "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e", size = 42701, upload-time = "2026-01-05T11:04:40.75Z" }, ] [[package]] diff --git a/uv.lock b/uv.lock index bca514698a..e6dd9520a9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,15 @@ version = 1 revision = 3 -requires-python = "==3.13.5" +requires-python = "==3.13.11" +resolution-markers = [ + "sys_platform == 'win32'", + "sys_platform == 'emscripten'", + "sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P7D" [[package]] name = "aiohappyeyeballs" @@ -13,7 +22,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.15" +version = "3.13.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -24,25 +33,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, - { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, - { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, - { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, - { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, - { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, - { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" }, + { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" }, + { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" }, ] [[package]] @@ -59,36 +68,36 @@ wheels = [ [[package]] name = "arrow" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, - { name = "types-python-dateutil" }, + { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] [[package]] name = "attrs" -version = "25.3.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] name = "basedpyright" -version = "1.31.4" +version = "1.39.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodejs-wheel-binaries" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/53/570b03ec0445a9b2cc69788482c1d12902a9b88a9b159e449c4c537c4e3a/basedpyright-1.31.4.tar.gz", hash = "sha256:2450deb16530f7c88c1a7da04530a079f9b0b18ae1c71cb6f812825b3b82d0b1", size = 22494467, upload-time = "2025-09-03T13:05:55.817Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/19/5a5b9b9197973da732638957be3a65cf514d2f5a4964eeedbf33b6c65bbd/basedpyright-1.39.3.tar.gz", hash = "sha256:2f794e6b5f4260fb89f614ca6cd23c6f305373bb6b50c4ed7794ff2ae647fb14", size = 25503187, upload-time = "2026-04-20T22:14:47.424Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/40/d1047a5addcade9291685d06ef42a63c1347517018bafd82747af9da0294/basedpyright-1.31.4-py3-none-any.whl", hash = "sha256:055e4a38024bd653be12d6216c1cfdbee49a1096d342b4d5f5b4560f7714b6fc", size = 11731440, upload-time = "2025-09-03T13:05:52.308Z" }, + { url = "https://files.pythonhosted.org/packages/54/5c/f950c1239ad26f3bb453e665428a2cf1893995de725a5eb0b64a2520b366/basedpyright-1.39.3-py3-none-any.whl", hash = "sha256:aba760dc83307727554f936d6b4381caa14482f30dbc2173167710e217c1f7ab", size = 12419181, upload-time = "2026-04-20T22:14:51.975Z" }, ] [[package]] @@ -104,6 +113,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/88/27b4b4374e96bfd6b8e49cdde4e5aaa61eb9046b8ead9b18dd2d3ad6a154/bc_jsonpath_ng-1.6.1-py3-none-any.whl", hash = "sha256:2c85bb1d194376808fe1fc49558dd484e39024b15c719995e22de811e6ba4dc8", size = 29783, upload-time = "2023-11-26T13:29:28.789Z" }, ] +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + [[package]] name = "bidict" version = "0.23.1" @@ -124,108 +146,103 @@ wheels = [ [[package]] name = "brotli" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, - { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, - { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" }, + { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" }, + { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" }, + { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" }, + { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" }, + { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" }, + { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" }, ] [[package]] name = "build" -version = "1.3.0" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "os_name == 'nt'" }, { name = "packaging" }, { name = "pyproject-hooks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/1c/23e33405a7c9eac261dff640926b8b5adaed6a6eb3e1767d441ed611d0c0/build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", size = 48544, upload-time = "2025-08-01T21:27:09.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, -] - -[[package]] -name = "cachetools" -version = "5.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, ] [[package]] name = "certifi" -version = "2025.8.3" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" -version = "8.2.1" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] [[package]] @@ -239,46 +256,59 @@ wheels = [ [[package]] name = "configargparse" -version = "1.7.1" +version = "1.7.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/4d/6c9ef746dfcc2a32e26f3860bb4a011c008c392b83eabdfb598d1a8bbe5d/configargparse-1.7.1.tar.gz", hash = "sha256:79c2ddae836a1e5914b71d58e4b9adbd9f7779d4e6351a637b7d2d9b6c46d3d9", size = 43958, upload-time = "2025-05-23T14:26:17.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/0b/30328302903c55218ffc5199646d0e9d28348ff26c02ba77b2ffc58d294a/configargparse-1.7.5.tar.gz", hash = "sha256:e3f9a7bb6be34d66b2e3c4a2f58e3045f8dfae47b0dc039f87bcfaa0f193fb0f", size = 53548, upload-time = "2026-03-11T02:19:38.144Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607, upload-time = "2025-05-23T14:26:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/fe/19/3ba5e1b0bcc7b91aeab6c258afd70e4907d220fed3972febe38feb40db30/configargparse-1.7.5-py3-none-any.whl", hash = "sha256:1e63fdffedf94da9cd435fc13a1cd24777e76879dd2343912c1f871d4ac8c592", size = 27692, upload-time = "2026-03-11T02:19:36.442Z" }, ] [[package]] name = "cryptography" -version = "45.0.6" +version = "48.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" }, - { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" }, - { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" }, - { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" }, - { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" }, - { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" }, - { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" }, - { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" }, - { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" }, - { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" }, - { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" }, - { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" }, - { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" }, - { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" }, - { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, +] + +[[package]] +name = "cssselect" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/2e/cdfd8b01c37cbf4f9482eefd455853a3cf9c995029a46acd31dfaa9c1dd6/cssselect-1.4.0.tar.gz", hash = "sha256:fdaf0a1425e17dfe8c5cf66191d211b357cf7872ae8afc4c6762ddd8ac47fc92", size = 40589, upload-time = "2026-01-29T07:00:26.701Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/0c/7bb51e3acfafd16c48875bf3db03607674df16f5b6ef8d056586af7e2b8b/cssselect-1.4.0-py3-none-any.whl", hash = "sha256:c0ec5c0191c8ee39fcc8afc1540331d8b55b0183478c50e9c8a79d44dbceb1d8", size = 18540, upload-time = "2026-01-29T07:00:24.994Z" }, ] [[package]] @@ -290,6 +320,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, +] + [[package]] name = "docker" version = "7.1.0" @@ -315,19 +357,19 @@ wheels = [ [[package]] name = "faker" -version = "37.5.3" +version = "40.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "tzdata" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/5d/7797a74e8e31fa227f0303239802c5f09b6722bdb6638359e7b6c8f30004/faker-37.5.3.tar.gz", hash = "sha256:8315d8ff4d6f4f588bd42ffe63abd599886c785073e26a44707e10eeba5713dc", size = 1907147, upload-time = "2025-07-30T15:52:19.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/13/6741787bd91c4109c7bed047d68273965cd52ce8a5f773c471b949334b6d/faker-40.15.0.tar.gz", hash = "sha256:20f3a6ec8c266b74d4c554e34118b21c3c2056c0b4a519d15c8decb3a4e6e795", size = 1967447, upload-time = "2026-04-17T20:05:27.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/bf/d06dd96e7afa72069dbdd26ed0853b5e8bd7941e2c0819a9b21d6e6fc052/faker-37.5.3-py3-none-any.whl", hash = "sha256:386fe9d5e6132a915984bf887fcebcc72d6366a25dd5952905b31b141a17016d", size = 1949261, upload-time = "2025-07-30T15:52:17.729Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a7/a600f8f30d4505e89166de51dd121bd540ab8e560e8cf0901de00a81de8c/faker-40.15.0-py3-none-any.whl", hash = "sha256:71ab3c3370da9d2205ab74ffb0fd51273063ad562b3a3bb69d0026a20923e318", size = 2004447, upload-time = "2026-04-17T20:05:25.437Z" }, ] [[package]] name = "flask" -version = "3.1.1" +version = "3.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, @@ -337,22 +379,22 @@ dependencies = [ { name = "markupsafe" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440, upload-time = "2025-05-13T15:01:17.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, ] [[package]] name = "flask-cors" -version = "6.0.1" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/37/bcfa6c7d5eec777c4c7cf45ce6b27631cebe5230caf88d85eadd63edd37a/flask_cors-6.0.1.tar.gz", hash = "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db", size = 13463, upload-time = "2025-06-11T01:32:08.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/74/0fc0fa68d62f21daef41017dafab19ef4b36551521260987eb3a5394c7ba/flask_cors-6.0.2.tar.gz", hash = "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423", size = 13472, upload-time = "2025-12-12T20:31:42.861Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/f8/01bf35a3afd734345528f98d0353f2a978a476528ad4d7e78b70c4d149dd/flask_cors-6.0.1-py3-none-any.whl", hash = "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c", size = 13244, upload-time = "2025-06-11T01:32:07.352Z" }, + { url = "https://files.pythonhosted.org/packages/4f/af/72ad54402e599152de6d067324c46fe6a4f531c7c65baf7e96c63db55eaf/flask_cors-6.0.2-py3-none-any.whl", hash = "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a", size = 13257, upload-time = "2025-12-12T20:31:41.3Z" }, ] [[package]] @@ -370,45 +412,43 @@ wheels = [ [[package]] name = "frozenlist" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, - { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, - { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, - { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, - { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, - { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, - { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, - { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, - { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, - { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, - { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, - { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, - { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, - { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, - { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, - { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, - { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, - { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, - { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] [[package]] @@ -431,7 +471,7 @@ wheels = [ [[package]] name = "gevent" -version = "25.5.1" +version = "25.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, @@ -439,21 +479,21 @@ dependencies = [ { name = "zope-event" }, { name = "zope-interface" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/58/267e8160aea00ab00acd2de97197eecfe307064a376fb5c892870a8a6159/gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61", size = 6388207, upload-time = "2025-05-12T12:57:59.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/48/b3ef2673ffb940f980966694e40d6d32560f3ffa284ecaeb5ea3a90a6d3f/gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd", size = 5059025, upload-time = "2025-09-17T16:15:34.528Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/25/2162b38d7b48e08865db6772d632bd1648136ce2bb50e340565e45607cad/gevent-25.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc", size = 2928044, upload-time = "2025-05-12T11:11:36.33Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e0/dbd597a964ed00176da122ea759bf2a6c1504f1e9f08e185379f92dc355f/gevent-25.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922", size = 1788751, upload-time = "2025-05-12T11:52:32.643Z" }, - { url = "https://files.pythonhosted.org/packages/f1/74/960cc4cf4c9c90eafbe0efc238cdf588862e8e278d0b8c0d15a0da4ed480/gevent-25.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d", size = 1869766, upload-time = "2025-05-12T11:54:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/56/78/fa84b1c7db79b156929685db09a7c18c3127361dca18a09e998e98118506/gevent-25.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d", size = 1835358, upload-time = "2025-05-12T12:00:06.794Z" }, - { url = "https://files.pythonhosted.org/packages/00/5c/bfefe3822bbca5b83bfad256c82251b3f5be13d52d14e17a786847b9b625/gevent-25.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e", size = 2073071, upload-time = "2025-05-12T11:33:04.2Z" }, - { url = "https://files.pythonhosted.org/packages/20/e4/08a77a3839a37db96393dea952e992d5846a881b887986dde62ead6b48a1/gevent-25.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce", size = 1809805, upload-time = "2025-05-12T12:00:00.537Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ac/28848348f790c1283df74b0fc0a554271d0606676470f848eccf84eae42a/gevent-25.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276", size = 2138305, upload-time = "2025-05-12T11:40:56.566Z" }, - { url = "https://files.pythonhosted.org/packages/52/9e/0e9e40facd2d714bfb00f71fc6dacaacc82c24c1c2e097bf6461e00dec9f/gevent-25.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896", size = 1637444, upload-time = "2025-05-12T12:17:45.995Z" }, + { url = "https://files.pythonhosted.org/packages/5a/77/b97f086388f87f8ad3e01364f845004aef0123d4430241c7c9b1f9bde742/gevent-25.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:4f84591d13845ee31c13f44bdf6bd6c3dbf385b5af98b2f25ec328213775f2ed", size = 2973739, upload-time = "2025-09-17T14:53:30.279Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/9d5f204ead343e5b27bbb2fedaec7cd0009d50696b2266f590ae845d0331/gevent-25.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9cdbb24c276a2d0110ad5c978e49daf620b153719ac8a548ce1250a7eb1b9245", size = 1809165, upload-time = "2025-09-17T15:41:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/10/3e/791d1bf1eb47748606d5f2c2aa66571f474d63e0176228b1f1fd7b77ab37/gevent-25.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:88b6c07169468af631dcf0fdd3658f9246d6822cc51461d43f7c44f28b0abb82", size = 1890638, upload-time = "2025-09-17T15:49:02.45Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5c/9ad0229b2b4d81249ca41e4f91dd8057deaa0da6d4fbe40bf13cdc5f7a47/gevent-25.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b7bb0e29a7b3e6ca9bed2394aa820244069982c36dc30b70eb1004dd67851a48", size = 1857118, upload-time = "2025-09-17T15:49:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/49/2a/3010ed6c44179a3a5c5c152e6de43a30ff8bc2c8de3115ad8733533a018f/gevent-25.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2951bb070c0ee37b632ac9134e4fdaad70d2e660c931bb792983a0837fe5b7d7", size = 2111598, upload-time = "2025-09-17T15:15:15.226Z" }, + { url = "https://files.pythonhosted.org/packages/08/75/6bbe57c19a7aa4527cc0f9afcdf5a5f2aed2603b08aadbccb5bf7f607ff4/gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47", size = 1829059, upload-time = "2025-09-17T15:52:42.596Z" }, + { url = "https://files.pythonhosted.org/packages/06/6e/19a9bee9092be45679cb69e4dd2e0bf5f897b7140b4b39c57cc123d24829/gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117", size = 2173529, upload-time = "2025-09-17T15:24:13.897Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4f/50de9afd879440e25737e63f5ba6ee764b75a3abe17376496ab57f432546/gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa", size = 1681518, upload-time = "2025-09-17T19:39:47.488Z" }, ] [[package]] name = "geventhttpclient" -version = "2.3.4" +version = "2.3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "brotli" }, @@ -461,38 +501,32 @@ dependencies = [ { name = "gevent" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/19/1ca8de73dcc0596d3df01be299e940d7fc3bccbeb6f62bb8dd2d427a3a50/geventhttpclient-2.3.4.tar.gz", hash = "sha256:1749f75810435a001fc6d4d7526c92cf02b39b30ab6217a886102f941c874222", size = 83545, upload-time = "2025-06-11T13:18:14.144Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ff/cb3db11fca4223b2753ae170d1a09c9d32bfbfa3e8d4a6181324db686830/geventhttpclient-2.3.9.tar.gz", hash = "sha256:16807578dc4a175e8d97e6e39d65a10b04b5237a8c55f7a5ef39044e869baeb8", size = 84353, upload-time = "2026-03-03T08:09:03.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/ad/132fddde6e2dca46d6a86316962437acd2bfaeb264db4e0fae83c529eb04/geventhttpclient-2.3.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:be64c5583884c407fc748dedbcb083475d5b138afb23c6bc0836cbad228402cc", size = 71967, upload-time = "2025-06-11T13:17:22.121Z" }, - { url = "https://files.pythonhosted.org/packages/f4/34/5e77d9a31d93409a8519cf573843288565272ae5a016be9c9293f56c50a1/geventhttpclient-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:15b2567137734183efda18e4d6245b18772e648b6a25adea0eba8b3a8b0d17e8", size = 52632, upload-time = "2025-06-11T13:17:23.016Z" }, - { url = "https://files.pythonhosted.org/packages/47/d2/cf0dbc333304700e68cee9347f654b56e8b0f93a341b8b0d027ee96800d6/geventhttpclient-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a4bca1151b8cd207eef6d5cb3c720c562b2aa7293cf113a68874e235cfa19c31", size = 51980, upload-time = "2025-06-11T13:17:23.933Z" }, - { url = "https://files.pythonhosted.org/packages/27/6e/049e685fc43e2e966c83f24b3187f6a6736103f0fc51118140f4ca1793d4/geventhttpclient-2.3.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8a681433e2f3d4b326d8b36b3e05b787b2c6dd2a5660a4a12527622278bf02ed", size = 114998, upload-time = "2025-08-24T12:16:54.72Z" }, - { url = "https://files.pythonhosted.org/packages/24/13/1d08cf0400bf0fe0bb21e70f3f5fab2130aecef962b4362b7a1eba3cd738/geventhttpclient-2.3.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:736aa8e9609e4da40aeff0dbc02fea69021a034f4ed1e99bf93fc2ca83027b64", size = 115690, upload-time = "2025-08-24T12:16:56.328Z" }, - { url = "https://files.pythonhosted.org/packages/fd/bc/15d22882983cac573859d274783c5b0a95881e553fc312e7b646be432668/geventhttpclient-2.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9d477ae1f5d42e1ee6abbe520a2e9c7f369781c3b8ca111d1f5283c1453bc825", size = 121681, upload-time = "2025-08-24T12:16:58.344Z" }, - { url = "https://files.pythonhosted.org/packages/ec/5b/c0c30ccd9d06c603add3f2d6abd68bd98430ee9730dc5478815759cf07f7/geventhttpclient-2.3.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b50d9daded5d36193d67e2fc30e59752262fcbbdc86e8222c7df6b93af0346a", size = 118987, upload-time = "2025-06-11T13:17:24.97Z" }, - { url = "https://files.pythonhosted.org/packages/4f/56/095a46af86476372064128162eccbd2ba4a7721503759890d32ea701d5fd/geventhttpclient-2.3.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe705e7656bc6982a463a4ed7f9b1db8c78c08323f1d45d0d1d77063efa0ce96", size = 124519, upload-time = "2025-06-11T13:17:25.933Z" }, - { url = "https://files.pythonhosted.org/packages/ae/12/7c9ba94b58f7954a83d33183152ce6bf5bda10c08ebe47d79a314cd33e29/geventhttpclient-2.3.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69668589359db4cbb9efa327dda5735d1e74145e6f0a9ffa50236d15cf904053", size = 115574, upload-time = "2025-06-11T13:17:27.331Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/c4e7c5bce0199428fdb811d6adf6e347180d89eaa1b9b723f711f6bbc830/geventhttpclient-2.3.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ba526e07ccaf4f1c2cd3395dda221139f01468b6eee1190d4a616f187a0378", size = 114222, upload-time = "2025-06-11T13:17:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/a3/79/58802d300950dbd7d4e31eb24afd7c270fc7900ff3923fd266cc915bb086/geventhttpclient-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:525bd192705b5cb41a7cc3fe41fca194bfd6b5b59997ab9fe68fe0a82dab6140", size = 111682, upload-time = "2025-06-11T13:17:29.291Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9c/ae04e4033459b8142788dad80d8d0b42d460bc6db9150e0815c2d0a02cb4/geventhttpclient-2.3.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:42b6f6afb0d3aab6a013c9cdb97e19bf4fe08695975670d0a018113d24cb344c", size = 113252, upload-time = "2025-06-11T13:17:30.357Z" }, - { url = "https://files.pythonhosted.org/packages/d3/67/5ae5d5878b06397a7b54334d1d31bb78cefc950ae890c2b8f5c917eb271e/geventhttpclient-2.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:227579b703085c4e5c6d5217ad6565b19ac8d1164404133e5874efaae1905114", size = 118426, upload-time = "2025-06-11T13:17:31.363Z" }, - { url = "https://files.pythonhosted.org/packages/ca/36/9065bb51f261950c42eddf8718e01a9ff344d8082e31317a8b6677be9bd6/geventhttpclient-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d1d0db89c1c8f3282eac9a22fda2b4082e1ed62a2107f70e3f1de1872c7919f", size = 112245, upload-time = "2025-06-11T13:17:32.331Z" }, - { url = "https://files.pythonhosted.org/packages/21/7e/08a615bec095c288f997951e42e48b262d43c6081bef33cfbfad96ab9658/geventhttpclient-2.3.4-cp313-cp313-win32.whl", hash = "sha256:4e492b9ab880f98f8a9cc143b96ea72e860946eae8ad5fb2837cede2a8f45154", size = 48360, upload-time = "2025-06-11T13:17:33.349Z" }, - { url = "https://files.pythonhosted.org/packages/ec/19/ef3cb21e7e95b14cfcd21e3ba7fe3d696e171682dfa43ab8c0a727cac601/geventhttpclient-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:72575c5b502bf26ececccb905e4e028bb922f542946be701923e726acf305eb6", size = 48956, upload-time = "2025-06-11T13:17:34.956Z" }, + { url = "https://files.pythonhosted.org/packages/12/0c/ec3e7926e5a24780ad0f2d422799966f2f13342c793ed9f37f0c03282f58/geventhttpclient-2.3.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9d0568d38cf74cecd37fd1ef65459f60ecd26dbc0d33bc2a1e0d8df4af24f07d", size = 70144, upload-time = "2026-03-03T08:08:19.932Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/d28f76482880f9233de07fb9422db26b983a901cad4670bba8bc1170f988/geventhttpclient-2.3.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:02e06a2f78a225b70e616b493317073f3e2fddd4e51ddfc44569d188f368bd8d", size = 51779, upload-time = "2026-03-03T08:08:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/35/ff/930be8f0e4f84d1b229b1ec394463ea36701991d888f4856904e292a6b0b/geventhttpclient-2.3.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eec8e442214d4086e40a3ae7fe1e1e3ecbc422157d8d2118059cf9977336d9f", size = 51516, upload-time = "2026-03-03T08:08:21.802Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ac/952c51392527f707c1f08401d0b477cdd1840a487dffa6e9fce444d54122/geventhttpclient-2.3.9-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a18b28d2f8bc7fcfc721227733bccb647602399db6b0fd093c00ff9699717b74", size = 115412, upload-time = "2026-03-03T08:08:22.615Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1f/1b61f8dae1efb670f7728cd727c35ff294b89af727db268f9e2d90102a97/geventhttpclient-2.3.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b16e30dbbc528453a4130210d83638444229357c073eb911421eb44e3367359", size = 116088, upload-time = "2026-03-03T08:08:23.474Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/941c05d483fe8a95672f8f39e7410292f4b617020d1d595b88da5660b132/geventhttpclient-2.3.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06df5597edf65d4c691052fce3e37620cbc037879a3b872bc16a7b2a0941d59a", size = 122068, upload-time = "2026-03-03T08:08:24.495Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/84400d58934f774cef259c8b49292542313c02224c2f11b1b116d720b464/geventhttpclient-2.3.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:47a303bcac3d69569f025d0c81781c5f0c1a48c9f225e43082d1b56e4c0440f8", size = 112054, upload-time = "2026-03-03T08:08:25.663Z" }, + { url = "https://files.pythonhosted.org/packages/12/ae/12821cad292235d4db8532f58c8bc93db4211862845bf76a4c06e6ed1416/geventhttpclient-2.3.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e73b25415e83064f5a334e83495d97b138e66f67a98cfcad154068c257733973", size = 118837, upload-time = "2026-03-03T08:08:26.816Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d3/34e80569f3563eb26f5d7bb971677de0b53b16d720f87373cc7aeee51c04/geventhttpclient-2.3.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:98ff3350d8be75586076140bde565c35ccdd72a6840b88f94037ec6595407383", size = 112643, upload-time = "2026-03-03T08:08:27.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/94ace94281e40f7258ba4e7166ae846394d2a673dbe47a0a255eb0d53ca8/geventhttpclient-2.3.9-cp313-cp313-win32.whl", hash = "sha256:af7931f55522cddedf84e837769c66d9ceb130b29182ad1e2d0201f501df899f", size = 48741, upload-time = "2026-03-03T08:08:28.507Z" }, + { url = "https://files.pythonhosted.org/packages/89/67/15b1ba79dfbab515c0d42a01b6545adef7dad00968eaa89ec21cca030c2e/geventhttpclient-2.3.9-cp313-cp313-win_amd64.whl", hash = "sha256:14daf2f0361f19b0221f900d7e9d563c184bb7186676e61fe848495b1f2483d3", size = 49371, upload-time = "2026-03-03T08:08:29.319Z" }, ] [[package]] name = "google-auth" -version = "2.40.3" +version = "2.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cachetools" }, + { name = "cryptography" }, { name = "pyasn1-modules" }, - { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/18/238d7021d151bdab868f23433817b027dd759135202f4dfce0670d1230ca/google_auth-2.50.0.tar.gz", hash = "sha256:f35eafb191195328e8ce10a7883970877e7aeb49c2bfaa54aa0e394316d353d0", size = 336523, upload-time = "2026-04-30T21:19:29.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, + { url = "https://files.pythonhosted.org/packages/37/cf/4880c2137c14280b2f59975cdf12cc442bc0ae1f9ea473a26eaa0c146786/google_auth-2.50.0-py3-none-any.whl", hash = "sha256:04382175e28b94f49694977f0a792688b59a668def1499e9d8de996dc9ce5b15", size = 246495, upload-time = "2026-04-30T21:19:27.664Z" }, ] [[package]] @@ -506,31 +540,32 @@ wheels = [ [[package]] name = "greenlet" -version = "3.2.4" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, - { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, - { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, + { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, + { url = "https://files.pythonhosted.org/packages/6a/15/a643b4ecd09969e30b8a150d5919960caae0abe4f5af75ab040b1ab85e78/greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d", size = 623234, upload-time = "2026-04-27T13:02:40.611Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, + { url = "https://files.pythonhosted.org/packages/77/18/3b13d5ef1275b0ffaf933b05efa21408ac4ca95823c7411d79682e4fdcff/greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae", size = 425243, upload-time = "2026-04-27T13:05:15.689Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, ] [[package]] name = "gunicorn" -version = "23.0.0" +version = "26.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, + { url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" }, ] [[package]] @@ -544,50 +579,53 @@ wheels = [ [[package]] name = "h5py" -version = "3.14.0" +version = "3.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/57/dfb3c5c3f1bf5f5ef2e59a22dec4ff1f3d7408b55bfcefcfb0ea69ef21c6/h5py-3.14.0.tar.gz", hash = "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", size = 424323, upload-time = "2025-06-06T14:06:15.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", size = 3289245, upload-time = "2025-06-06T14:05:28.24Z" }, - { url = "https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", size = 2807335, upload-time = "2025-06-06T14:05:31.997Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ce/3a21d87896bc7e3e9255e0ad5583ae31ae9e6b4b00e0bcb2a67e2b6acdbc/h5py-3.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", size = 4700675, upload-time = "2025-06-06T14:05:37.38Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", size = 4921632, upload-time = "2025-06-06T14:05:43.464Z" }, - { url = "https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", size = 2852929, upload-time = "2025-06-06T14:05:47.659Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, + { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, ] [[package]] name = "idna" -version = "3.10" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] name = "implicitdict" -version = "4.0.0" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "arrow" }, { name = "jsonschema" }, { name = "pytimeparse" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/35/08b36415cd0da95c347ce9e2ad74440d67108a3a14e47ec39ffb8141bd85/implicitdict-4.0.0.tar.gz", hash = "sha256:d7a799822e6be60884b7a4a91716d1ec805c184040e8b3da3eac6d0d4c012d2e", size = 57839, upload-time = "2025-08-19T09:27:58.197Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/42/657eac38a6f71090790a8399decb4c6a8243136cebc7929c18c8c0df0613/implicitdict-4.1.0.tar.gz", hash = "sha256:afa39b97180993c0c8a75bccb72fa05f14a8db7550ffc18d92a3a2717396701b", size = 53878, upload-time = "2026-01-08T04:33:24.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/0c/2b5bc3961cc1d0cc279cb7ef29b0dcdba328a935dfa5c9b8a36d5876d3e4/implicitdict-4.0.0-py3-none-any.whl", hash = "sha256:b0450ce897b982a32c8969c5c7a8d6fe0896add536c64a13403c2b6d2f86ef20", size = 13625, upload-time = "2025-08-19T09:27:57.323Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/9382537211f4009a99de7b6c005d52b55ee789c245b9e9a77015edd36958/implicitdict-4.1.0-py3-none-any.whl", hash = "sha256:e550f55aafe933e3132ec0dc9fe68e686dc2060d5ba676bf54f748cfd5fedfd0", size = 13737, upload-time = "2026-01-08T04:33:23.541Z" }, ] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -613,20 +651,19 @@ wheels = [ [[package]] name = "jsonnet" -version = "0.21.0" +version = "0.22.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/bd/e4a77ccb757a3060f30eefbd090b9593fe6ad15e5ef8ff0c3fc4aa5237cf/jsonnet-0.21.0.tar.gz", hash = "sha256:7fe2865e6e1dc2b9791d880fea3eba7e72334b256d85f027da3ae1f56a55b1da", size = 461207, upload-time = "2025-05-07T13:20:51.321Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/90/b35ecd8d60c910874a645d153d4084e07950bc746b4e77dbbe54e199d1cf/jsonnet-0.22.0.tar.gz", hash = "sha256:eae5d9bf23c778baad39390f88c13c03b2090dfb5dfa148d6e217df8c1448258", size = 529697, upload-time = "2026-03-24T14:51:45.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/9a/b7825f91d889fbe47125911a34f56f0cb94f01afdffb0bc6390f1573bb1c/jsonnet-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:95d0e0e59ed29f7e424066c05c4585fd255e288fd6050686e1d5bb54bd719896", size = 473524, upload-time = "2025-05-07T13:20:31.601Z" }, - { url = "https://files.pythonhosted.org/packages/d0/66/fe05afdcf269a8be7a99aa33741ad31cf083a505acb46010a90781b00106/jsonnet-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb926cae6ea157e2e0851e6ec8f6a2949e926f67754a87980bbcb2698a211dc5", size = 438913, upload-time = "2025-05-07T13:20:33.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2c/c4760c07b3506312f37c237c9a0840f3db44e476da5af2c0b883bb5b1070/jsonnet-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb642fe864e41a432957f71bfa57ae4eaab904886f06dec183c9e40d6ce4e24b", size = 6529866, upload-time = "2025-05-07T13:20:35.359Z" }, - { url = "https://files.pythonhosted.org/packages/1c/56/33a2eb1d263952603f9b16f3789ecac0c7a3b9bb5a6410d69173a6ed3bd9/jsonnet-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22a87070c1c50ecf6c0c8df252a4984a89275ceb18fe059dfa99eeaf548be71f", size = 6782518, upload-time = "2025-05-07T13:20:37.777Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d9/2c68a80f9cbda8e4b4721032b7def236109bd8991d1670b60beb5cfb505c/jsonnet-0.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e23e55e0a0811b899398aaa03a5b46eea01ffcafc697a705fe7b07eb8cd0ce7", size = 318264, upload-time = "2025-05-07T13:20:39.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/b9/d0ead4a4a5fbcee2807127aed41063d7482aa969a41d6d6a7c0194cc2686/jsonnet-0.22.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:42d86e5b2740e31a5e970997d470f517d9073c8a743ddb09ae69069e37529aa6", size = 443379, upload-time = "2026-03-24T14:51:38.804Z" }, + { url = "https://files.pythonhosted.org/packages/68/9a/b1bb5a0ce21a0fc1ace044d28e69003e63f88b15bc22fcb14ca7e74cf63b/jsonnet-0.22.0-cp38-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1136eefea98f01d654984e868f10faac2749d062464881491ec79ae5c38dc0e", size = 6739976, upload-time = "2026-03-24T14:51:40.153Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b7/3270203be535725bf4b1ded5574f07c5cf530a954e2e1b4760efb5bb1e55/jsonnet-0.22.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aefd2c07038eb9412ae6c46e49b7cfec74f10c53007755b176f7e075e70646f4", size = 7524407, upload-time = "2026-03-24T14:51:42.027Z" }, + { url = "https://files.pythonhosted.org/packages/90/b1/91bd98399359b255b906cfce28d898d96d52348e37502859ccb469c083fa/jsonnet-0.22.0-cp38-abi3-win_amd64.whl", hash = "sha256:46df8787b30b001b39d9d551b3d85892d99a699cf3311a63f7bcef080d8a18ff", size = 305494, upload-time = "2026-03-24T14:51:44.272Z" }, ] [[package]] name = "jsonschema" -version = "4.25.0" +version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -634,45 +671,43 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [[package]] name = "jsonschema-specifications" -version = "2025.4.1" +version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] name = "jwcrypto" -version = "1.5.6" +version = "1.5.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168, upload-time = "2024-03-06T19:58:31.831Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/90/f065668004d22715c1940d6e88e4c3afc8ee16d5664e4478d2c8fd23a250/jwcrypto-1.5.7.tar.gz", hash = "sha256:70204d7cca406eda8c82352e3c41ba2d946610dafd19e54403f0a1f4f18633c6", size = 89535, upload-time = "2026-04-07T00:35:36.116Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" }, + { url = "https://files.pythonhosted.org/packages/72/24/fb7da4d6613de7001feaf540d4b5969c6b5a1c42839043b0196cb13aa057/jwcrypto-1.5.7-py3-none-any.whl", hash = "sha256:729463fefe28b6de5cf1ebfda3e94f1a1b41d2799148ef98a01cb9678ebe2bb0", size = 94799, upload-time = "2026-04-07T00:35:35.085Z" }, ] [[package]] name = "kubernetes" -version = "33.1.0" +version = "35.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "durationpy" }, - { name = "google-auth" }, - { name = "oauthlib" }, { name = "python-dateutil" }, { name = "pyyaml" }, { name = "requests" }, @@ -681,14 +716,14 @@ dependencies = [ { name = "urllib3" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/52/19ebe8004c243fdfa78268a96727c71e08f00ff6fe69a301d0b7fcbce3c2/kubernetes-33.1.0.tar.gz", hash = "sha256:f64d829843a54c251061a8e7a14523b521f2dc5c896cf6d65ccf348648a88993", size = 1036779, upload-time = "2025-06-09T21:57:58.521Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642, upload-time = "2026-01-16T01:05:27.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/43/d9bebfc3db7dea6ec80df5cb2aad8d274dd18ec2edd6c4f21f32c237cbbb/kubernetes-33.1.0-py2.py3-none-any.whl", hash = "sha256:544de42b24b64287f7e0aa9513c93cb503f7f40eea39b20f66810011a86eabc5", size = 1941335, upload-time = "2025-06-09T21:57:56.327Z" }, + { url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602, upload-time = "2026-01-16T01:05:25.991Z" }, ] [[package]] name = "locust" -version = "2.38.1" +version = "2.43.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "configargparse" }, @@ -697,34 +732,19 @@ dependencies = [ { name = "flask-login" }, { name = "gevent" }, { name = "geventhttpclient" }, - { name = "locust-cloud" }, { name = "msgpack" }, { name = "psutil" }, + { name = "pytest" }, + { name = "python-engineio" }, + { name = "python-socketio", extra = ["client"] }, { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "pyzmq" }, { name = "requests" }, - { name = "setuptools" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/03/2f92b75d971e6043cca6fcec59ceccfa800a1324425a74950603d8cac33a/locust-2.38.1.tar.gz", hash = "sha256:4ad9f2f9e7d56b7747ba67cb16e47ca0466b3908f402f50660f15f37621a5218", size = 1406572, upload-time = "2025-08-12T11:38:52.007Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/f6/4a8087f44abd67bb8cc51fba52dcfdddc09d69c154819d56b7da4c79f9ad/locust-2.38.1-py3-none-any.whl", hash = "sha256:34978219ee0d682a135fd4c67f287c26725e7b3fa83d34d65be70efdb42ab4d1", size = 1424130, upload-time = "2025-08-12T11:38:49.707Z" }, -] - -[[package]] -name = "locust-cloud" -version = "1.26.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "configargparse" }, - { name = "gevent" }, - { name = "platformdirs" }, - { name = "python-engineio" }, - { name = "python-socketio", extra = ["client"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/ad/10b299b134068a4250a9156e6832a717406abe1dfea2482a07ae7bdca8f3/locust_cloud-1.26.3.tar.gz", hash = "sha256:587acfd4d2dee715fb5f0c3c2d922770babf0b7cff7b2927afbb693a9cd193cc", size = 456042, upload-time = "2025-07-15T19:51:53.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/be/6df1c778f673e1e2d785f262d20a4e130fdb8e51242466d7ae434b66a587/locust-2.43.4.tar.gz", hash = "sha256:4ace60f07f5fa9bf08d1b64da25915707befca19a790897eed6372656824deee", size = 1434321, upload-time = "2026-04-01T20:43:04.322Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/6a/276fc50a9d170e7cbb6715735480cb037abb526639bca85491576e6eee4a/locust_cloud-1.26.3-py3-none-any.whl", hash = "sha256:8cb4b8bb9adcd5b99327bc8ed1d98cf67a29d9d29512651e6e94869de6f1faa8", size = 410023, upload-time = "2025-07-15T19:51:52.056Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2c/a90d0b6fc476eb0f8e5a705f49e410563450b9b087688cc93a50eab54d63/locust-2.43.4-py3-none-any.whl", hash = "sha256:a4f40403e9f665e0dcb94991d9a8f19317d0d36afe88400833c5fab99ba942ed", size = 1454332, upload-time = "2026-04-01T20:43:02.767Z" }, ] [[package]] @@ -742,63 +762,67 @@ wheels = [ [[package]] name = "lxml" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" }, - { url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" }, - { url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" }, - { url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" }, - { url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" }, - { url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" }, - { url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" }, - { url = "https://files.pythonhosted.org/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" }, - { url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" }, - { url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" }, - { url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" }, - { url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" }, +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, ] [[package]] name = "marko" -version = "2.2.0" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/6a/32545d2379822fb9a8843f01150011402888492541977a1193fe8d695df0/marko-2.2.0.tar.gz", hash = "sha256:213c146ba197c1d6bcb06ae3658b7d87e45f6def35c09905b86aa6bb1984eba6", size = 143406, upload-time = "2025-08-08T09:47:05.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/2f/050b6d485f052ddf17d76a41f9334d6fb2a8a85df35347a12d97ed3bc5c1/marko-2.2.2.tar.gz", hash = "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", size = 143641, upload-time = "2026-01-05T11:04:41.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/b1/87f54d8842b2aafdbb162301ac730587e04f30ad0fe9aabb12fa29f7a6f7/marko-2.2.0-py3-none-any.whl", hash = "sha256:d84f867429142627e896322c8ef167664f3a6cd6ea5a2b70c6af055998041bb7", size = 42683, upload-time = "2025-08-08T09:47:04.175Z" }, + { url = "https://files.pythonhosted.org/packages/83/f8/36d79bac5701e6786f9880c61bbe57574760a13c1af84ab71e5ed21faecc/marko-2.2.2-py3-none-any.whl", hash = "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e", size = 42701, upload-time = "2026-01-05T11:04:40.75Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, ] [[package]] @@ -808,9 +832,9 @@ source = { virtual = "." } dependencies = [ { name = "aiohttp" }, { name = "arrow" }, - { name = "basedpyright" }, { name = "bc-jsonpath-ng" }, { name = "cryptography" }, + { name = "deprecation" }, { name = "faker" }, { name = "flask" }, { name = "flask-login" }, @@ -838,11 +862,8 @@ dependencies = [ { name = "pykml" }, { name = "pyopenssl" }, { name = "pyproj" }, - { name = "pytest" }, - { name = "pytest-mock" }, { name = "pyyaml" }, { name = "requests" }, - { name = "ruff" }, { name = "s2sphere" }, { name = "scipy" }, { name = "shapely" }, @@ -853,13 +874,22 @@ dependencies = [ { name = "uuid6" }, ] +[package.dev-dependencies] +dev = [ + { name = "basedpyright" }, + { name = "pytest" }, + { name = "pytest-mock" }, + { name = "ruff" }, + { name = "types-lxml" }, +] + [package.metadata] requires-dist = [ { name = "aiohttp" }, { name = "arrow" }, - { name = "basedpyright", specifier = ">=1.31.1" }, { name = "bc-jsonpath-ng" }, { name = "cryptography" }, + { name = "deprecation" }, { name = "faker" }, { name = "flask" }, { name = "flask-login" }, @@ -887,11 +917,8 @@ requires-dist = [ { name = "pykml" }, { name = "pyopenssl" }, { name = "pyproj" }, - { name = "pytest" }, - { name = "pytest-mock" }, { name = "pyyaml" }, { name = "requests" }, - { name = "ruff" }, { name = "s2sphere" }, { name = "scipy" }, { name = "shapely" }, @@ -902,111 +929,120 @@ requires-dist = [ { name = "uuid6" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "basedpyright", specifier = ">=1.31.1" }, + { name = "pytest" }, + { name = "pytest-mock" }, + { name = "ruff" }, + { name = "types-lxml", specifier = ">=2025.8.25" }, +] + [[package]] name = "msgpack" -version = "1.1.1" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" }, - { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" }, - { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" }, - { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" }, - { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" }, - { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" }, - { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" }, - { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" }, - { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, ] [[package]] name = "multidict" -version = "6.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, - { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, - { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, - { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, - { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, - { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, - { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, - { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, - { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, - { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, - { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, - { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, - { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, - { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, - { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, - { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, - { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, - { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] [[package]] name = "nodejs-wheel-binaries" -version = "22.18.0" +version = "24.15.0" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/70/a1e4f4d5986768ab90cc860b1cc3660fd2ded74ca175a900a5c29f839c7d/nodejs_wheel_binaries-24.15.0.tar.gz", hash = "sha256:b43f5c4f6e5768d8845b2ae4682eb703a19bf7aadc84187e2d903ed3a611c859", size = 8057, upload-time = "2026-04-19T15:48:16.899Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/6d/773e09de4a052cc75c129c3766a3cf77c36bff8504a38693b735f4a1eb55/nodejs_wheel_binaries-22.18.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b04495857755c5d5658f7ac969d84f25898fe0b0c1bdc41172e5e0ac6105ca", size = 50873051, upload-time = "2025-08-01T11:10:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/ae/fc/3d6fd4ad5d26c9acd46052190d6a8895dc5050297b03d9cce03def53df0d/nodejs_wheel_binaries-22.18.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:bd4d016257d4dfe604ed526c19bd4695fdc4f4cc32e8afc4738111447aa96d03", size = 51814481, upload-time = "2025-08-01T11:10:33.086Z" }, - { url = "https://files.pythonhosted.org/packages/10/f9/7be44809a861605f844077f9e731a117b669d5ca6846a7820e7dd82c9fad/nodejs_wheel_binaries-22.18.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b125f94f3f5e8ab9560d3bd637497f02e45470aeea74cf6fe60afe751cfa5f", size = 57804907, upload-time = "2025-08-01T11:10:36.83Z" }, - { url = "https://files.pythonhosted.org/packages/e9/67/563e74a0dff653ec7ddee63dc49b3f37a20df39f23675cfc801d7e8e4bb7/nodejs_wheel_binaries-22.18.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bbb81b6e67c15f04e2a9c6c220d7615fb46ae8f1ad388df0d66abac6bed5f8", size = 58335587, upload-time = "2025-08-01T11:10:40.716Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b1/ec45fefef60223dd40e7953e2ff087964e200d6ec2d04eae0171d6428679/nodejs_wheel_binaries-22.18.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5d3ea8b7f957ae16b73241451f6ce831d6478156f363cce75c7ea71cbe6c6f7", size = 59662356, upload-time = "2025-08-01T11:10:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ed/6de2c73499eebf49d0d20e0704f64566029a3441c48cd4f655d49befd28b/nodejs_wheel_binaries-22.18.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bcda35b07677039670102a6f9b78c2313fd526111d407cb7ffc2a4c243a48ef9", size = 60706806, upload-time = "2025-08-01T11:10:48.985Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f5/487434b1792c4f28c63876e4a896f2b6e953e2dc1f0b3940e912bd087755/nodejs_wheel_binaries-22.18.0-py2.py3-none-win_amd64.whl", hash = "sha256:0f55e72733f1df2f542dce07f35145ac2e125408b5e2051cac08e5320e41b4d1", size = 39998139, upload-time = "2025-08-01T11:10:52.676Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/54051d14853d6ab4fb85f8be9b042b530be653357fb9a19557498bc91ab7/nodejs_wheel_binaries-24.15.0-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:a6232fa8b754220941f52388c8ead923f7c1c7fdf0ea0d98f657523bd9a81ef4", size = 55173485, upload-time = "2026-04-19T15:47:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/66acada164da5ca10a0824db021aa7394ae18396c550cd9280e839a43126/nodejs_wheel_binaries-24.15.0-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:001a6b62c69d9109c1738163cca00608dd2722e8663af59300054ea02610972d", size = 55348100, upload-time = "2026-04-19T15:47:40.521Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2d/0cbd5ff40c9bb030ca1735d8f8793bd74f08a4cbd49100a1d19313ea57ab/nodejs_wheel_binaries-24.15.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:0fbc48765e60ed0ff30d43898dbf5cadbadf2e5f1e7f204afc2b01493b7ebce6", size = 59668206, upload-time = "2026-04-19T15:47:46.848Z" }, + { url = "https://files.pythonhosted.org/packages/da/d5/91ac63951ec75927a486b83b8cafe650e360fa70ac01dc94adfb32b93b97/nodejs_wheel_binaries-24.15.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:20ee0536809795da8a4942fc1ab4cbdebbcaaf29383eab67ba8874268fb00008", size = 60206736, upload-time = "2026-04-19T15:47:52.668Z" }, + { url = "https://files.pythonhosted.org/packages/db/72/dc22776974d928869c0c30d23ee98ed7df254243c2df68f09f5963e8e8b8/nodejs_wheel_binaries-24.15.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1fade6c214285e72472ca40a631e98ff36559671cd5eefc8bf009471d67f04b4", size = 61720456, upload-time = "2026-04-19T15:47:58.325Z" }, + { url = "https://files.pythonhosted.org/packages/01/0a/34461b9050cb45ee371dccdefc622aef6351506ea2691b08fc761ca67150/nodejs_wheel_binaries-24.15.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3984cb8d87766567aee67a49743227ab40ede6f47734ec990ff90e50b74e7740", size = 62326172, upload-time = "2026-04-19T15:48:04.094Z" }, + { url = "https://files.pythonhosted.org/packages/c9/17/09252bf35672dba926649d59dfe51443a0f6955ad13784e91131d5ec82a2/nodejs_wheel_binaries-24.15.0-py2.py3-none-win_amd64.whl", hash = "sha256:a437601956b532dcb3082046e6978e622733f90edc0932cbb9adb3bb97a16501", size = 41543461, upload-time = "2026-04-19T15:48:09.332Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/b649777d148e1e0c2ce349156603cdb12f7ed99921b95d93717393650193/nodejs_wheel_binaries-24.15.0-py2.py3-none-win_arm64.whl", hash = "sha256:bdf4a431e08321a32efc604111c6f23941f87055d796a537e8c4110daecad23f", size = 39233248, upload-time = "2026-04-19T15:48:13.326Z" }, ] [[package]] name = "numpy" -version = "2.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, - { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, - { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, - { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, - { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, - { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, - { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, - { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, - { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, - { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, - { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, - { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, - { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, ] [[package]] @@ -1020,38 +1056,39 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "pandas" -version = "2.3.1" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" }, - { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" }, - { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" }, - { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" }, - { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" }, - { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" }, - { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105, upload-time = "2026-03-31T06:46:57.327Z" }, + { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088, upload-time = "2026-03-31T06:46:59.935Z" }, + { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066, upload-time = "2026-03-31T06:47:02.792Z" }, + { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780, upload-time = "2026-03-31T06:47:06.205Z" }, + { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181, upload-time = "2026-03-31T06:47:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899, upload-time = "2026-03-31T06:47:12.686Z" }, + { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574, upload-time = "2026-03-31T06:47:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156, upload-time = "2026-03-31T06:47:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238, upload-time = "2026-03-31T06:47:22.012Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520, upload-time = "2026-03-31T06:47:25.197Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154, upload-time = "2026-03-31T06:47:28.097Z" }, + { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449, upload-time = "2026-03-31T06:47:30.85Z" }, + { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475, upload-time = "2026-03-31T06:47:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568, upload-time = "2026-03-31T06:47:36.941Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652, upload-time = "2026-03-31T06:47:40.612Z" }, ] [[package]] @@ -1065,16 +1102,16 @@ wheels = [ [[package]] name = "pip" -version = "25.2" +version = "26.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/16/650289cd3f43d5a2fadfd98c68bd1e1e7f2550a1a5326768cddfbcedb2c5/pip-25.2.tar.gz", hash = "sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2", size = 1840021, upload-time = "2025-07-30T21:50:15.401Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/48/cb9b7a682f6fe01a4221e1728941dd4ac3cd9090a17db3779d6ff490b602/pip-26.1.1.tar.gz", hash = "sha256:d36762751d156a4ee895de8af39aa0abeeeb577f93a2eca6ab62467bbf0f8a78", size = 1840400, upload-time = "2026-05-04T19:02:21.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/3f/945ef7ab14dc4f9d7f40288d2df998d1837ee0888ec3659c813487572faa/pip-25.2-py3-none-any.whl", hash = "sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717", size = 1752557, upload-time = "2025-07-30T21:50:13.323Z" }, + { url = "https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl", hash = "sha256:99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb", size = 1812777, upload-time = "2026-05-04T19:02:18.9Z" }, ] [[package]] name = "pip-tools" -version = "7.5.0" +version = "7.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "build" }, @@ -1084,18 +1121,9 @@ dependencies = [ { name = "setuptools" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/33/06/25a48f5f04851c4a5a010e8ce2b0070f19ac514b4d23e7ae687741bdaa76/pip_tools-7.5.0.tar.gz", hash = "sha256:30639f50961bb09f49d22f4389e8d7d990709677c094ce1114186b1f2e9b5821", size = 158683, upload-time = "2025-07-31T09:45:20.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/db/c6e2a02db5d98aa5f3250a305ce71e8bc3d1a022d1f47a54d14492ae23de/pip_tools-7.5.3.tar.gz", hash = "sha256:8fa364779ebc010cbfe17cb9de404457ac733e100840423f28f6955de7742d41", size = 176153, upload-time = "2026-02-11T18:25:07.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/91/93377825e9c7e929a02b418204a9f906596e398b7e92da3326a76b1db223/pip_tools-7.5.0-py3-none-any.whl", hash = "sha256:69758e4e5a65f160e315d74db46246fdbb30d549f1ed0c4236d057122c9b0f18", size = 65080, upload-time = "2025-07-31T09:45:18.546Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, + { url = "https://files.pythonhosted.org/packages/6e/74/59906d876c6cb1137f42a137164f2fe683b06283cde84bfcf7f5dd43970b/pip_tools-7.5.3-py3-none-any.whl", hash = "sha256:3aac0c473240ae90db7213c033401f345b05197293ccbdd2704e52e7a783785e", size = 71359, upload-time = "2026-02-11T18:25:06.119Z" }, ] [[package]] @@ -1118,70 +1146,75 @@ wheels = [ [[package]] name = "propcache" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, - { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, - { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, - { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, - { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, - { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, - { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] [[package]] name = "psutil" -version = "7.0.0" +version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "psycopg" -version = "3.2.9" +version = "3.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" }, ] [package.optional-dependencies] @@ -1191,25 +1224,25 @@ binary = [ [[package]] name = "psycopg-binary" -version = "3.2.9" +version = "3.3.4" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, - { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, - { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, - { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, - { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, - { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, - { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/09/43/13e9c406fbbf354580476e248a16b64802a376873ebe6339e30bb655572d/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7", size = 4590377, upload-time = "2026-05-01T23:29:18.782Z" }, + { url = "https://files.pythonhosted.org/packages/22/be/2923cd7c3683e7afdecf4f10796a18de02f5c5ddc0969aa2ad0a8cdd3bbd/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31", size = 4669023, upload-time = "2026-05-01T23:29:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/96/a0/2c913d6fe13d6a8bd13597d36739bf47af063ad9399e402cfecab16f3c1e/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70", size = 5467423, upload-time = "2026-05-01T23:29:33.416Z" }, + { url = "https://files.pythonhosted.org/packages/e7/38/205d10bc1ad0df4a21c5c51659126bd3ea0ef98fcad1e852f78c249bb9c3/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3", size = 5151137, upload-time = "2026-05-01T23:29:42.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/fc/f0381ddcd45eff3bb70dbca6823a996048d7f507b2ec3fc92c6fabc0fe87/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c", size = 6736671, upload-time = "2026-05-01T23:29:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/95/40/fa545ae152c24327651e5624e4902121e808270be36c10b12e9939be09bc/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae", size = 4979601, upload-time = "2026-05-01T23:29:56.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/e4/2f8a47ee97f90cd2b933d0463081d35631ff419de2b8c984a5f369857de0/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc", size = 4510513, upload-time = "2026-05-01T23:30:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/94e842ff4a7f98ed162580ca2e8b8864b28c1e0350f2443f8ee47f821167/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf", size = 4187243, upload-time = "2026-05-01T23:30:15.352Z" }, + { url = "https://files.pythonhosted.org/packages/d0/83/fc6c174b672e29b7de996ea77b6cbddf46c891751c3355f6974292baa6b4/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260", size = 3927347, upload-time = "2026-05-01T23:30:21.186Z" }, + { url = "https://files.pythonhosted.org/packages/e9/65/768364d4a97a15b1a7f47ba52688c1686f22941d8332a8398cefc468e25f/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf", size = 4236393, upload-time = "2026-05-01T23:30:26.211Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/218efbc9e645becd80cdf651acda05f85cfe546b7a9c0458c7cbc8fe1f74/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38", size = 3564592, upload-time = "2026-05-01T23:30:31.764Z" }, ] [[package]] name = "pvlib" -version = "0.13.0" +version = "0.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h5py" }, @@ -1219,18 +1252,18 @@ dependencies = [ { name = "requests" }, { name = "scipy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/94/83df0a86ebc22b894b21ae68f402fb3216ba2ae5b6c3eb04c477113eb625/pvlib-0.13.0.tar.gz", hash = "sha256:cef36cdd932a6a7bf2dd7c992b0004b1a08d315ee8043b273d192ad57902e85a", size = 38656117, upload-time = "2025-06-07T12:58:30.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/21/e770846e7ff65e79c5f44df70a4ca80f4f1fdf417ac83f6a315c40700574/pvlib-0.15.1.tar.gz", hash = "sha256:b8824aa28fd4b70179b31888fc4bc9c9c17e1c99ff171a3fec5edc53f9347bcd", size = 38696804, upload-time = "2026-04-21T18:21:37.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/d5/f82a98fa41b709541ee435e0868dd9fdb0ff39a22bd7dca76fec0a23195b/pvlib-0.13.0-py3-none-any.whl", hash = "sha256:ed0ffdd703aa12ef13f5bd34a2d57675da0c000305fdf7709207b7b08120ffd0", size = 19338250, upload-time = "2025-06-07T12:58:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/4ece93734300e55abb8b5935c805aa28a6d6eb7701ec3f8b3c011db9c9e4/pvlib-0.15.1-py3-none-any.whl", hash = "sha256:ebd41a93dbc215db8ca3b3ee7d69bde213a1aa861b8313d5d3be6f82fb74b6b8", size = 19353995, upload-time = "2026-04-21T18:21:33.976Z" }, ] [[package]] name = "pyasn1" -version = "0.6.1" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, ] [[package]] @@ -1247,29 +1280,29 @@ wheels = [ [[package]] name = "pycparser" -version = "2.22" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] [[package]] @@ -1286,14 +1319,14 @@ wheels = [ [[package]] name = "pyopenssl" -version = "25.1.0" +version = "26.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771, upload-time = "2025-05-17T16:28:29.197Z" }, + { url = "https://files.pythonhosted.org/packages/73/b8/a0e2790ae249d6f38c9f66de7a211621a7ab2650217bcd04e1262f578a56/pyopenssl-26.2.0-py3-none-any.whl", hash = "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", size = 55823, upload-time = "2026-05-04T23:06:08.395Z" }, ] [[package]] @@ -1336,7 +1369,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.1" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1345,21 +1378,21 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] name = "pytest-mock" -version = "3.14.1" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] [[package]] @@ -1376,36 +1409,36 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] name = "python-engineio" -version = "4.12.2" +version = "4.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "simple-websocket" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677, upload-time = "2025-06-04T19:22:18.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/12/bdef9dbeedbe2cdeba2a2056ad27b1fb081557d34b69a97f574843462cae/python_engineio-4.13.1.tar.gz", hash = "sha256:0a853fcef52f5b345425d8c2b921ac85023a04dfcf75d7b74696c61e940fd066", size = 92348, upload-time = "2026-02-06T23:38:06.12Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536, upload-time = "2025-06-04T19:22:16.916Z" }, + { url = "https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl", hash = "sha256:f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399", size = 59847, upload-time = "2026-02-06T23:38:04.861Z" }, ] [[package]] name = "python-socketio" -version = "5.13.0" +version = "5.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bidict" }, { name = "python-engineio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125, upload-time = "2025-04-12T15:46:59.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/81/cf8284f45e32efa18d3848ed82cdd4dcc1b657b082458fbe01ad3e1f2f8d/python_socketio-5.16.1.tar.gz", hash = "sha256:f863f98eacce81ceea2e742f6388e10ca3cdd0764be21d30d5196470edf5ea89", size = 128508, upload-time = "2026-02-06T23:42:07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload-time = "2025-04-12T15:46:58.412Z" }, + { url = "https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl", hash = "sha256:a3eb1702e92aa2f2b5d3ba00261b61f062cce51f1cfb6900bf3ab4d1934d2d35", size = 82054, upload-time = "2026-02-06T23:42:05.772Z" }, ] [package.optional-dependencies] @@ -1425,11 +1458,11 @@ wheels = [ [[package]] name = "pytz" -version = "2025.2" +version = "2026.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, ] [[package]] @@ -1444,70 +1477,71 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, ] [[package]] name = "pyzmq" -version = "27.0.1" +version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/5f/557d2032a2f471edbcc227da724c24a1c05887b5cda1e3ae53af98b9e0a5/pyzmq-27.0.1.tar.gz", hash = "sha256:45c549204bc20e7484ffd2555f6cf02e572440ecf2f3bdd60d4404b20fddf64b", size = 281158, upload-time = "2025-08-03T05:05:40.352Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/9b/c0957041067c7724b310f22c398be46399297c12ed834c3bc42200a2756f/pyzmq-27.0.1-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:af7ebce2a1e7caf30c0bb64a845f63a69e76a2fadbc1cac47178f7bb6e657bdd", size = 1305432, upload-time = "2025-08-03T05:03:32.177Z" }, - { url = "https://files.pythonhosted.org/packages/8e/55/bd3a312790858f16b7def3897a0c3eb1804e974711bf7b9dcb5f47e7f82c/pyzmq-27.0.1-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8f617f60a8b609a13099b313e7e525e67f84ef4524b6acad396d9ff153f6e4cd", size = 895095, upload-time = "2025-08-03T05:03:33.918Z" }, - { url = "https://files.pythonhosted.org/packages/20/50/fc384631d8282809fb1029a4460d2fe90fa0370a0e866a8318ed75c8d3bb/pyzmq-27.0.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d59dad4173dc2a111f03e59315c7bd6e73da1a9d20a84a25cf08325b0582b1a", size = 651826, upload-time = "2025-08-03T05:03:35.818Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0a/2356305c423a975000867de56888b79e44ec2192c690ff93c3109fd78081/pyzmq-27.0.1-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5b6133c8d313bde8bd0d123c169d22525300ff164c2189f849de495e1344577", size = 839751, upload-time = "2025-08-03T05:03:37.265Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1b/81e95ad256ca7e7ccd47f5294c1c6da6e2b64fbace65b84fe8a41470342e/pyzmq-27.0.1-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:58cca552567423f04d06a075f4b473e78ab5bdb906febe56bf4797633f54aa4e", size = 1641359, upload-time = "2025-08-03T05:03:38.799Z" }, - { url = "https://files.pythonhosted.org/packages/50/63/9f50ec965285f4e92c265c8f18344e46b12803666d8b73b65d254d441435/pyzmq-27.0.1-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:4b9d8e26fb600d0d69cc9933e20af08552e97cc868a183d38a5c0d661e40dfbb", size = 2020281, upload-time = "2025-08-03T05:03:40.338Z" }, - { url = "https://files.pythonhosted.org/packages/02/4a/19e3398d0dc66ad2b463e4afa1fc541d697d7bc090305f9dfb948d3dfa29/pyzmq-27.0.1-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2329f0c87f0466dce45bba32b63f47018dda5ca40a0085cc5c8558fea7d9fc55", size = 1877112, upload-time = "2025-08-03T05:03:42.012Z" }, - { url = "https://files.pythonhosted.org/packages/bf/42/c562e9151aa90ed1d70aac381ea22a929d6b3a2ce4e1d6e2e135d34fd9c6/pyzmq-27.0.1-cp312-abi3-win32.whl", hash = "sha256:57bb92abdb48467b89c2d21da1ab01a07d0745e536d62afd2e30d5acbd0092eb", size = 558177, upload-time = "2025-08-03T05:03:43.979Z" }, - { url = "https://files.pythonhosted.org/packages/40/96/5c50a7d2d2b05b19994bf7336b97db254299353dd9b49b565bb71b485f03/pyzmq-27.0.1-cp312-abi3-win_amd64.whl", hash = "sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686", size = 618923, upload-time = "2025-08-03T05:03:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/13/33/1ec89c8f21c89d21a2eaff7def3676e21d8248d2675705e72554fb5a6f3f/pyzmq-27.0.1-cp312-abi3-win_arm64.whl", hash = "sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed", size = 552358, upload-time = "2025-08-03T05:03:46.887Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/f26e276211ec8090a4d11e4ec70eb8a8b15781e591c1d44ce62f372963a0/pyzmq-27.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5", size = 1122287, upload-time = "2025-08-03T05:03:48.838Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d8/af4b507e4f7eeea478cc8ee873995a6fd55582bfb99140593ed460e1db3c/pyzmq-27.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7", size = 1155756, upload-time = "2025-08-03T05:03:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/ac/55/37fae0013e11f88681da42698e550b08a316d608242551f65095cc99232a/pyzmq-27.0.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96", size = 1340826, upload-time = "2025-08-03T05:03:52.568Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e4/3a87854c64b26fcf63a9d1b6f4382bd727d4797c772ceb334a97b7489be9/pyzmq-27.0.1-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9", size = 897283, upload-time = "2025-08-03T05:03:54.167Z" }, - { url = "https://files.pythonhosted.org/packages/17/3e/4296c6b0ad2d07be11ae1395dccf9cae48a0a655cf9be1c3733ad2b591d1/pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1", size = 660565, upload-time = "2025-08-03T05:03:56.152Z" }, - { url = "https://files.pythonhosted.org/packages/72/41/a33ba3aa48b45b23c4cd4ac49aafde46f3e0f81939f2bfb3b6171a437122/pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45c3e00ce16896ace2cd770ab9057a7cf97d4613ea5f2a13f815141d8b6894b9", size = 847680, upload-time = "2025-08-03T05:03:57.696Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8c/bf2350bb25b3b58d2e5b5d2290ffab0e923f0cc6d02288d3fbf4baa6e4d1/pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:678e50ec112bdc6df5a83ac259a55a4ba97a8b314c325ab26b3b5b071151bc61", size = 1650151, upload-time = "2025-08-03T05:03:59.387Z" }, - { url = "https://files.pythonhosted.org/packages/f7/1a/a5a07c54890891344a8ddc3d5ab320dd3c4e39febb6e4472546e456d5157/pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d0b96c30be9f9387b18b18b6133c75a7b1b0065da64e150fe1feb5ebf31ece1c", size = 2023766, upload-time = "2025-08-03T05:04:01.883Z" }, - { url = "https://files.pythonhosted.org/packages/62/5e/514dcff08f02c6c8a45a6e23621901139cf853be7ac5ccd0b9407c3aa3de/pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88dc92d9eb5ea4968123e74db146d770b0c8d48f0e2bfb1dbc6c50a8edb12d64", size = 1885195, upload-time = "2025-08-03T05:04:03.923Z" }, - { url = "https://files.pythonhosted.org/packages/c8/91/87f74f98a487fbef0b115f6025e4a295129fd56b2b633a03ba7d5816ecc2/pyzmq-27.0.1-cp313-cp313t-win32.whl", hash = "sha256:6dcbcb34f5c9b0cefdfc71ff745459241b7d3cda5b27c7ad69d45afc0821d1e1", size = 574213, upload-time = "2025-08-03T05:04:05.905Z" }, - { url = "https://files.pythonhosted.org/packages/e6/d7/07f7d0d7f4c81e08be7b60e52ff2591c557377c017f96204d33d5fca1b07/pyzmq-27.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9fd0fda730461f510cfd9a40fafa5355d65f5e3dbdd8d6dfa342b5b3f5d1949", size = 640202, upload-time = "2025-08-03T05:04:07.439Z" }, - { url = "https://files.pythonhosted.org/packages/ab/83/21d66bcef6fb803647a223cbde95111b099e2176277c0cbc8b099c485510/pyzmq-27.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:56a3b1853f3954ec1f0e91085f1350cc57d18f11205e4ab6e83e4b7c414120e0", size = 561514, upload-time = "2025-08-03T05:04:09.071Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, ] [[package]] name = "referencing" -version = "0.36.2" +version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] name = "requests" -version = "2.32.4" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1515,9 +1549,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -1535,77 +1569,64 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.27.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/d9/991a0dee12d9fc53ed027e26a26a64b151d77252ac477e22666b9688bc16/rpds_py-0.27.0.tar.gz", hash = "sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f", size = 27420, upload-time = "2025-08-07T08:26:39.624Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/d2/dfdfd42565a923b9e5a29f93501664f5b984a802967d48d49200ad71be36/rpds_py-0.27.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff", size = 362133, upload-time = "2025-08-07T08:24:04.508Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/0a2e2460c4b66021d349ce9f6331df1d6c75d7eea90df9785d333a49df04/rpds_py-0.27.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367", size = 347128, upload-time = "2025-08-07T08:24:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/35/8d/7d1e4390dfe09d4213b3175a3f5a817514355cb3524593380733204f20b9/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185", size = 384027, upload-time = "2025-08-07T08:24:06.841Z" }, - { url = "https://files.pythonhosted.org/packages/c1/65/78499d1a62172891c8cd45de737b2a4b84a414b6ad8315ab3ac4945a5b61/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc", size = 399973, upload-time = "2025-08-07T08:24:08.143Z" }, - { url = "https://files.pythonhosted.org/packages/10/a1/1c67c1d8cc889107b19570bb01f75cf49852068e95e6aee80d22915406fc/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe", size = 515295, upload-time = "2025-08-07T08:24:09.711Z" }, - { url = "https://files.pythonhosted.org/packages/df/27/700ec88e748436b6c7c4a2262d66e80f8c21ab585d5e98c45e02f13f21c0/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9", size = 406737, upload-time = "2025-08-07T08:24:11.182Z" }, - { url = "https://files.pythonhosted.org/packages/33/cc/6b0ee8f0ba3f2df2daac1beda17fde5cf10897a7d466f252bd184ef20162/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c", size = 385898, upload-time = "2025-08-07T08:24:12.798Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7e/c927b37d7d33c0a0ebf249cc268dc2fcec52864c1b6309ecb960497f2285/rpds_py-0.27.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295", size = 405785, upload-time = "2025-08-07T08:24:14.906Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/8ed50746d909dcf402af3fa58b83d5a590ed43e07251d6b08fad1a535ba6/rpds_py-0.27.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43", size = 419760, upload-time = "2025-08-07T08:24:16.129Z" }, - { url = "https://files.pythonhosted.org/packages/d3/60/2b2071aee781cb3bd49f94d5d35686990b925e9b9f3e3d149235a6f5d5c1/rpds_py-0.27.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432", size = 561201, upload-time = "2025-08-07T08:24:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/98/1f/27b67304272521aaea02be293fecedce13fa351a4e41cdb9290576fc6d81/rpds_py-0.27.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b", size = 591021, upload-time = "2025-08-07T08:24:18.999Z" }, - { url = "https://files.pythonhosted.org/packages/db/9b/a2fadf823164dd085b1f894be6443b0762a54a7af6f36e98e8fcda69ee50/rpds_py-0.27.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d", size = 556368, upload-time = "2025-08-07T08:24:20.54Z" }, - { url = "https://files.pythonhosted.org/packages/24/f3/6d135d46a129cda2e3e6d4c5e91e2cc26ea0428c6cf152763f3f10b6dd05/rpds_py-0.27.0-cp313-cp313-win32.whl", hash = "sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd", size = 221236, upload-time = "2025-08-07T08:24:22.144Z" }, - { url = "https://files.pythonhosted.org/packages/c5/44/65d7494f5448ecc755b545d78b188440f81da98b50ea0447ab5ebfdf9bd6/rpds_py-0.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2", size = 232634, upload-time = "2025-08-07T08:24:23.642Z" }, - { url = "https://files.pythonhosted.org/packages/70/d9/23852410fadab2abb611733933401de42a1964ce6600a3badae35fbd573e/rpds_py-0.27.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac", size = 222783, upload-time = "2025-08-07T08:24:25.098Z" }, - { url = "https://files.pythonhosted.org/packages/15/75/03447917f78512b34463f4ef11066516067099a0c466545655503bed0c77/rpds_py-0.27.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774", size = 359154, upload-time = "2025-08-07T08:24:26.249Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fc/4dac4fa756451f2122ddaf136e2c6aeb758dc6fdbe9ccc4bc95c98451d50/rpds_py-0.27.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b", size = 343909, upload-time = "2025-08-07T08:24:27.405Z" }, - { url = "https://files.pythonhosted.org/packages/7b/81/723c1ed8e6f57ed9d8c0c07578747a2d3d554aaefc1ab89f4e42cfeefa07/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd", size = 379340, upload-time = "2025-08-07T08:24:28.714Z" }, - { url = "https://files.pythonhosted.org/packages/98/16/7e3740413de71818ce1997df82ba5f94bae9fff90c0a578c0e24658e6201/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb", size = 391655, upload-time = "2025-08-07T08:24:30.223Z" }, - { url = "https://files.pythonhosted.org/packages/e0/63/2a9f510e124d80660f60ecce07953f3f2d5f0b96192c1365443859b9c87f/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433", size = 513017, upload-time = "2025-08-07T08:24:31.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/4e/cf6ff311d09776c53ea1b4f2e6700b9d43bb4e99551006817ade4bbd6f78/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615", size = 402058, upload-time = "2025-08-07T08:24:32.613Z" }, - { url = "https://files.pythonhosted.org/packages/88/11/5e36096d474cb10f2a2d68b22af60a3bc4164fd8db15078769a568d9d3ac/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8", size = 383474, upload-time = "2025-08-07T08:24:33.767Z" }, - { url = "https://files.pythonhosted.org/packages/db/a2/3dff02805b06058760b5eaa6d8cb8db3eb3e46c9e452453ad5fc5b5ad9fe/rpds_py-0.27.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858", size = 400067, upload-time = "2025-08-07T08:24:35.021Z" }, - { url = "https://files.pythonhosted.org/packages/67/87/eed7369b0b265518e21ea836456a4ed4a6744c8c12422ce05bce760bb3cf/rpds_py-0.27.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5", size = 412085, upload-time = "2025-08-07T08:24:36.267Z" }, - { url = "https://files.pythonhosted.org/packages/8b/48/f50b2ab2fbb422fbb389fe296e70b7a6b5ea31b263ada5c61377e710a924/rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9", size = 555928, upload-time = "2025-08-07T08:24:37.573Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/b18eb51045d06887666c3560cd4bbb6819127b43d758f5adb82b5f56f7d1/rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79", size = 585527, upload-time = "2025-08-07T08:24:39.391Z" }, - { url = "https://files.pythonhosted.org/packages/be/03/a3dd6470fc76499959b00ae56295b76b4bdf7c6ffc60d62006b1217567e1/rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c", size = 554211, upload-time = "2025-08-07T08:24:40.6Z" }, - { url = "https://files.pythonhosted.org/packages/bf/d1/ee5fd1be395a07423ac4ca0bcc05280bf95db2b155d03adefeb47d5ebf7e/rpds_py-0.27.0-cp313-cp313t-win32.whl", hash = "sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23", size = 216624, upload-time = "2025-08-07T08:24:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/1c/94/4814c4c858833bf46706f87349c37ca45e154da7dbbec9ff09f1abeb08cc/rpds_py-0.27.0-cp313-cp313t-win_amd64.whl", hash = "sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1", size = 230007, upload-time = "2025-08-07T08:24:43.329Z" }, -] - -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, ] [[package]] name = "ruff" -version = "0.12.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/45/2e403fa7007816b5fbb324cb4f8ed3c7402a927a0a0cb2b6279879a8bfdc/ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", size = 5254702, upload-time = "2025-08-14T16:08:55.2Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/20/53bf098537adb7b6a97d98fcdebf6e916fcd11b2e21d15f8c171507909cc/ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e", size = 11759705, upload-time = "2025-08-14T16:08:12.968Z" }, - { url = "https://files.pythonhosted.org/packages/20/4d/c764ee423002aac1ec66b9d541285dd29d2c0640a8086c87de59ebbe80d5/ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", size = 12527042, upload-time = "2025-08-14T16:08:16.54Z" }, - { url = "https://files.pythonhosted.org/packages/8b/45/cfcdf6d3eb5fc78a5b419e7e616d6ccba0013dc5b180522920af2897e1be/ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", size = 11724457, upload-time = "2025-08-14T16:08:18.686Z" }, - { url = "https://files.pythonhosted.org/packages/72/e6/44615c754b55662200c48bebb02196dbb14111b6e266ab071b7e7297b4ec/ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", size = 11949446, upload-time = "2025-08-14T16:08:21.059Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d1/9b7d46625d617c7df520d40d5ac6cdcdf20cbccb88fad4b5ecd476a6bb8d/ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", size = 11566350, upload-time = "2025-08-14T16:08:23.433Z" }, - { url = "https://files.pythonhosted.org/packages/59/20/b73132f66f2856bc29d2d263c6ca457f8476b0bbbe064dac3ac3337a270f/ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", size = 13270430, upload-time = "2025-08-14T16:08:25.837Z" }, - { url = "https://files.pythonhosted.org/packages/a2/21/eaf3806f0a3d4c6be0a69d435646fba775b65f3f2097d54898b0fd4bb12e/ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", size = 14264717, upload-time = "2025-08-14T16:08:27.907Z" }, - { url = "https://files.pythonhosted.org/packages/d2/82/1d0c53bd37dcb582b2c521d352fbf4876b1e28bc0d8894344198f6c9950d/ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", size = 13684331, upload-time = "2025-08-14T16:08:30.352Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2f/1c5cf6d8f656306d42a686f1e207f71d7cebdcbe7b2aa18e4e8a0cb74da3/ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", size = 12739151, upload-time = "2025-08-14T16:08:32.55Z" }, - { url = "https://files.pythonhosted.org/packages/47/09/25033198bff89b24d734e6479e39b1968e4c992e82262d61cdccaf11afb9/ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", size = 12954992, upload-time = "2025-08-14T16:08:34.816Z" }, - { url = "https://files.pythonhosted.org/packages/52/8e/d0dbf2f9dca66c2d7131feefc386523404014968cd6d22f057763935ab32/ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", size = 12899569, upload-time = "2025-08-14T16:08:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b614d7c08515b1428ed4d3f1d4e3d687deffb2479703b90237682586fa66/ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", size = 11751983, upload-time = "2025-08-14T16:08:39.314Z" }, - { url = "https://files.pythonhosted.org/packages/58/d6/383e9f818a2441b1a0ed898d7875f11273f10882f997388b2b51cb2ae8b5/ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", size = 11538635, upload-time = "2025-08-14T16:08:41.297Z" }, - { url = "https://files.pythonhosted.org/packages/20/9c/56f869d314edaa9fc1f491706d1d8a47747b9d714130368fbd69ce9024e9/ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", size = 12534346, upload-time = "2025-08-14T16:08:43.39Z" }, - { url = "https://files.pythonhosted.org/packages/bd/4b/d8b95c6795a6c93b439bc913ee7a94fda42bb30a79285d47b80074003ee7/ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", size = 13017021, upload-time = "2025-08-14T16:08:45.889Z" }, - { url = "https://files.pythonhosted.org/packages/c7/c1/5f9a839a697ce1acd7af44836f7c2181cdae5accd17a5cb85fcbd694075e/ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", size = 11734785, upload-time = "2025-08-14T16:08:48.062Z" }, - { url = "https://files.pythonhosted.org/packages/fa/66/cdddc2d1d9a9f677520b7cfc490d234336f523d4b429c1298de359a3be08/ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", size = 12840654, upload-time = "2025-08-14T16:08:50.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/fd/669816bc6b5b93b9586f3c1d87cd6bc05028470b3ecfebb5938252c47a35/ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", size = 11949623, upload-time = "2025-08-14T16:08:52.233Z" }, +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, ] [[package]] @@ -1622,67 +1643,69 @@ wheels = [ [[package]] name = "scipy" -version = "1.16.1" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, - { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, - { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, - { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, - { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, - { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, - { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, - { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, - { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, - { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, - { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, - { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, ] [[package]] name = "setuptools" -version = "80.9.0" +version = "82.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] [[package]] name = "shapely" -version = "2.1.1" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107, upload-time = "2025-05-19T11:04:19.736Z" }, - { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355, upload-time = "2025-05-19T11:04:21.035Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871, upload-time = "2025-05-19T11:04:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830, upload-time = "2025-05-19T11:04:23.997Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961, upload-time = "2025-05-19T11:04:25.702Z" }, - { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623, upload-time = "2025-05-19T11:04:27.171Z" }, - { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916, upload-time = "2025-05-19T11:04:28.405Z" }, - { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746, upload-time = "2025-05-19T11:04:29.643Z" }, - { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482, upload-time = "2025-05-19T11:04:30.852Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256, upload-time = "2025-05-19T11:04:32.068Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614, upload-time = "2025-05-19T11:04:33.7Z" }, - { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542, upload-time = "2025-05-19T11:04:34.952Z" }, - { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961, upload-time = "2025-05-19T11:04:36.32Z" }, - { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514, upload-time = "2025-05-19T11:04:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607, upload-time = "2025-05-19T11:04:38.925Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644, upload-time = "2025-09-24T13:50:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887, upload-time = "2025-09-24T13:50:46.735Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931, upload-time = "2025-09-24T13:50:48.374Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855, upload-time = "2025-09-24T13:50:50.037Z" }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960, upload-time = "2025-09-24T13:50:51.74Z" }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851, upload-time = "2025-09-24T13:50:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890, upload-time = "2025-09-24T13:50:55.337Z" }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151, upload-time = "2025-09-24T13:50:57.153Z" }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130, upload-time = "2025-09-24T13:50:58.49Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802, upload-time = "2025-09-24T13:50:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460, upload-time = "2025-09-24T13:51:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223, upload-time = "2025-09-24T13:51:04.472Z" }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760, upload-time = "2025-09-24T13:51:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078, upload-time = "2025-09-24T13:51:08.584Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178, upload-time = "2025-09-24T13:51:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756, upload-time = "2025-09-24T13:51:12.105Z" }, ] [[package]] @@ -1706,27 +1729,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + [[package]] name = "structlog" -version = "25.4.0" +version = "25.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b9/6e672db4fec07349e7a8a8172c1a6ae235c58679ca29c3f86a61b5e59ff3/structlog-25.4.0.tar.gz", hash = "sha256:186cd1b0a8ae762e29417095664adf1d6a31702160a46dacb7796ea82f7409e4", size = 1369138, upload-time = "2025-06-02T08:21:12.971Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/52/9ba0f43b686e7f3ddfeaa78ac3af750292662284b3661e91ad5494f21dbc/structlog-25.5.0.tar.gz", hash = "sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98", size = 1460830, upload-time = "2025-10-27T08:28:23.028Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/4a/97ee6973e3a73c74c8120d59829c3861ea52210667ec3e7a16045c62b64d/structlog-25.4.0-py3-none-any.whl", hash = "sha256:fe809ff5c27e557d14e613f45ca441aabda051d119ee5a0102aaba6ce40eed2c", size = 68720, upload-time = "2025-06-02T08:21:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, ] [[package]] name = "termcolor" -version = "3.1.0" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, ] [[package]] name = "testcontainers" -version = "4.12.0" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docker" }, @@ -1735,57 +1767,84 @@ dependencies = [ { name = "urllib3" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/62/01d9f648e9b943175e0dcddf749cf31c769665d8ba08df1e989427163f33/testcontainers-4.12.0.tar.gz", hash = "sha256:13ee89cae995e643f225665aad8b200b25c4f219944a6f9c0b03249ec3f31b8d", size = 66631, upload-time = "2025-07-21T20:32:26.37Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/ac/a597c3a0e02b26cbed6dd07df68be1e57684766fd1c381dee9b170a99690/testcontainers-4.14.2.tar.gz", hash = "sha256:1340ccf16fe3acd9389a6c9e1d9ab21d9fe99a8afdf8165f89c3e69c1967d239", size = 166841, upload-time = "2026-03-18T05:19:16.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/2d/26b8b30067d94339afee62c3edc9b803a6eb9332f521ba77d8aaab5de873/testcontainers-4.14.2-py3-none-any.whl", hash = "sha256:0d0522c3cd8f8d9627cda41f7a6b51b639fa57bdc492923c045117933c668d68", size = 125712, upload-time = "2026-03-18T05:19:15.29Z" }, +] + +[[package]] +name = "types-html5lib" +version = "1.1.11.20260408" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/59/914d00107c770e49fa57d4c4572e0371bbce14321385fd2ea3e06691b62d/types_html5lib-1.1.11.20260408.tar.gz", hash = "sha256:8a281aa367bc77dbc758358cd9bef79530f2d154eeed9b33705bb035a0dab9e4", size = 18316, upload-time = "2026-04-08T04:35:49.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/19/12d95e98e42e120522665ec6850b38df8d2c1cca94e21c4d7f8578acb64e/types_html5lib-1.1.11.20260408-py3-none-any.whl", hash = "sha256:d18dc4b90d6d6745585790b920db13ede43e1f8ff6ee1ac0ceb0dec4223a06fa", size = 24313, upload-time = "2026-04-08T04:35:48.679Z" }, +] + +[[package]] +name = "types-lxml" +version = "2026.2.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "cssselect" }, + { name = "types-html5lib" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/ad/c70ac8cbdc28eb58a17301c69b4925af54b614e47f9b2ebc9de5cc10f786/types_lxml-2026.2.16.tar.gz", hash = "sha256:b3a1340cc06db98d541c785732f6f68bea438daff4e2b7809ef748d545d01406", size = 161204, upload-time = "2026-02-17T02:34:50.855Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/e8/9e2c392e5d671afda47b917597cac8fde6a452f5776c4c9ceb93fbd2889f/testcontainers-4.12.0-py3-none-any.whl", hash = "sha256:26caef57e642d5e8c5fcc593881cf7df3ab0f0dc9170fad22765b184e226ab15", size = 111791, upload-time = "2025-07-21T20:32:25.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/03ec9befbf4bb5309bfd576c6a5ac1c75633f78f6b64cf1f594e97cd3d23/types_lxml-2026.2.16-py3-none-any.whl", hash = "sha256:5dd81ffa54830e5f361988737c5f1d6a0ae48b2742790637ec560df790ea0401", size = 97040, upload-time = "2026-02-17T02:34:49.286Z" }, ] [[package]] -name = "types-python-dateutil" -version = "2.9.0.20250809" +name = "types-webencodings" +version = "0.5.0.20260408" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/53/07dac71db45fb6b3c71c2fd29a87cada2239eac7ecfb318e6ebc7da00a3b/types_python_dateutil-2.9.0.20250809.tar.gz", hash = "sha256:69cbf8d15ef7a75c3801d65d63466e46ac25a0baa678d89d0a137fc31a608cc1", size = 15820, upload-time = "2025-08-09T03:14:14.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/d2/21567fac142315580ce3ee37d08a4e8819921dae833bbcd27b9f8b373799/types_webencodings-0.5.0.20260408.tar.gz", hash = "sha256:28c596619f367e43eee393d85f63e8d2fdb6874c654a8d441c37f8afe29c6d0d", size = 7504, upload-time = "2026-04-08T04:28:51.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/5e/67312e679f612218d07fcdbd14017e6d571ce240a5ba1ad734f15a8523cc/types_python_dateutil-2.9.0.20250809-py3-none-any.whl", hash = "sha256:768890cac4f2d7fd9e0feb6f3217fce2abbfdfc0cadd38d11fba325a815e4b9f", size = 17707, upload-time = "2025-08-09T03:14:13.314Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e4/f13be8f6d9a561166f7d963012d0ccc833e13aee3044c4f1a8fb1fee462a/types_webencodings-0.5.0.20260408-py3-none-any.whl", hash = "sha256:19a2afe5c22d9b1e880b49ff823c7b531f473a390fe47ac903c0bdb5cd677dd9", size = 8717, upload-time = "2026-04-08T04:28:50.943Z" }, ] [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "tzdata" -version = "2025.2" +version = "2026.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, ] [[package]] name = "uas-standards" -version = "4.0.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "implicitdict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/b1/1bfe5e08436faf65b7e0eef795577cec9177dd4a3cb318faafa9fdcd2568/uas_standards-4.0.0.tar.gz", hash = "sha256:77bde013efe5be472e9099c9f55e005439686d5192ed6ea4e4109aa62104a92e", size = 116943, upload-time = "2025-08-19T15:46:30.865Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/e3/e5124aa4c6fcebb22f5f1b5baa0f055366e7e4c5ff73470daa211141c2e1/uas_standards-4.3.0.tar.gz", hash = "sha256:f3339deaad4a356ddeb0011192b9634ad89b12a1a7b43fa589fb39c60bb0306e", size = 131017, upload-time = "2026-04-13T16:26:39.236Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/70/dce704cba3631141f0da26d9f26e033413f4e6ad09c9132ff0366a418d7e/uas_standards-4.0.0-py3-none-any.whl", hash = "sha256:d4f163a4b96a673371d2cf081ab70b50b50423b8600fa71ed39be2d3f7cf016c", size = 83522, upload-time = "2025-08-19T15:46:29.699Z" }, + { url = "https://files.pythonhosted.org/packages/5e/99/506e8e98f6cc986dcc2977d3c6a848470b06218cc3f25178f69925797613/uas_standards-4.3.0-py3-none-any.whl", hash = "sha256:b5d69e323aada665b4295c3cb730023e80b080f14da29b8ae7102a42c10d6053", size = 90296, upload-time = "2026-04-13T16:26:38.021Z" }, ] [[package]] name = "urllib3" -version = "2.5.0" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] @@ -1799,32 +1858,35 @@ wheels = [ [[package]] name = "websocket-client" -version = "1.8.0" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] name = "werkzeug" -version = "3.1.3" +version = "3.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, ] [[package]] name = "wheel" -version = "0.45.1" +version = "0.47.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, + { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, ] [[package]] @@ -1838,108 +1900,117 @@ wheels = [ [[package]] name = "wrapt" -version = "1.17.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, + { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, + { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, + { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, + { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, + { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, + { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, ] [[package]] name = "wsproto" -version = "1.2.0" +version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, ] [[package]] name = "yarl" -version = "1.20.1" +version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, - { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, - { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, - { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, - { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, - { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, - { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, - { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, - { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, - { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, - { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, - { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, - { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, - { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, - { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, - { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] [[package]] name = "zope-event" -version = "5.1.1" +version = "6.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/9f/c443569a68d3844c044d9fa9711e08adb33649b527b4d432433f4c2a6a02/zope_event-5.1.1.tar.gz", hash = "sha256:c1ac931abf57efba71a2a313c5f4d57768a19b15c37e3f02f50eb1536be12d4e", size = 18811, upload-time = "2025-07-22T07:04:00.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/41/faa10af34d48d9cd6fa0249a1162943ad84a9590bd1a06939981e6640416/zope_event-6.2.tar.gz", hash = "sha256:b97d5d6327067ee6b9dfcbdf606ade9ade70991e19c162e808ea39e5fcf0f8d3", size = 18958, upload-time = "2026-04-28T06:24:10.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/04/fd55695f6448abd22295fc68b2d3a135389558f0f49a24b0dffe019d0ecb/zope_event-5.1.1-py3-none-any.whl", hash = "sha256:8d5ea7b992c42ce73a6fa9c2ba99a004c52cd9f05d87f3220768ef0329b92df7", size = 7014, upload-time = "2025-07-22T07:03:59.9Z" }, + { url = "https://files.pythonhosted.org/packages/9e/33/848922889e946d4befc415c219fe516af75c49555d8e736e183bfd30db42/zope_event-6.2-py3-none-any.whl", hash = "sha256:5e755153ac4faf64c10a4b6dd3307680166a3edf65b38df22df592610f8fa874", size = 6525, upload-time = "2026-04-28T06:24:09.176Z" }, ] [[package]] name = "zope-interface" -version = "7.2" +version = "8.4" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/65/34a6e6e4dfa260c4c55ee02bb2fc53625e126ff0181485286cf0c9d453d6/zope_interface-8.4.tar.gz", hash = "sha256:9dbee7925a23aa6349738892c911019d4095a96cff487b743482073ecbc174a8", size = 257736, upload-time = "2026-04-25T07:22:10.439Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/3b/e309d731712c1a1866d61b5356a069dd44e5b01e394b6cb49848fa2efbff/zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98", size = 208961, upload-time = "2024-11-28T08:48:29.865Z" }, - { url = "https://files.pythonhosted.org/packages/49/65/78e7cebca6be07c8fc4032bfbb123e500d60efdf7b86727bb8a071992108/zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d", size = 209356, upload-time = "2024-11-28T08:48:33.297Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/627384b745310d082d29e3695db5f5a9188186676912c14b61a78bbc6afe/zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c", size = 264196, upload-time = "2024-11-28T09:18:17.584Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f6/54548df6dc73e30ac6c8a7ff1da73ac9007ba38f866397091d5a82237bd3/zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398", size = 259237, upload-time = "2024-11-28T08:48:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" }, - { url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6a/a08c62bc1fa0e34fe7b8b401646cba4817427c716bfbef6cc88937cd327f/zope_interface-8.4-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:cd55965d715413038774aead54851bc3dbdd74a69f3ce30252182a94407b9905", size = 211924, upload-time = "2026-04-25T07:28:22.219Z" }, + { url = "https://files.pythonhosted.org/packages/50/30/2011f17e00ff078658bc317e1f7eccd7843fc1ce60695b665b0a52c45c1b/zope_interface-8.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0d88c1f106a4f06e074a3ada2d20f4a602e3f2871c4f55726ed5d91e94ec19b1", size = 211995, upload-time = "2026-04-25T07:28:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/25/f3/a16fe884571cfa89271412dbb40def6d6865824428d1e14785a82795100c/zope_interface-8.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:36c575356732d59ffd3279ad67e302a6fe517e67db5b061b36b377ee0fa016c4", size = 264443, upload-time = "2026-04-25T07:28:26.401Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/e08923fcd8a8c8704af05a90418b07cd897ac90865925b37d7ad8139adfa/zope_interface-8.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:29f09ec8bda65f7b30294328070070a2590b90f252f834ee0817cdb0e2c35f6a", size = 269626, upload-time = "2026-04-25T07:28:28.423Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/96c94cd307f9946d0b0f03402a335f7aae7b4f0b129b5734cc56cc78cb65/zope_interface-8.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2bc388cebcb753d21eaf2a0481fd6f0ce6840a47300a40dcec0b56bac27d0f97", size = 269583, upload-time = "2026-04-25T07:28:30.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d4/7e9fcc8bb0dba5d023b9fca92035d68c018457cc550e9d51746670b76a6b/zope_interface-8.4-cp313-cp313-win_amd64.whl", hash = "sha256:3e5866917ccb57d929e515a1136d729bd3fa4f367965fb16e38a4bc72cb05521", size = 214422, upload-time = "2026-04-25T07:28:32.201Z" }, + { url = "https://files.pythonhosted.org/packages/16/26/b0bcde302f6a4c155d047a8ab5cba1003363031919d6e8f3bcdc139c28a6/zope_interface-8.4-cp313-cp313-win_arm64.whl", hash = "sha256:f1f854bef8bc137519e4413bcc1322d55faad28b20b3ca39f7bec49d2f1b26df", size = 213029, upload-time = "2026-04-25T07:28:34.677Z" }, ]