Skip to content

feat(motor,flight): tank geometry + fluid density inputs, KML export - #72

Open
aasitvora99 wants to merge 16 commits into
masterfrom
enh/tank-geometry-fluid-density
Open

feat(motor,flight): tank geometry + fluid density inputs, KML export#72
aasitvora99 wants to merge 16 commits into
masterfrom
enh/tank-geometry-fluid-density

Conversation

@aasitvora99

@aasitvora99 aasitvora99 commented May 1, 2026

Copy link
Copy Markdown
Member

Summary

Two capabilities that clients could not express through the API before:

  1. Liquid and hybrid motor tanks can be declared as cylindrical or spherical shapes instead of hand-discretised piecewise geometry, and fluid density accepts temperature-dependent samples instead of only a constant.
  2. Flight trajectories download as a Google Earth KML file from GET /flights/{flight_id}/kml.

These ship together because the KML work (#73) branched off this branch and was merged back into it.

Tank geometry and fluid density

Previously geometry had to be an explicit list of ((z_start, z_end), radius) segments, so modelling a plain cylinder meant precomputing the discretisation client-side. density was a single float, which left cryogenic propellants unrepresentable.

MotorTank.geometry is now a discriminated union on geometry_kind:

geometry_kind Fields rocketpy target
cylindrical radius, height, spherical_caps CylindricalTank
spherical radius SphericalTank
custom geometry (piecewise segments) TankGeometry

TankFluids.density takes either a scalar or [(temperature_K, density)] samples. Samples get linearly interpolated into a rocketpy Function and wrapped as a (T, P) callable, since Fluid expects density to depend on both. Pressure dependence is out of scope here, and the wrapper ignores that argument deliberately.

Breaking change: geometry is an object now, not a bare list. Payloads sending a raw segment list must wrap it as {"geometry_kind": "custom", "geometry": [...]}.

Validation moved to the API boundary

MotorTank rejects payloads where tank_kind and the supplied fields disagree, naming exactly what is missing (LEVEL without liquid_height, MASS without liquid_mass/gas_mass, and so on). Before, these reached rocketpy and failed during Tank construction with an opaque trace.

burn_time is optional for solid, liquid, and hybrid motors, where rocketpy infers the burn window from the thrust_source span. GenericMotor genuinely requires it, so that path raises an explicit 422. nozzle_position is only forwarded when the client supplies it, letting rocketpy's default apply otherwise.

KML trajectory export

GET /flights/{flight_id}/kml responds with application/vnd.google-earth.kml+xml and a Content-Disposition attachment header, so browsers save it as flight_{id}.kml.

Implementation note for reviewers: rocketpy's FlightDataExporter.export_kml only writes to a file path, it does not return bytes. The service writes to a NamedTemporaryFile, reads it back, and unlinks in a finally.

Round-trip is lossy in two places

Reading a flight back projects rocketpy state onto the API schema, and two projections cannot be inverted:

  • Geometry always returns as custom with discretised segments, even when it was submitted as cylindrical or spherical.
  • A Function-valued density collapses to a scalar evaluated at rocketpy's reference point (273.15 K, 101325 Pa).

Both are documented in comments at the call sites. Callers that need to preserve their original input should keep it themselves rather than reading it back.

Tests

  • tests/unit/test_services/test_motor_service.py (new) covers geometry adapter dispatch across all 3 kinds plus the unsupported case, fluid adapter for scalar and sampled density, and from_motor_model for liquid motors.
  • tests/unit/test_routes/test_motors_route.py adds 3 tests for the new geometry kinds and sampled density, and 5 asserting each tank_kind rejects payloads missing its required fields.
  • tests/unit/test_routes/test_flights_route.py covers the KML success, 404, and 500 paths.

Summary by CodeRabbit

  • New Features
    • Added downloadable KML exports for flight trajectories, compatible with mapping and visualization tools.
    • Added support for cylindrical, spherical, and custom tank geometries.
    • Added scalar and temperature-dependent fluid density inputs.
  • Bug Fixes
    • Improved validation for tank configurations and motor settings, returning clearer errors for incomplete or incompatible data.
    • Optional motor parameters now use system defaults when omitted.
  • Documentation
    • Expanded motor creation API examples with supported tank geometries and density formats.

Compound Engineering
Claude Code

aasitvora99 and others added 12 commits April 20, 2026 12:40
Exposes structured drawing geometry that mirrors rocketpy.Rocket.draw(),
so clients can redraw a rocket using the same shape math rocketpy uses
without server-side rendering or duplicated geometry logic in the UI.

Response carries per-surface shape_x/shape_y arrays, body tube segments,
motor patch polygons (nozzle, chamber, grains, tanks, outline), rail
button positions, sensors, t=0 CG/CP, and drawing bounds. Coordinates
are already transformed into the draw frame rocketpy uses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align MotorModel and MotorTank with RocketPy's actual constructor
requirements so invalid motors are rejected at the API boundary with a
clear error rather than crashing deep inside RocketPy at simulate time.

- MotorModel.validate_dry_inertia_for_kind: SOLID / LIQUID / HYBRID
  motors in RocketPy require dry_inertia with no default. Only
  GenericMotor accepts (0, 0, 0). Reject the default tuple for every
  kind except GENERIC with a message the user can act on.
- MotorTank.discretize: change to Optional[int] = 100 to match the
  RocketPy Tank classes' default. Forms can now omit the field and still
  submit successfully.
- stub_motor_dump fixture: use dry_inertia=[0.1, 0.1, 0.1] so tests that
  override motor_kind to SOLID / LIQUID / HYBRID still pass the new
  validator without each having to add a dry_inertia override locally.
RocketPy's rocket.draw() does not draw a combustion chamber for
GenericMotor because _MotorPlots._generate_combustion_chamber reads
grain-only attributes (grain_initial_height, grain_outer_radius, etc.)
that GenericMotor lacks — it only emits a nozzle. Users who populate
chamber_radius / chamber_height / chamber_position then saw no chamber
in the jarvis playground.

Add a GenericMotor branch in RocketService._build_motor_geometry that
constructs an equivalent rectangular chamber patch from the chamber_*
fields. Vertex ordering mirrors _generate_combustion_chamber so the
patch flows through _generate_motor_region for outline assembly the
same way a SolidMotor chamber does.

Patch is emitted with role='chamber', flowing through the existing
drawingMotorSchema + GeometryRocket renderer without frontend changes.
…eneric kinds

Two related fixes surfaced during jarvis form refactor.

1. MotorModel.burn_time: float  →  Optional[float] = None.
   RocketPy's LiquidMotor / HybridMotor / SolidMotor all auto-detect
   burn_time from the thrust_source array span — forcing clients to
   supply it was wrong. GenericMotor still requires it; the service
   now raises a 422 at the API boundary when the GENERIC path receives
   None, instead of letting rocketpy error deeper in construction.

2. MotorService.from_motor_model: nozzle_position previously landed in
   motor_core only for the GenericMotor branch; Liquid / Hybrid / Solid
   silently ignored the user's value and took rocketpy's default of 0.
   Now forwarded via motor_core for every kind (conditionally, so a
   null still falls back to rocketpy's default). Removed the redundant
   nozzle_position kwarg on the GenericMotor constructor call.

3. Optional-forwarding convention: motor_core only carries burn_time /
   nozzle_position when the client actually supplied them, so rocketpy
   picks its own default otherwise instead of receiving None for
   number-typed args.

Verified against real rocketpy:
- LIQUID, burn_time=None  → LiquidMotor auto-detects burn window
- LIQUID, burn_time=2.0   → LiquidMotor builds with explicit window
- LIQUID, nozzle_position=0.3  →  LiquidMotor.nozzle_position == 0.3
- LIQUID, nozzle_position=None →  LiquidMotor.nozzle_position == 0
- GENERIC, burn_time=None → 422 'burn_time is required for generic motors.'

All 173 unit tests pass.
… guard

Paired API-side work for the jarvis tank/fluid migration (branch
feat/tank-fluid-schema-migration in jarvis-ts). Mirrors what that repo
now sends on the wire:

Schema (src/models/sub/tanks.py):
- MotorTank.geometry is a discriminated union on geometry_kind:
  * custom       → legacy piecewise (TankGeometry)
  * cylindrical  → CylindricalTank(radius, height, spherical_caps)
  * spherical    → SphericalTank(radius)
- TankFluids.density accepts float or List[(T_K, rho)] temperature
  samples; pressure dependence deferred.
- New validate_tank_kind_fields model_validator mirrors the
  validate_dry_inertia_for_kind pattern from motor.py — rejects
  payloads whose tank_kind omits required kind-specific fields at
  the API boundary with a kind-named 422 instead of letting rocketpy
  crash deeper in construction.
- discretize is now optional (defaults to 100).

Service (src/services/motor.py):
- _build_rocketpy_tank_geometry dispatches on geometry_kind to
  TankGeometry/CylindricalTank/SphericalTank.
- _build_rocketpy_fluid instantiates a real rocketpy.Fluid and wraps
  sampled density in a 1D Function-of-temperature callable; scalars
  pass through. (Eliminates the duck-typed Pydantic-into-rocketpy
  pattern that worked by accident.)

Inverse path (src/services/flight.py):
- _extract_fluid_density collapses Function-valued density back to a
  scalar at rocketpy's reference state (273.15 K, 101325 Pa) — lossy
  round-trip is documented; samples-roundtrip not supported in this
  iteration.
- Geometry inverse always emits the 'custom' segment list shape; all
  three rocketpy geometry subclasses expose a piecewise .geometry dict
  so one code path covers them uniformly.

Tests:
- test_motors_route.py: 8 new cases covering each geometry_kind,
  sampled density, invalid discriminator, and all four tank_kind guard
  paths (MASS / MASS_FLOW / LEVEL / ULLAGE missing sub-fields).
- tests/unit/test_services/test_motor_service.py: new suite exercising
  the adapter end-to-end against real rocketpy for each
  geometry×variant combination plus sampled density roundtrip.

Routes (src/routes/motor.py):
- POST /motors docstring gained an example payload showing the
  discriminated geometry union and sampled density shape.

Gitignore:
- Added .context and .pylint.d/ to keep local tooling artifacts out
  of the tree.

Full suite: 173/173 pass.
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add a flight KML download endpoint and export service. They also add structured motor tank geometry, sampled fluid density, tank-kind validation, optional burn time handling, RocketPy conversion, documentation, fixtures, and tests.

Changes

Flight KML export

Layer / File(s) Summary
KML export service
src/services/flight.py
FlightService exports flight data to a temporary KML file, returns its bytes, and removes the file after processing.
KML route integration
src/controllers/flight.py, src/routes/flight.py, tests/unit/test_routes/test_flights_route.py
The controller and route expose downloadable KML responses with flight-specific filenames and tested error handling.

Motor tank modeling

Layer / File(s) Summary
Tank contracts and validation
src/models/motor.py, src/models/sub/tanks.py
Tank models support discriminated geometry variants, scalar or sampled density, tank-kind field validation, and optional burn time.
RocketPy motor conversion
src/services/motor.py
API tank geometry and density values convert into RocketPy objects. Optional motor parameters are forwarded only when supplied.
Motor endpoint and conversion coverage
src/routes/motor.py, tests/unit/test_routes/conftest.py, tests/unit/test_routes/test_motors_route.py, tests/unit/test_services/test_motor_service.py
Documentation, fixtures, route tests, and service tests cover supported tank variants, sampled density, and invalid configurations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: gabrielbarberini

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main changes: tank geometry and fluid density inputs, plus KML export.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enh/tank-geometry-fluid-density

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

aasitvora99 and others added 2 commits May 16, 2026 16:19
…luid-density

# Conflicts:
#	src/services/motor.py
#	src/services/rocket.py
#	src/views/rocket.py
…n tests

pylint W0613 (unused-argument) on 5 schema-validation tests failed the build
step (pylint exit 4), blocking this PR and the stacked KML PR #73. These tests
only assert a 422 from schema validation, so they never touch the controller;
and mock_controller_instance is @pytest.fixture(autouse=True), so it still runs
for every test regardless — the parameter was vestigial. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Gui-FernandesBR

Copy link
Copy Markdown
Member

CI unblocked ✅ — and why this PR matters for the Jarvis beta

Pushed a small commit that gets the build green again: the build (3.12.5) step was failing on pylint W0613 (unused-argument mock_controller_instance) in 5 of the new tank-validation tests. Those tests only assert a 422 from schema validation, so they never touch the controller — and mock_controller_instance is @pytest.fixture(autouse=True), so it still runs for every test regardless. The parameter was vestigial; removing it is a no-op behavior-wise. build (3.12.5) now passes.

Why this is on the critical path: the Jarvis web client's motor wire-schema was written against this PR's tank model. Against Infinity-API/master (the discriminated-union geometry object → flat segment list, sampled TankFluids.density, etc.) every liquid/hybrid motor 422s, which is a big part of the "unreliable sims" the beta is blocked on. See the Jarvis audit: RocketPy-Team/jarvis-ts#70 and docs/release/2026-07-22-beta-prep-handoff.md (§4, wire-contract drift).

Remaining (your call, @aasitvora99): mark this out of draft → merge → then #73 (stacked on this) → and deploy to api.rocketpy.org. Jarvis calls the deployed API, so the deploy is the actual gate for the contract-drift, not the merge.

— pushed as part of a Claude Code audit of the beta (Gui).

simplified docs
@GabrielBarberini
GabrielBarberini marked this pull request as ready for review July 25, 2026 19:20
@GabrielBarberini
GabrielBarberini self-requested a review July 25, 2026 19:21

@GabrielBarberini GabrielBarberini left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took the liberty to put it as ready for review, CI is green and I patched the docs verbosity. LGTM

Co-authored-by: Gui-FernandesBR <guilherme_fernandes@usp.br>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Gabriel Barberini <gabrielbarberinirc@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@aasitvora99 aasitvora99 changed the title Enh/tank geometry fluid density feat(motor,flight): tank geometry + fluid density inputs, KML export Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/unit/test_routes/conftest.py (1)

93-149: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fixtures are correct; consider deduplicating the repeated field-clearing dict.

stub_level_tank_dump, stub_ullage_tank_dump, and stub_mass_tank_dump each repeat the same six-key dict that clears gas_mass_flow_rate_in, gas_mass_flow_rate_out, liquid_mass_flow_rate_in, liquid_mass_flow_rate_out, initial_liquid_mass, and initial_gas_mass. Extract this into a module-level constant or small helper function to reduce duplication across the three fixtures.

♻️ Proposed refactor
+_CLEAR_MASS_FLOW_FIELDS = {
+    'gas_mass_flow_rate_in': None,
+    'gas_mass_flow_rate_out': None,
+    'liquid_mass_flow_rate_in': None,
+    'liquid_mass_flow_rate_out': None,
+    'initial_liquid_mass': None,
+    'initial_gas_mass': None,
+}
+
+
 `@pytest.fixture`
 def stub_level_tank_dump(stub_tank_dump):
     stub_tank_dump.update(
         {
             'tank_kind': TankKinds.LEVEL,
             'liquid_height': 0,
-            'gas_mass_flow_rate_in': None,
-            'gas_mass_flow_rate_out': None,
-            'liquid_mass_flow_rate_in': None,
-            'liquid_mass_flow_rate_out': None,
-            'initial_liquid_mass': None,
-            'initial_gas_mass': None,
+            **_CLEAR_MASS_FLOW_FIELDS,
         }
     )
     return stub_tank_dump
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_routes/conftest.py` around lines 93 - 149, Deduplicate the
repeated kind-specific field-clearing mapping used by stub_level_tank_dump,
stub_ullage_tank_dump, and stub_mass_tank_dump by extracting it into a
module-level constant or small helper. Reuse that shared definition in all three
fixtures while preserving each fixture’s tank_kind and kind-specific values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/controllers/flight.py`:
- Around line 166-168: Update the controller method around get_flight_by_id so
the complete synchronous FlightService.from_flight_model(...).get_flight_kml()
export runs in a worker thread after the awaited database read. Preserve the
returned KML result while ensuring temp-file creation, reading, and deletion do
not execute on the event-loop thread.

In `@src/models/motor.py`:
- Around line 23-27: Update MotorModel.validate_motor_kind to reject GENERIC
motors when burn_time is None, alongside the existing motor-kind requirements.
Ensure this validation occurs during POST model construction before
MotorRepository.create_motor persists model_dump(exclude_none=True), while
preserving validation behavior for other motor kinds.

In `@src/services/flight.py`:
- Around line 230-244: Update _extract_fluid_density and its
import_flight_from_rpy caller to preserve temperature-dependent densities by
converting representable Function values into DensityInput (T_K, density)
samples; do not collapse them to a scalar at 273.15 K. If a density function
cannot be represented by the API’s sampled schema, reject the import explicitly
rather than persisting an inaccurate scalar.
- Around line 262-271: Update _extract_tanks() and the geometry_segments
construction to preserve position-dependent tank geometry during import: do not
sample each function only at position 0 or collapse cylindrical/spherical shapes
into constant-radius custom segments. Retain the discriminated
cylindrical/spherical representation, or serialize the complete variable-radius
custom geometry so spherical tank radii remain height-dependent and downstream
.rpy motor simulations preserve volume.

---

Nitpick comments:
In `@tests/unit/test_routes/conftest.py`:
- Around line 93-149: Deduplicate the repeated kind-specific field-clearing
mapping used by stub_level_tank_dump, stub_ullage_tank_dump, and
stub_mass_tank_dump by extracting it into a module-level constant or small
helper. Reuse that shared definition in all three fixtures while preserving each
fixture’s tank_kind and kind-specific values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7949a846-1add-4204-8a34-1385caa8432d

📥 Commits

Reviewing files that changed from the base of the PR and between cf325e4 and 4109660.

📒 Files selected for processing (13)
  • .gitignore
  • src/controllers/flight.py
  • src/models/motor.py
  • src/models/sub/tanks.py
  • src/routes/flight.py
  • src/routes/motor.py
  • src/services/flight.py
  • src/services/motor.py
  • tests/unit/test_routes/conftest.py
  • tests/unit/test_routes/test_flights_route.py
  • tests/unit/test_routes/test_motors_route.py
  • tests/unit/test_services/__init__.py
  • tests/unit/test_services/test_motor_service.py

Comment thread src/controllers/flight.py
Comment on lines +166 to +168
flight = await self.get_flight_by_id(flight_id)
flight_service = FlightService.from_flight_model(flight.flight)
return flight_service.get_flight_kml()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)flight\.py$|api\.py$|rocketpy|kml|FlightService' || true

echo
echo "src/controllers/flight.py outline:"
ast-grep outline src/controllers/flight.py 2>/dev/null || true

echo
echo "Relevant src/controllers/flight.py lines:"
cat -n src/controllers/flight.py | sed -n '1,230p'

echo
echo "Search FlightService/get_flight_kml:"
rg -n "class FlightService|def get_flight_kml|from_flight_model|RocketPy|KML|rocket" src tests -S || true

Repository: RocketPy-Team/Infinity-API

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant src/routes/flight.py lines:"
cat -n src/routes/flight.py | sed -n '290,350p'

echo
echo "Relevant src/services/flight.py outline and contents:"
wc -l src/services/flight.py
ast-grep outline src/services/flight.py 2>/dev/null || true
cat -n src/services/flight.py | sed -n '1,340p'

echo
echo "Focused RocketPy KML usage in src/services/flight.py:"
rg -n -C 8 "kml|KML|Flight\(|save_kml|plot|export" src/services/flight.py src/controllers/flight.py || true

echo
echo "Check package versions/deps mentioning rocketpy:"
fd -a 'requirements|pyproject|poetry|Pipfile|environment|uv.lock' . | sed 's#^\./##' | sort
rg -n "rocketpy|rocketpy-dev|FastAPI|starlette|uvicorn|aiohttp|asyncio\.to_thread" -S --glob '!node_modules' --glob '!dist' --glob '!build' . | head -n 200

Repository: RocketPy-Team/Infinity-API

Length of output: 46105


Move KML generation off the event-loop thread.

get_flight_kml still runs FlightService.from_flight_model(...).get_flight_kml() synchronously after awaiting the database read, and the controller is awaited by the FastAPI route handler. The export creates a temp file, reads it, and deletes it synchronously, so a long KML export blocks the shared event loop.

Run the complete synchronous export path in a worker thread.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/flight.py` around lines 166 - 168, Update the controller
method around get_flight_by_id so the complete synchronous
FlightService.from_flight_model(...).get_flight_kml() export runs in a worker
thread after the awaited database read. Preserve the returned KML result while
ensuring temp-file creation, reading, and deletion do not execute on the
event-loop thread.

Comment thread src/models/motor.py
Comment on lines +23 to +27
# burn_time is optional for Liquid/Hybrid/Solid motors — rocketpy
# auto-detects the burn window from the thrust_source array span.
# GenericMotor still requires it; the motor service re-raises an
# explicit error when the GENERIC path receives None.
burn_time: Optional[float] = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether MotorController.post_motor builds the rocketpy motor synchronously.
fd -e py motor.py --full-path src/controllers | xargs -I{} sh -c 'echo "--- {} ---"; cat -n {}'

Repository: RocketPy-Team/Infinity-API

Length of output: 3177


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked controller files:"
git ls-files 'src/controllers/*.py'
echo "--- MotorController methods ---"
rg -n "def (create|create_?motor|post|get|update|delete|add|upsert|fetch)|motor_id|motor\.motor|MotorModel|get_motor_by_id" src/controllers/motor.py
echo "--- motor.py line count ---"
wc -l src/controllers/motor.py

Repository: RocketPy-Team/Infinity-API

Length of output: 1224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- src/controllers/interface.py relevant sections ---"
wc -l src/controllers/interface.py
sed -n '1,220p' src/controllers/interface.py | cat -n

echo "--- src/api.py imports and registered motor/controller routes ---"
wc -l src/api.py
rg -n "MotorController|motor|controllers|router|BaseController|create" src/api.py -C 3

echo "--- MotorModel and service validation snippets ---"
wc -l src/models/motor.py src/services/motor.py
sed -n '1,130p' src/models/motor.py | cat -n
sed -n '220,260p' src/services/motor.py | cat -n

Repository: RocketPy-Team/Infinity-API

Length of output: 14542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- create_motor methods in motor controllers/repositories ---"
rg -n "def create_motor|create_motor\\(" src/controllers src/repositories src/routes -C 6

echo "--- motor router outline and relevant snippets ---"
wc -l src/routes/motor.py
sed -n '1,220p' src/routes/motor.py | cat -n

echo "--- motor repository filenames and create_motor snippets ---"
git ls-files 'src/repositories/*motor*' 'src/repositories/motor.py'
fd -e py 'motor|Motor' src/repositories -x sh -c 'echo "--- $1 ---"; rg -n "create_motor|create_model|NAME" "$1" -C 5' sh {}

Repository: RocketPy-Team/Infinity-API

Length of output: 8123


Enforce GENERIC burn_time in the model validator.

POST /motors builds MotorModel, then MotorRepository.create_motor persists motor.model_dump(exclude_none=True). The GENERIC burn_time check lives only in MotorService.from_motor_model, so a malformed GENERIC motor can be stored and fail later. Add the same POST-level check used for solid/grain parameters and other motor-kind requirements in MotorModel.validate_motor_kind.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/motor.py` around lines 23 - 27, Update
MotorModel.validate_motor_kind to reject GENERIC motors when burn_time is None,
alongside the existing motor-kind requirements. Ensure this validation occurs
during POST model construction before MotorRepository.create_motor persists
model_dump(exclude_none=True), while preserving validation behavior for other
motor kinds.

Comment thread src/services/flight.py
Comment on lines +230 to +244
@staticmethod
def _extract_fluid_density(fluid):
"""Project a rocketpy Fluid's density back onto the API schema.

The API accepts either a scalar or a list of (T_K, density)
samples. Rocketpy may store density as either a raw scalar or a
``Function`` wrapping a 2D ``(T, P) -> density`` callable. A
full sample round-trip is not supported in this iteration;
Function-valued densities are collapsed to a scalar evaluated
at rocketpy's default reference (273.15 K, 101325 Pa).
"""
density = fluid.density
if isinstance(density, Function):
return float(density(273.15, 101325))
return density

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching flight.py:"
fd -a 'flight.py$' . || true

echo
echo "Outline src/services/flight.py:"
if [ -f src/services/flight.py ]; then
  wc -l src/services/flight.py
  ast-grep outline src/services/flight.py --view expanded | sed -n '1,220p'
fi

echo
echo "Relevant sections:"
for start in 1 180 210 280 380 460 540; do
  end=$((start+120))
  echo "--- src/services/flight.py $start-$end"
  sed -n "${start},${end}p" src/services/flight.py
done

echo
echo "Search DensityInput and extract_models:"
rg -n "DensityInput|extract_models|import_flight_from_rpy|_extract_fluid_density|density_input|fluid_dens|fluid" src tests | sed -n '1,240p'

Repository: RocketPy-Team/Infinity-API

Length of output: 33062


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "src/controllers/flight.py relevant section:"
sed -n '180,240p' src/controllers/flight.py | cat -n

echo
echo "src/services/motor.py fluid construction:"
sed -n '55,100p' src/services/motor.py | cat -n
sed -n '250,275p' src/services/motor.py | cat -n

echo
echo "MotorService round-trip usages:"
rg -n "from_models|from_motor_model|extract_models|get_models|_build_rocketpy_fluid|Fluid\\(" src/services/motor.py src/services/flight.py src/services/*.py | sed -n '1,220p'

echo
echo "Search for create flight from model round-trip:"
rg -n "FlightService\\.(from_flight_model|extract_models)|import_flight_from_rpy|from_motor_model" src tests | sed -n '1,220p'

Repository: RocketPy-Team/Infinity-API

Length of output: 10363


Preserve temperature-sampled fluid density during import.

_extract_fluid_density collapses function-backed tank gas and liquid density to one value at 273.15 K, then import_flight_from_rpy persists that scalar. A later simulation rebuilds fluids from those persisted samples and ignores the original temperature-dependent density. Extract representable (T_K, density) samples into DensityInput, or reject the import for non-representable density functions instead of changing the flight model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/flight.py` around lines 230 - 244, Update _extract_fluid_density
and its import_flight_from_rpy caller to preserve temperature-dependent
densities by converting representable Function values into DensityInput (T_K,
density) samples; do not collapse them to a scalar at 273.15 K. If a density
function cannot be represented by the API’s sampled schema, reject the import
explicitly rather than persisting an inaccurate scalar.

Comment thread src/services/flight.py
Comment on lines +262 to 271
# Geometry round-trip is lossy: even if the client originally
# sent a cylindrical/spherical geometry, we discretise it back
# to the generic piecewise form on read. Every rocketpy tank
# geometry exposes its internal piecewise dict via
# `tank.geometry.geometry`, so this path covers all three
# geometry subclasses uniformly.
geometry_segments = [
(bounds, float(func(0)))
for bounds, func in tank.geometry.geometry.items()
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant files =="
git ls-files | rg '(^src/services/flight.py$|rocketpy|tank|geometry)' | sed -n '1,120p'

echo
echo "== outline src/services/flight.py around import/export =="
ast-grep outline src/services/flight.py --view expanded 2>/dev/null | sed -n '1,220p' | rg -n 'def|class|flight|tank|import|serde|geometry' || true

echo
echo "== lines 220-310 =="
sed -n '220,310p' src/services/flight.py | nl -ba -v220

echo
echo "== search geometry classes/config =="
rg -n "class .*Tank|Geometry|geometry\.geometry|TankGeometry|Tank|tank\.geometry|rocketpy" src tests -g '*.py' | sed -n '1,240p'

Repository: RocketPy-Team/Infinity-API

Length of output: 1357


🌐 Web query:

rocketpy geometry TankGeometry geometry attribute cylindrical spherical piecewise

💡 Result:

In RocketPy, the TankGeometry class is used to define the geometry of an axi-symmetric tank by specifying its radius as a function of its height along the axis of symmetry [1][2]. The geometry is defined using a dictionary provided to the geometry_dict parameter [3][4]. This dictionary allows for the construction of complex, piecewise defined shapes [1][5]. Key details regarding the TankGeometry attribute: 1. The geometry attribute is a dictionary where each key is a disjoint tuple (lower_bound, upper_bound) representing a domain along the tank's symmetry axis in meters [1][3]. 2. The corresponding values are functions (typically Python lambdas or RocketPy.Function objects) that map the tank's height to its radius at that specific section [1][2]. 3. Because the dictionary can contain multiple disjoint domains, the tank's overall radius profile is effectively a piecewise-defined function [1][5][6]. While users can manually define geometries using this dictionary approach for arbitrary shapes, RocketPy also provides convenience classes such as CylindricalTank and SphericalTank for standard configurations [3][4]. For example, a spherical tank can be defined using the geometry dictionary by specifying a radius function such as lambda h: (radius2 - h2)(1/2) over the domain (-radius, radius) [1][2]. The internal logic of TankGeometry then computes the overall radius, area, and volume functions based on these inputs [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/services/flight.py lines 247-330 =="
sed -n '247,330p' src/services/flight.py

echo
echo "== src/models/sub/tanks.py =="
cat -n src/models/sub/tanks.py

echo
echo "== imports and serialization helpers =="
sed -n '1,120p' src/services/flight.py

echo
echo "== tank field serialization references =="
rg -n "MotorTank|tank|geometry|_extract_tanks|rpy|from_rpy" src -g '*.py' | sed -n '1,260p'

Repository: RocketPy-Team/Infinity-API

Length of output: 28255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

src = Path('src/services/flight.py').read_text()
tree = ast.parse(src, filename='src/services/flight.py')
for node in tree.body:
    if node.name == 'FlightService':
        for cls in node.body:
            if isinstance(cls, ast.FunctionDef) and cls.name == '_extract_tanks':
                src_snippet = src.splitlines()[cls.lineno-1:cls.end_lineno]
                print(f'function: {cls.name} lines {cls.lineno}-{cls.end_lineno}')
                lines = [
                    ('geometry_segments =', any('geometry_segments = [' in line for line in src_snippet)),
                    ('evaluating func(0) explicitly:', any('func(0)' in line for line in src_snippet)),
                    ('custom geometry_kind:', any('geometry_kind": "custom"' in line for line in src_snippet)),
                    ('tank geometry dict:', any('tank.geometry.geometry.items()' in line for line in src_snippet)),
                ]
                for label, ok in lines:
                    print(f'{label}: {ok!s}')
                print('target block:')
                for i, line in enumerate(src_snippet, start=cls.lineno):
                    print(f'{i}: {line}')
PY

Repository: RocketPy-Team/Infinity-API

Length of output: 442


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect _build_rocketpy_tank_geometry =="
sed -n '40,62p' src/services/motor.py

echo
echo "== inspect _generate_positioned_tanks if present in installed rocketpy =="
python3 - <<'PY'
import inspect
try:
    import rocketpy
    from rocketpy import LevelBasedTank, MassBasedTank, UllageBasedTank
    print('rocketpy imported', getattr(rocketpy, '__version__', ''))
    for name, cls in [('LevelBasedTank', LevelBasedTank), ('MassBasedTank', MassBasedTank), ('UllageBasedTank', UllageBasedTank)]:
        print(f'\n{name}:')
        print(inspect.signature(cls))
        print('methods:', sorted([m for m in dir(cls) if not m.startswith('_') and callable(getattr(cls, m))]))
        if hasattr(cls, 'plots') and hasattr(getattr(cls, 'plots'), '_generate_positioned_tanks'):
            print(inspect.getsource(cls.plots._generate_positioned_tanks))
except Exception as exc:
    print(type(exc).__name__ + ': ' + str(exc))
PY

Repository: RocketPy-Team/Infinity-API

Length of output: 1101


Preserve position-dependent tank geometry during import.

_extract_tanks() collapses each shape into a geometry_kind: "custom" segment whose radius is sampled at position == 0. For spherical tanks, the radius depends on tank height, so the imported geometry changes volume and later .rpy-loaded motor simulations. Preserve the discriminated cylindrical/spherical forms, or serialize the full variable-radius custom geometry instead of collapsing it to a constant-radius segment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/flight.py` around lines 262 - 271, Update _extract_tanks() and
the geometry_segments construction to preserve position-dependent tank geometry
during import: do not sample each function only at position 0 or collapse
cylindrical/spherical shapes into constant-radius custom segments. Retain the
discriminated cylindrical/spherical representation, or serialize the complete
variable-radius custom geometry so spherical tank radii remain height-dependent
and downstream .rpy motor simulations preserve volume.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants