I have no idea what's different

This commit is contained in:
2025-10-10 21:09:50 -05:00
parent d8ff8b3641
commit 77dae021dc
5 changed files with 233 additions and 5 deletions

23
data/planes.csv Normal file
View File

@@ -0,0 +1,23 @@
Name,Manufacturer,Range (nm),Avg. Cruise Spt (kt),Capacity (people)
737 MAX 8,Boeing,3550,449,178
737-800,Boeing,2935,449,162
A320neo,Airbus,3500,447,180
A321neo,Airbus,4000,454,206
A319,Airbus,3750,447,124
777-200ER,Boeing,7700,488,314
777-300ER,Boeing,7370,488,396
787-8,Boeing,7305,488,242
787-9,Boeing,7635,488,296
787-10,Boeing,6430,488,336
747-400,Boeing,7260,493,416
747-8I,Boeing,7730,493,467
A330-200,Airbus,7200,470,247
A330-300,Airbus,6350,470,277
A330-900neo,Airbus,6550,470,287
A350-900,Airbus,8100,488,325
A350-1000,Airbus,8000,488,369
A380-800,Airbus,8000,490,555
E175,Embraer,2200,447,78
E195-E2,Embraer,2600,447,132
CRJ900,Bombardier,1670,447,76
CRJ700,Bombardier,1378,447,66
1 Name Manufacturer Range (nm) Avg. Cruise Spt (kt) Capacity (people)
2 737 MAX 8 Boeing 3550 449 178
3 737-800 Boeing 2935 449 162
4 A320neo Airbus 3500 447 180
5 A321neo Airbus 4000 454 206
6 A319 Airbus 3750 447 124
7 777-200ER Boeing 7700 488 314
8 777-300ER Boeing 7370 488 396
9 787-8 Boeing 7305 488 242
10 787-9 Boeing 7635 488 296
11 787-10 Boeing 6430 488 336
12 747-400 Boeing 7260 493 416
13 747-8I Boeing 7730 493 467
14 A330-200 Airbus 7200 470 247
15 A330-300 Airbus 6350 470 277
16 A330-900neo Airbus 6550 470 287
17 A350-900 Airbus 8100 488 325
18 A350-1000 Airbus 8000 488 369
19 A380-800 Airbus 8000 490 555
20 E175 Embraer 2200 447 78
21 E195-E2 Embraer 2600 447 132
22 CRJ900 Bombardier 1670 447 76
23 CRJ700 Bombardier 1378 447 66

View File

@@ -1,21 +1,67 @@
altgraph==0.17.4
annotated-types==0.7.0
anyio==4.11.0
APScheduler==3.11.0
asgiref==3.9.2 asgiref==3.9.2
astroid==3.3.11 astroid==3.3.11
bottle==0.13.4
certifi==2025.8.3
contourpy==1.3.3
cycler==0.12.1
dill==0.4.0 dill==0.4.0
distro==1.9.0
Django==5.2.6 Django==5.2.6
django-apscheduler==0.7.0
django-stubs==5.2.5 django-stubs==5.2.5
django-stubs-ext==5.2.5 django-stubs-ext==5.2.5
djangorestframework==3.16.1
dotenv==0.9.9 dotenv==0.9.9
fonttools==4.60.1
geopandas==1.1.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.10
isort==6.0.1 isort==6.0.1
jiter==0.11.0
kiwisolver==1.4.9
matplotlib==3.10.6
mccabe==0.7.0 mccabe==0.7.0
mypy==1.18.2 mypy==1.18.2
mypy_extensions==1.1.0 mypy_extensions==1.1.0
networkx==3.5
numpy==2.3.3
openai==2.0.0
packaging==25.0
pandas==2.3.3
pathspec==0.12.1 pathspec==0.12.1
pillow==11.3.0
platformdirs==4.4.0 platformdirs==4.4.0
proxy_tools==0.1.0
psycopg2-binary==2.9.10
pydantic==2.11.9
pydantic_core==2.33.2
pyinstaller==6.16.0
pyinstaller-hooks-contrib==2025.9
pylint==3.3.8 pylint==3.3.8
pyogrio==0.11.1
pyparsing==3.2.5
pyproj==3.7.2
python-dateutil==2.9.0.post0
python-dotenv==1.1.1 python-dotenv==1.1.1
pytz==2025.2
pywebview==6.0
setuptools==80.9.0
shapely==2.1.2
six==1.17.0
sniffio==1.3.1
sqlparse==0.5.3 sqlparse==0.5.3
tomlkit==0.13.3 tomlkit==0.13.3
tqdm==4.67.1
types-PyYAML==6.0.12.20250915 types-PyYAML==6.0.12.20250915
types-requests==2.32.4.20250913 types-requests==2.32.4.20250913
typing-inspection==0.4.2
typing_extensions==4.15.0 typing_extensions==4.15.0
tzdata==2025.2
tzlocal==5.3.1
urllib3==2.5.0 urllib3==2.5.0

