1111from monitoring .mock_uss .app import webapp
1212from monitoring .mock_uss .config import KEY_BASE_URL
1313from monitoring .mock_uss .f3548v21 import utm_client
14- from monitoring .mock_uss .flights .database import FlightRecord , db
14+ from monitoring .mock_uss .flights .database import Database , FlightRecord , db
1515from monitoring .monitorlib .clients import scd as scd_client
1616from monitoring .monitorlib .clients .flight_planning .flight_info import FlightInfo
1717from monitoring .monitorlib .fetch import QueryError
@@ -104,9 +104,10 @@ def conflicts_with_flightrecords(
104104 )
105105
106106 for other_flight in flights :
107- if not other_flight :
107+ if not other_flight or not other_flight . op_intent :
108108 continue
109109
110+ # TODO(mock_uss_flight_id): Use flight ID that is independent of op_intent
110111 if other_flight .op_intent .reference .id == op_intent .reference .id : # Same flight
111112 continue
112113
@@ -158,6 +159,7 @@ def log(msg):
158159 for op_intent in op_intents :
159160 if (
160161 existing_flight
162+ and existing_flight .op_intent
161163 and existing_flight .op_intent .reference .id == op_intent .reference .id
162164 ):
163165 log (
@@ -189,11 +191,12 @@ def log(msg):
189191
190192 modifying_activated = (
191193 existing_flight
194+ and existing_flight .op_intent
192195 and existing_flight .op_intent .reference .state
193196 == scd_api .OperationalIntentState .Activated
194197 and op_intent .reference .state == scd_api .OperationalIntentState .Activated
195198 )
196- if modifying_activated :
199+ if modifying_activated and existing_flight and existing_flight . op_intent :
197200 preexisting_conflict = Volume4DCollection .from_interuss_scd_api (
198201 existing_flight .op_intent .details .volumes
199202 ).intersects_vol4s (v2 )
@@ -216,22 +219,24 @@ def op_intent_transition_valid(
216219 transition_to : scd_api .OperationalIntentState | None ,
217220) -> bool :
218221 valid_states = {
222+ None ,
219223 scd_api .OperationalIntentState .Accepted ,
220224 scd_api .OperationalIntentState .Activated ,
221225 scd_api .OperationalIntentState .Nonconforming ,
222226 scd_api .OperationalIntentState .Contingent ,
223227 }
224- if transition_from is not None and transition_from not in valid_states :
228+ if transition_from not in valid_states :
225229 raise ValueError (
226230 f"Cannot transition from state { transition_from } as it is an invalid operational intent state"
227231 )
228- if transition_to is not None and transition_to not in valid_states :
232+ if transition_to not in valid_states :
229233 raise ValueError (
230234 f"Cannot transition to state { transition_to } as it is an invalid operational intent state"
231235 )
232236
233237 if transition_from is None :
234238 return transition_to in {
239+ None ,
235240 scd_api .OperationalIntentState .Accepted ,
236241 scd_api .OperationalIntentState .Activated ,
237242 }
@@ -385,6 +390,10 @@ def op_intent_from_flightinfo(
385390def op_intent_from_flightrecord (
386391 flight : FlightRecord , method : str
387392) -> f3548_v21 .OperationalIntent :
393+ if not flight .op_intent :
394+ raise RuntimeError (
395+ "op_intent_from_flightrecord was called with a FlightRecord containing no op intent"
396+ )
388397 ref = flight .op_intent .reference
389398 details = f3548_v21 .OperationalIntentDetails (
390399 volumes = flight .op_intent .details .volumes ,
@@ -423,9 +432,13 @@ def query_operational_intents(
423432 op_intent_refs = scd_client .query_operational_intent_references (
424433 utm_client , area_of_interest
425434 )
426- tx = db .value
435+ dbcontent : Database = db .value
427436 get_details_for = []
428- own_flights = {f .op_intent .reference .id : f for f in tx .flights .values () if f }
437+ own_flights = {
438+ f .op_intent .reference .id : f
439+ for f in dbcontent .flights .values ()
440+ if f and f .op_intent
441+ }
429442 result = []
430443 for op_intent_ref in op_intent_refs :
431444 if op_intent_ref .id in own_flights :
@@ -434,12 +447,12 @@ def query_operational_intents(
434447 op_intent_from_flightrecord (own_flights [op_intent_ref .id ], "GET" )
435448 )
436449 elif (
437- op_intent_ref .id in tx .cached_operations
438- and tx .cached_operations [op_intent_ref .id ].reference .version
450+ op_intent_ref .id in dbcontent .cached_operations
451+ and dbcontent .cached_operations [op_intent_ref .id ].reference .version
439452 == op_intent_ref .version
440453 ):
441454 # We have a current version of this op intent cached
442- result .append (tx .cached_operations [op_intent_ref .id ])
455+ result .append (dbcontent .cached_operations [op_intent_ref .id ])
443456 else :
444457 # We need to get the details for this op intent
445458 get_details_for .append (op_intent_ref )
@@ -537,57 +550,63 @@ def check_op_intent(
537550 # Check the transition is valid
538551 state_transition_from = (
539552 f3548_v21 .OperationalIntentState (existing_flight .op_intent .reference .state )
540- if existing_flight
553+ if existing_flight and existing_flight . op_intent
541554 else None
542555 )
543556 state_transition_to = f3548_v21 .OperationalIntentState (
544- new_flight .op_intent .reference .state
557+ new_flight .op_intent .reference .state if new_flight . op_intent else None
545558 )
546559 if not op_intent_transition_valid (state_transition_from , state_transition_to ):
547560 raise PlanningError (
548561 f"Operational intent state transition from { state_transition_from } to { state_transition_to } is invalid"
549562 )
550563
551- # Check the priority is allowed in the locality
552- priority = priority_of (new_flight .op_intent .details )
553- if (
554- priority > locality .highest_priority ()
555- or priority <= locality .lowest_bound_priority ()
556- ):
557- raise PlanningError (
558- f"Operational intent priority { priority } is outside the bounds of the locality priority range (]{ locality .lowest_bound_priority ()} ,{ locality .highest_priority ()} ])"
559- )
564+ if new_flight .op_intent :
565+ # Check the priority is allowed in the locality
566+ priority = priority_of (new_flight .op_intent .details )
567+ if (
568+ priority > locality .highest_priority ()
569+ or priority <= locality .lowest_bound_priority ()
570+ ):
571+ raise PlanningError (
572+ f"Operational intent priority { priority } is outside the bounds of the locality priority range (]{ locality .lowest_bound_priority ()} ,{ locality .highest_priority ()} ])"
573+ )
560574
561- if new_flight .op_intent .reference .state in (
562- f3548_v21 .OperationalIntentState .Accepted ,
563- f3548_v21 .OperationalIntentState .Activated ,
564- ):
565- # Check for intersections if the flight is nominal
575+ if new_flight .op_intent .reference .state in (
576+ f3548_v21 .OperationalIntentState .Accepted ,
577+ f3548_v21 .OperationalIntentState .Activated ,
578+ ):
579+ # Check for intersections if the flight is nominal
566580
567- # Check for operational intents in the DSS
568- log ("Obtaining latest operational intent information" )
569- v1 = Volume4DCollection .from_interuss_scd_api (
570- new_flight .op_intent .details .volumes
571- + new_flight .op_intent .details .off_nominal_volumes
572- )
573- vol4 = v1 .bounding_volume .to_f3548v21 ()
574- op_intents = query_operational_intents (locality , vol4 )
581+ # Check for operational intents in the DSS
582+ log ("Obtaining latest operational intent information" )
583+ v1 = Volume4DCollection .from_f3548v21 (
584+ ( new_flight .op_intent .details .volumes or [])
585+ + ( new_flight .op_intent .details .off_nominal_volumes or [])
586+ )
587+ vol4 = v1 .bounding_volume .to_f3548v21 ()
588+ op_intents = query_operational_intents (locality , vol4 )
575589
576- # Check for intersections
577- log (
578- f"Checking for intersections with { ', ' .join (op_intent .reference .id for op_intent in op_intents )} "
579- )
580- has_conflicts = check_for_conflicts (
581- new_flight .op_intent , existing_flight , op_intents , locality , log
582- )
590+ # Check for intersections
591+ log (
592+ f"Checking for intersections with { ', ' .join (op_intent .reference .id for op_intent in op_intents )} "
593+ )
594+ has_conflicts = check_for_conflicts (
595+ new_flight .op_intent , existing_flight , op_intents , locality , log
596+ )
597+
598+ key = [
599+ f3548_v21 .EntityOVN (op .reference .ovn )
600+ for op in op_intents
601+ if op .reference .ovn is not None
602+ ]
603+ else :
604+ # Flight is not nominal and therefore doesn't need to check intersections
605+ key = []
606+ has_conflicts = False
583607
584- key = [
585- f3548_v21 .EntityOVN (op .reference .ovn )
586- for op in op_intents
587- if op .reference .ovn is not None
588- ]
589608 else :
590- # Flight is not nominal and therefore doesn't need to check intersections
609+ # Flight does not have an op intent
591610 key = []
592611 has_conflicts = False
593612
@@ -611,6 +630,10 @@ def share_op_intent(
611630 * ConnectionError
612631 * requests.exceptions.ConnectionError
613632 """
633+ if not new_flight .op_intent :
634+ raise RuntimeError (
635+ "share_op_intent called with new_flight that is missing an op_intent"
636+ )
614637 # Create operational intent in DSS
615638 log ("Sharing operational intent with DSS" )
616639 base_url = new_flight .op_intent .reference .uss_base_url
@@ -624,7 +647,7 @@ def share_op_intent(
624647 uss_base_url = base_url
625648 ),
626649 )
627- if existing_flight :
650+ if existing_flight and existing_flight . op_intent :
628651 id = existing_flight .op_intent .reference .id
629652 log (f"Updating existing operational intent { id } in DSS" )
630653 result = scd_client .update_operational_intent_reference (
0 commit comments