From 973fd8bdd6d29cdf49dc83d22b4dbfae9871c8dd Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 23 Jul 2026 09:51:02 -0300 Subject: [PATCH 1/2] fix(environment): default missing wind to 0 for custom_atmosphere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RocketPy's set_atmospheric_model guards pressure/temperature against None but not wind, so a custom_atmosphere environment with wind_u/wind_v left unset raised "'NoneType' object has no attribute 'shape'" and hard-failed the simulation. Default a None wind component to 0.0 (only None is replaced — real 0.0 values and wind-profile lists pass through untouched). Surfaced from the Jarvis beta audit: the web client seeds wind as null and only writes on user edit, so the default Custom-atmosphere path crashed. Jarvis now coerces null->0 on egress too; this is the belt-and-suspenders backend guard. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/services/environment.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/services/environment.py b/src/services/environment.py index 991c865..96477c3 100644 --- a/src/services/environment.py +++ b/src/services/environment.py @@ -28,13 +28,17 @@ def from_env_model(cls, env: EnvironmentModel) -> Self: elevation=env.elevation, date=env.date, ) + # RocketPy guards pressure/temperature against None but NOT wind, so a + # custom_atmosphere with wind_u/wind_v left unset raises + # "'NoneType' object has no attribute 'shape'". Default missing wind to 0 + # (only None is replaced — real 0.0 values and wind profiles pass through). rocketpy_env.set_atmospheric_model( type=env.atmospheric_model_type, file=env.atmospheric_model_file, pressure=env.pressure, temperature=env.temperature, - wind_u=env.wind_u, - wind_v=env.wind_v, + wind_u=env.wind_u if env.wind_u is not None else 0.0, + wind_v=env.wind_v if env.wind_v is not None else 0.0, ) return cls(environment=rocketpy_env) From 29a32602be23e53801d735aa9f56d6d23acec087 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Wed, 5 Aug 2026 13:36:22 -0300 Subject: [PATCH 2/2] TST: add unit tests and fix line length for wind normalization (#74) --- src/services/environment.py | 9 +- .../test_services/test_environment_service.py | 95 +++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_services/test_environment_service.py diff --git a/src/services/environment.py b/src/services/environment.py index 96477c3..3330869 100644 --- a/src/services/environment.py +++ b/src/services/environment.py @@ -28,10 +28,11 @@ def from_env_model(cls, env: EnvironmentModel) -> Self: elevation=env.elevation, date=env.date, ) - # RocketPy guards pressure/temperature against None but NOT wind, so a - # custom_atmosphere with wind_u/wind_v left unset raises - # "'NoneType' object has no attribute 'shape'". Default missing wind to 0 - # (only None is replaced — real 0.0 values and wind profiles pass through). + # RocketPy guards pressure/temperature against None but NOT wind, so + # a custom_atmosphere with wind_u/wind_v left unset raises + # "'NoneType' object has no attribute 'shape'". Default missing wind + # to 0 (only None is replaced — real 0.0 values and wind profiles pass + # through). rocketpy_env.set_atmospheric_model( type=env.atmospheric_model_type, file=env.atmospheric_model_file, diff --git a/tests/unit/test_services/test_environment_service.py b/tests/unit/test_services/test_environment_service.py new file mode 100644 index 0000000..9f4ce9e --- /dev/null +++ b/tests/unit/test_services/test_environment_service.py @@ -0,0 +1,95 @@ +from src.models.environment import EnvironmentModel +from src.services.environment import EnvironmentService + + +def test_from_env_model_custom_atmosphere_default_wind_none(): + env_model = EnvironmentModel( + latitude=0.0, + longitude=0.0, + elevation=0.0, + atmospheric_model_type='custom_atmosphere', + pressure=101325.0, + temperature=288.15, + wind_u=None, + wind_v=None, + ) + service = EnvironmentService.from_env_model(env_model) + assert service.environment.wind_velocity_x(0) == 0.0 + assert service.environment.wind_velocity_y(0) == 0.0 + + +def test_from_env_model_custom_atmosphere_one_wind_component_none(): + env_model_u_set = EnvironmentModel( + latitude=0.0, + longitude=0.0, + elevation=0.0, + atmospheric_model_type='custom_atmosphere', + pressure=101325.0, + temperature=288.15, + wind_u=5.0, + wind_v=None, + ) + service_u = EnvironmentService.from_env_model(env_model_u_set) + assert service_u.environment.wind_velocity_x(0) == 5.0 + assert service_u.environment.wind_velocity_y(0) == 0.0 + + env_model_v_set = EnvironmentModel( + latitude=0.0, + longitude=0.0, + elevation=0.0, + atmospheric_model_type='custom_atmosphere', + pressure=101325.0, + temperature=288.15, + wind_u=None, + wind_v=3.0, + ) + service_v = EnvironmentService.from_env_model(env_model_v_set) + assert service_v.environment.wind_velocity_x(0) == 0.0 + assert service_v.environment.wind_velocity_y(0) == 3.0 + + +def test_from_env_model_custom_atmosphere_explicit_zero_wind(): + env_model = EnvironmentModel( + latitude=0.0, + longitude=0.0, + elevation=0.0, + atmospheric_model_type='custom_atmosphere', + pressure=101325.0, + temperature=288.15, + wind_u=0.0, + wind_v=0.0, + ) + service = EnvironmentService.from_env_model(env_model) + assert service.environment.wind_velocity_x(0) == 0.0 + assert service.environment.wind_velocity_y(0) == 0.0 + + +def test_from_env_model_custom_atmosphere_wind_profile_list(): + wind_u_profile = [(0.0, 2.0), (1000.0, 10.0)] + wind_v_profile = [(0.0, 1.0), (1000.0, 5.0)] + env_model = EnvironmentModel( + latitude=0.0, + longitude=0.0, + elevation=0.0, + atmospheric_model_type='custom_atmosphere', + pressure=101325.0, + temperature=288.15, + wind_u=wind_u_profile, + wind_v=wind_v_profile, + ) + service = EnvironmentService.from_env_model(env_model) + assert service.environment.wind_velocity_x(0) == 2.0 + assert service.environment.wind_velocity_x(1000) == 10.0 + assert service.environment.wind_velocity_y(0) == 1.0 + assert service.environment.wind_velocity_y(1000) == 5.0 + + +def test_from_env_model_standard_atmosphere(): + env_model = EnvironmentModel( + latitude=0.0, + longitude=0.0, + elevation=0.0, + atmospheric_model_type='standard_atmosphere', + ) + service = EnvironmentService.from_env_model(env_model) + assert service.environment is not None