@app.command
def estimate(
flight: Annotated[Path, cyclopts.Parameter(help="Input flight CSV or parquet")],
*,
out: Annotated[
Path | None,
cyclopts.Parameter(
help="Output path (.csv/.parquet); default: <flight>_fuel.<ext>"
),
] = None,
typecode: Annotated[
str, cyclopts.Parameter(help="Aircraft type column")
] = "typecode",
groundspeed: Annotated[
str, cyclopts.Parameter(help="Groundspeed column (kt)")
] = "groundspeed",
altitude: Annotated[
str, cyclopts.Parameter(help="Altitude column (ft)")
] = "altitude",
vertical_rate: Annotated[
str, cyclopts.Parameter(help="Vertical rate column (ft/min)")
] = "vertical_rate",
airspeed: Annotated[
str, cyclopts.Parameter(help="Airspeed column (kt)")
] = "airspeed",
mass: Annotated[str, cyclopts.Parameter(help="Mass column (kg)")] = "mass",
second: Annotated[
str | None, cyclopts.Parameter(help="Timestamp column (s); enables derivatives")
] = None,
) -> None:
"""Estimate fuel flow for a flight and write the enriched table.
Adds ``fuel_flow`` (kg/s), ``fuel_flow_kgh`` (kg/h) and, when ``--second`` is
given, ``fuel_cumsum`` (kg).
"""
if not flight.exists():
print(f"error: file not found: {flight}", file=sys.stderr)
raise SystemExit(1)
frame = _read(flight)
mapping = {
"typecode": typecode,
"groundspeed": groundspeed,
"altitude": altitude,
"vertical_rate": vertical_rate,
"airspeed": airspeed,
"mass": mass,
}
if second is not None:
mapping["second"] = second
try:
estimated = FuelEstimator().estimate(frame, **mapping)
except (ValueError, KeyError) as exc:
print(f"error: {exc}", file=sys.stderr)
raise SystemExit(1) from exc
# estimate() echoes its input frame type; _read always yields polars, so the
# result is polars too. Narrow defensively (and convert pandas if it ever isn't).
result = (
estimated if isinstance(estimated, pl.DataFrame) else pl.from_pandas(estimated)
)
if out is None:
out = flight.with_name(f"{flight.stem}_fuel{flight.suffix}")
_write(result, out)
print(f"wrote {len(result)} rows with fuel columns to {out}")