fix(prediction): correct dataset mapping
This commit is contained in:
parent
7f70ebdd96
commit
a53adc4585
3 changed files with 107 additions and 3 deletions
74
stratoflights_api/test_launch_bounds.py
Normal file
74
stratoflights_api/test_launch_bounds.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
from django.test import SimpleTestCase
|
||||
|
||||
from .serializers import PredictionRequestSerializer
|
||||
|
||||
|
||||
class LaunchLongitudeTests(SimpleTestCase):
|
||||
"""
|
||||
This API does not bound longitude. The predictor does.
|
||||
|
||||
Longitude lives on a circle: every real number names a real meridian, and
|
||||
5170 is a legitimate way to write 50 E. So any interval is a policy about
|
||||
typos, not about places, and there is exactly one place that policy belongs —
|
||||
the service that owns the wind grid. Two layers each guessing at it is how the
|
||||
original defect happened: this serializer declared min_value=0, narrower than
|
||||
the endpoint it calls, and refused every launch west of Greenwich. Canada,
|
||||
Greenland and Alaska, i.e. most of the Arctic sites this product targets.
|
||||
|
||||
The endpoint actually called is GET /api/v1/prediction, which applies no
|
||||
longitude bound at all — it normalises and lets the grid refuse what it cannot
|
||||
use. Mirroring that exactly is the point: this layer must never be the narrower
|
||||
one.
|
||||
|
||||
Nuuk is -51.7. The same meridian written unsigned is 308.3, and that always
|
||||
worked, which is what showed the constraint was about notation, not place.
|
||||
|
||||
Non-numbers are still refused here, by FloatField. NaN and infinity are not:
|
||||
FloatField parses them, and the predictor's request decoder rejects them with a
|
||||
400 (measured). Deliberately not re-checked here, so this layer keeps a single
|
||||
responsibility.
|
||||
"""
|
||||
|
||||
def payload(self, lng):
|
||||
return {
|
||||
"launch_latitude": 64.1,
|
||||
"launch_longitude": lng,
|
||||
"launch_datetime": "2026-08-05T12:00:00Z",
|
||||
"launch_altitude": 0,
|
||||
"ascent_rate": 5,
|
||||
"burst_altitude": 30000,
|
||||
"descent_rate": 5,
|
||||
"profile": "standard_profile",
|
||||
}
|
||||
|
||||
def assert_accepted(self, lng):
|
||||
s = PredictionRequestSerializer(data=self.payload(lng))
|
||||
self.assertTrue(s.is_valid(), f"lng={lng} rejected: {s.errors}")
|
||||
|
||||
def test_accepts_a_western_launch(self):
|
||||
self.assert_accepted(-51.7)
|
||||
|
||||
def test_accepts_the_same_meridian_written_unsigned(self):
|
||||
self.assert_accepted(308.3)
|
||||
|
||||
def test_accepts_every_notation_the_predictor_accepts(self):
|
||||
# Measured against GET /api/v1/prediction: each of these returns 200, and
|
||||
# the pairs below name the same meridian, so each returns the same
|
||||
# trajectory as its twin. -180/180 are one meridian; 0/360 are one
|
||||
# meridian; -90/270 are one meridian.
|
||||
for lng in (-180, 180, 0, 360, -90, 270, -200, -180.0001, 359.999):
|
||||
with self.subTest(lng=lng):
|
||||
self.assert_accepted(lng)
|
||||
|
||||
def test_does_not_bound_the_range(self):
|
||||
# 1e30 is nonsense, and it is the predictor that says so — it answers 400
|
||||
# "lng=1e+30 out of range". Asserting acceptance here is the explicit
|
||||
# statement that range is not this layer's job; a bound added here would
|
||||
# again risk being narrower than the service behind it.
|
||||
self.assert_accepted(1e30)
|
||||
|
||||
def test_still_refuses_things_that_are_not_numbers(self):
|
||||
for lng in ("abc", None, "", []):
|
||||
with self.subTest(lng=lng):
|
||||
s = PredictionRequestSerializer(data=self.payload(lng))
|
||||
self.assertFalse(s.is_valid(), f"lng={lng!r} accepted but is not a number")
|
||||
Loading…
Add table
Add a link
Reference in a new issue