From 9493fffff5d62c954a329b1e289e9cc7b25c1775 Mon Sep 17 00:00:00 2001 From: Raphael Bitton Date: Tue, 30 Sep 2025 18:26:14 -0500 Subject: [PATCH] added more flight logic --- ...uipment_air_time_hours_equipment_cycles.py | 23 +++++++++++++ simulator/models/aircraft.py | 12 +++++-- simulator/models/flight.py | 34 ++++++++++++++----- 3 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 simulator/migrations/0003_equipment_air_time_hours_equipment_cycles.py diff --git a/simulator/migrations/0003_equipment_air_time_hours_equipment_cycles.py b/simulator/migrations/0003_equipment_air_time_hours_equipment_cycles.py new file mode 100644 index 0000000..fde6d18 --- /dev/null +++ b/simulator/migrations/0003_equipment_air_time_hours_equipment_cycles.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.6 on 2025-09-30 23:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('simulator', '0002_equipment_base_location'), + ] + + operations = [ + migrations.AddField( + model_name='equipment', + name='air_time_hours', + field=models.DecimalField(decimal_places=2, default=0, help_text='Total air time in hours', max_digits=10), + ), + migrations.AddField( + model_name='equipment', + name='cycles', + field=models.PositiveIntegerField(default=0, help_text='Total number of flight cycles (takeoff/landing pairs)'), + ), + ] diff --git a/simulator/models/aircraft.py b/simulator/models/aircraft.py index 9a89b70..3f3c67b 100644 --- a/simulator/models/aircraft.py +++ b/simulator/models/aircraft.py @@ -26,6 +26,15 @@ class Equipment(models.Model): null=True, blank=True, ) + cycles = models.PositiveIntegerField( + default=0, help_text="Total number of flight cycles (takeoff/landing pairs)" + ) + air_time_hours = models.DecimalField( + max_digits=10, + decimal_places=2, + default=0, + help_text="Total air time in hours", + ) def __str__(self): return f"{self.registration} ({self.model})" @@ -38,7 +47,6 @@ class Equipment(models.Model): now = timezone.now() - # Get the most recent flight relative to now last_flight = ( self.flights.filter(departure_time__lte=now) .order_by("-departure_time") @@ -46,10 +54,8 @@ class Equipment(models.Model): ) if last_flight: - # Check if plane is currently in flight if last_flight.departure_time <= now < last_flight.arrival_time: return "In-Flight" - # Plane has arrived at destination elif last_flight.arrival_time <= now: return last_flight.destination diff --git a/simulator/models/flight.py b/simulator/models/flight.py index 46500ff..77ccaf8 100644 --- a/simulator/models/flight.py +++ b/simulator/models/flight.py @@ -7,6 +7,7 @@ from simulator.utils import haversine_nm from datetime import timedelta from django.core.exceptions import ValidationError from django.utils import timezone +from decimal import Decimal class Flight(models.Model): @@ -49,7 +50,7 @@ class Flight(models.Model): @property def duration(self) -> timedelta: - speed = self.equipment.model.cruise_speed_kt # knots = nm/hr + speed = self.equipment.model.cruise_speed_kt hours = self.distance_nm / speed return timedelta(hours=hours) @@ -58,15 +59,23 @@ class Flight(models.Model): return self.departure_time + self.duration def clean(self): - # Validate flight distance is within aircraft range + # Validate origin and destination are different + if self.origin == self.destination: + raise ValidationError("Origin and destination airports cannot be the same.") + + # Validate carrier owns the equipment + if self.equipment.owner != self.carrier: + raise ValidationError( + f"{self.equipment} is owned by {self.equipment.owner}, " + f"not {self.carrier}. Cannot assign equipment to this flight." + ) + if self.distance_nm > self.equipment.model.range_nm: raise ValidationError( f"Flight distance ({self.distance_nm:.0f} nm) exceeds {self.equipment.model} " f"maximum range ({self.equipment.model.range_nm} nm)." ) - # Validate equipment is at the origin airport - # Get the last flight before this one chronologically previous_flight = ( Flight.objects.filter( equipment=self.equipment, departure_time__lt=self.departure_time @@ -77,7 +86,6 @@ class Flight(models.Model): ) if previous_flight: - # Equipment must be at the destination of the previous flight if previous_flight.destination != self.origin: raise ValidationError( f"{self.equipment} will be at {previous_flight.destination.icao} " @@ -85,14 +93,12 @@ class Flight(models.Model): f"Cannot depart from {self.origin.icao}." ) - # Ensure enough time between arrival and next departure (at least arrival happens before departure) if previous_flight.arrival_time > self.departure_time: raise ValidationError( f"{self.equipment} arrives at {self.origin.icao} at {previous_flight.arrival_time}. " f"Cannot depart before arrival." ) else: - # No previous flights, equipment must be at base location if ( self.equipment.base_location and self.equipment.base_location != self.origin @@ -102,7 +108,6 @@ class Flight(models.Model): f"First flight must depart from base location, not {self.origin.icao}." ) - # Calculate arrival time for this flight arrival = self.arrival_time @property @@ -114,3 +119,16 @@ class Flight(models.Model): return "In-Flight" else: return "Arrived" + + def save(self, *args, **kwargs): + """Override save to update equipment cycles and air time when flight completes.""" + is_new = self.pk is None + super().save(*args, **kwargs) + + if not is_new and self.status == "Arrived": + if not hasattr(self, "_stats_updated"): + flight_hours = Decimal(str(self.duration.total_seconds() / 3600)) + self.equipment.cycles += 1 + self.equipment.air_time_hours += flight_hours + self.equipment.save(update_fields=["cycles", "air_time_hours"]) + self._stats_updated = True