View File

@@ -1,5 +1,8 @@
from django.db import models from django.db import models
# from .flight import Flight
from django.utils import timezone
class AircraftBase(models.Model): class AircraftBase(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
@@ -22,12 +25,12 @@ class Equipment(models.Model):
"Aerodrome", "Aerodrome",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name="based_equipment", related_name="based_equipment",
help_text="Home base for this equipment", help_text="Starting location for this equipment",
null=True, null=True,
blank=True, blank=True,
) )
cycles = models.PositiveIntegerField( cycles = models.PositiveIntegerField(
default=0, help_text="Total number of flight cycles (takeoff/landing pairs)" default=0, help_text="Total number of flight cycles"
) )
air_time_hours = models.DecimalField( air_time_hours = models.DecimalField(
max_digits=10, max_digits=10,
@@ -42,8 +45,6 @@ class Equipment(models.Model):
@property @property
def current_location(self): def current_location(self):
"""Get the current location of this equipment based on flight history.""" """Get the current location of this equipment based on flight history."""
from .flight import Flight
from django.utils import timezone
now = timezone.now() now = timezone.now()
@@ -59,5 +60,4 @@ class Equipment(models.Model):
elif last_flight.arrival_time <= now: elif last_flight.arrival_time <= now:
return last_flight.destination return last_flight.destination
# No flights yet or no flights have departed, equipment is at base
return self.base_location return self.base_location

View File

@@ -141,3 +141,11 @@ class Flight(models.Model):
class Route(models.Model): class Route(models.Model):
"""regulary scheduled flights""" """regulary scheduled flights"""
carrier = models.ForeignKey(
Carrier, on_delete=models.CASCADE, related_name="routes"
)
name = models.CharField()
days_of_week = models.JSONField(
default=list, help_text="[0,1,2,3,4] for weekdays, [5,6] for weekends, etc"
)

View File

@@ -0,0 +1,151 @@
from django.db import models
from .carrier import Carrier
from .aircraft import Equipment
from .aerodrome import Aerodrome
from django.core.validators import MinValueValidator, MaxValueValidator
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):
carrier = models.ForeignKey(
Carrier, on_delete=models.CASCADE, related_name="flights"
)
equipment = models.ForeignKey(
Equipment, on_delete=models.CASCADE, related_name="flights"
)
origin = models.ForeignKey(
Aerodrome, on_delete=models.CASCADE, related_name="departures"
)
destination = models.ForeignKey(
Aerodrome, on_delete=models.CASCADE, related_name="arrivals"
)
flight_number = models.PositiveIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(9999)]
)
departure_time = models.DateTimeField()
canceled = models.BooleanField(default=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["carrier", "flight_number"], name="unique_flight_per_carrier"
)
]
def __str__(self):
return f"{self.carrier.icao}{self.flight_number} {self.origin.icao} > {self.destination.icao}"
@property
def distance_nm(self) -> float:
return haversine_nm(
self.origin.latitude,
self.origin.longitude,
self.destination.latitude,
self.destination.longitude,
)
@property
def duration(self) -> timedelta:
speed = self.equipment.model.cruise_speed_kt
hours = self.distance_nm / speed
return timedelta(hours=hours)
@property
def arrival_time(self):
return self.departure_time + self.duration
def clean(self):
if self.origin == self.destination:
raise ValidationError("Origin and destination airports cannot be the same.")
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)."
)
previous_flights = Flight.objects.filter(
equipment=self.equipment,
departure_time__lt=self.departure_time,
canceled=False,
).exclude(pk=self.pk)
previous_flight = None
latest_arrival = None
for flight in previous_flights:
if latest_arrival is None or flight.arrival_time > latest_arrival:
latest_arrival = flight.arrival_time
previous_flight = flight
if previous_flight:
if previous_flight.destination != self.origin:
raise ValidationError(
f"{self.equipment} will be at {previous_flight.destination.icao} "
f"after flight {previous_flight.carrier.icao}{previous_flight.flight_number}. "
f"Cannot depart from {self.origin.icao}."
)
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:
if (
self.equipment.base_location
and self.equipment.base_location != self.origin
):
raise ValidationError(
f"{self.equipment} is based at {self.equipment.base_location.icao}. "
f"First flight must depart from base location, not {self.origin.icao}."
)
arrival = self.arrival_time
@property
def status(self):
if self.canceled:
return "Canceled"
now = timezone.now()
if now < self.departure_time:
return "Scheduled"
elif self.departure_time <= now < self.arrival_time:
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
class Route(models.Model):
"""regulary scheduled flights"""
carrier = models.ForeignKey(
Carrier, on_delete=models.CASCADE, related_name="routes"
)
name = models.CharField()
days_of_week = models.JSONField(
default=list, help_text="[0,1,2,3,4] for weekdays, [5,6] for weekends, etc"
)