Skip to content

spacecost 0.5.0

Choose a tag to compare

@loggger101 loggger101 released this 29 Sep 01:27
· 21 commits to main since this release

Data contract 1.16.0 → 1.17.0: every fairing volume is now derived from a
cited drawing, or left blank with a reason, and values moved.
A consumer
reading fairing_volume_m3 should take the release that carries this and
re-run. economicspace repins to this tag. The tests, CI, docs and
build-floor changes below it moved no name, table or number.

Fairing volumes are read off a drawing, or left blank

Until now, 37 of the 76 launch rows had no fairing_volume_m3, and the other
39 typed one with no source. A consumer that filled the blanks with a default
was pricing those rows on a number nobody had chosen. economicspace's Stage 4
used 100 m³, and SLS Block 1 won 332 rows of a cislunar sample on it. The
column is the usable payload envelope, and the new spacecost/fairings.py
derives it at import. A row that types it raises.

  • guide: the envelope a user's guide draws with its dimensions printed,
    revolved about the axis. Arcs with a printed radius are followed, cones take
    their top diameter from a printed half-angle, and an unprinted top closes to
    a point.
  • published: the maker states the usable volume (LVM3, Gravity-1,
    Saturn V).
  • estimate: only the outer diameter and length are published. The
    volume is that cylinder times FAIRING_FILL_RATIO, the median envelope fill
    of the guide rows that print both (0.482, range 0.27-0.63). It is derived,
    never typed.
  • none: NaN, with the reason in notes. Most Chinese commercial
    vehicles publish a diameter only.

The method reproduces the two totals a guide prints, and import asserts both:
New Glenn's 458 m³ and SLS Block 1B's 621 m³. New column fairing_basis, and
every launch row's notes gains a closing Fairing volume: sentence naming the
document and figure. Sources are in CITATIONS.md.

Every row whose volume moved:

row status was now basis
Falcon 9 (reusable) operational 145 157 guide
Falcon 9 (expendable) operational 145 157 guide
Falcon Heavy (reusable side cores) operational 145 157 guide
Falcon Heavy (expendable) operational 145 157 guide
SLS Block 1 operational blank 216 guide
Atlas V 551 operational 233 127 guide
Vulcan Centaur VC2 operational 233 171 guide
Vulcan Centaur VC4 operational 233 171 guide
Vulcan Centaur VC6 operational 233 171 guide
New Glenn operational 480 458 guide
Electron operational 1.85 1.19 guide
Alpha operational 22 12 guide
Minotaur IV operational blank 13.4 guide
Soyuz-2.1a operational 80 75.1 guide
Soyuz-2.1b operational 80 75.1 guide
Angara A5 operational blank 127 estimate
Proton-M operational blank 91.1 guide
Ariane 6 (A62) operational 124 140 guide
Ariane 6 (A64) operational 124 245 guide
Vega C operational 47 38.8 guide
H3 (24L) operational 184 168 estimate
H3 (30) operational blank 107 estimate
PSLV-XL operational 34 blank none
Long March 2C operational blank 35.7 guide
Long March 2D operational blank 29.7 estimate
Long March 3B/E operational blank 63.7 guide
Long March 5 operational 157 126 estimate
Long March 7 operational 111 blank none
Long March 10B operational blank 128 estimate
Ceres-1 operational blank 3.41 guide
Gravity-1 operational blank 100 published
Zhuque-3 operational 190 blank none
Pallas-1 operational blank 75.4 guide
Delta IV Heavy retired 310 216 guide
H-IIA 204 retired 122 82.5 guide
Pegasus XL retired blank 2.04 guide
Ariane 5 ECA retired blank 209 guide
Vega retired blank 20 guide
Saturn V retired blank 91.5 published
Starship (projected) development 1000 684 guide
Neutron development 113 183 guide
Terran R development 340 blank none
Nova development 80 blank none
Eclipse (MLV) development 160 blank none
Tianlong-3 development 150 blank none
Long March 10 development 310 blank none
Long March 9 development 1000 blank none
SLS Block 1B (Cargo) concept 340 621 guide
SpinLaunch Orbital concept 0.6 blank none
Light-gas gun (orbital) concept 0.05 blank none
StarTram (maglev) concept 200 blank none
Skylon / SABRE concept 140 212 guide
Sea Dragon concept 6000 blank none

⚠️ The largest moves are rows that were typed without a source. Delta
IV Heavy's 310 m³ and Starship's 1,000 m³ read as outer volumes; the drawn
envelopes are 216 and 684. Atlas V 551 is quoted with the 5-m Short fairing,
not the 5-m Long that 233 m³ implies. SLS Block 1 takes the 5-m Delta IV
fairing NASA documents for a Block 1 cargo flight (ESD 30000 s6.2.1). That
configuration has never flown, and the row's note says so.

⚠️ Rows that had a typed volume and now read blank (PSLV-XL, Long March
7, Zhuque-3 and several development and concept rows) had no source for it. A
consumer that defaults a blank now defaults these too.

The consumer contract follows what economicspace reads

tests/test_consumer_contract.py now lists what economicspace actually
reads, found by grepping it for spacecost.<name> on its main (master
v1.35.0) and on its pending master v1.36.0. It had mirrored that repo's Stage
3 adapter, which re-exports most of __all__, and so had missed what Stage 2
reads directly:

  • added: LEO_LAUNCH_VEHICLE, delivery_mass_ratio and
    delivery_hardware_usd_per_kg, which Stage 2 reads, and
    ENVIRONMENTS_REFERENCE, which the Stage 3 adapter re-exports and its
    banner counts.
  • new, SUBMODULE_SURFACE: four names reached through a submodule, which
    __all__ does not cover (delivery's three hardware rates, read by the
    worked calculation, and prices.merge_propellant_prices, read by
    verify_stage3.py).
  • new, REEXPORTED_ON_CONSUMER_MAIN: ten names the adapter on
    economicspace's main re-exports at import and nothing there reads:
    LITRES_PER_GAL, LITRES_PER_BBL, COMMODITY_DENSITY_KG_PER_L,
    load_storage, load_environments, propellant_mass_for_dv,
    cost_per_dv_usd_per_kg, build_transportation_summary,
    cheapest_launch_to and mission_cost_breakdown.

⚠️ The first draft of this change dropped nine of those ten, because
master v1.36.0 stops re-exporting them. v1.36.0 is on an unmerged branch.
economicspace's main still assigns every one at import, so a contract taken
from the branch would have let this package delete a name the released
consumer cannot import without. They stay until v1.36.0 merges there. The
tenth, load_environments, had never been listed.

CI now asks the contract of the built wheel, from outside the tree, as
well as of the source. The wheel step walked __all__ alone, so it could see
neither a name deleted together with its __all__ entry nor anything reached
through a submodule. Checked both ways: the step passes with the tree broken
and the wheel intact, and fails naming spacecost.delivery.STAGE_HARDWARE_USD_PER_KG
when that one name is removed from the installed wheel.

An audit of the prose against the code

Four README passages still described an earlier release, each beside a
correct one:

  • The delivery example printed 26813.64 for lunar_surface. The model
    has said 42,635 since v0.4.0, and the table two screens further down
    says so. tests/test_docs.py now runs every Python block in the README, as
    a doctest with ELLIPSIS where it shows >>>, so the example is held to the
    code rather than to memory. It stops its digits short because every figure
    in it is an exp().
  • "The launch price ... at the low end of its band." Since v0.4.0 the
    delivery chains read the headline, the band's centre, and the section below
    that table says so. Now it says centre.
  • "The downleg departure delta-v: typed." That was v0.3.0. Since v0.3.1
    four of the six are DELTA_V_REFERENCE lookups. Three figures are still
    typed: the LEO deorbit, the GEO deorbit and the lunar TEI component.
  • "All 64 bracketed rows". v0.4.0's Expendable upper stage recurring cost made it 65. A test now holds the figure to the tables.

Smaller: LH2 carries about twenty times kerolox's tank per kilogram
burnt, not fourteen. Fourteen is the volume ratio, before the deep-cryogen
tank multiplier. The package docstring promised all seven CSVs "byte for
byte", which the README declined to promise for the summary; it now states
both contracts. And it and the README now say Stage 2 reads this package too.

The strictest test had never run in CI

test_summary_hash_on_the_reference_platform holds the summary's BYTES, and
it skips on any host that is not the recorded platform: Windows, AMD64,
Python 3.13, numpy 2.2.6, pandas 2.3.3. The matrix runs 3.9, 3.12 and
3.14 on the newest numpy and pandas, so no leg ever matched. The test was
skipped on every run, which is the rot the README's "Changing a row" warns
about, reached a different way: not a stale block, but a matrix that moved
around a correct one.

A new CI job, reference-platform, reads that block out of
summary_meta.json, installs exactly those versions, and runs
tests/test_parity.py with SPACECOST_REQUIRE_REFERENCE_PLATFORM=1, under
which a platform mismatch FAILS instead of skipping. Because the job reads the
block, re-recording the platform moves the job with it. Its first run passed
the hash test outright: 17 passed, none skipped.

⚠️ The block pins versions, not the CPU, and numpy picks its float64
exp() kernel by CPU at run time; an AVX512 host takes numpy's own AVX512F
implementation. The platform was recorded on an AVX2-only machine and the
first run drew an AVX2-only runner. Hosted runners vary, so the job sets
NPY_DISABLE_CPU_FEATURES to switch AVX512 off, and prints numpy's dispatch
so a mismatch can be read from the log.

Nothing in reference/ moved. The recorded hash still reproduces off the
reference platform too: Windows, Python 3.14, numpy 2.5.2 and pandas 3.0.5 on
an AVX2 machine give the same bytes.

The build floor, and two things CI would have tripped on

  • setuptools>=68 could not build this package. The PEP 639
    license = "MIT" string needs 77: 76.1.0 rejects pyproject.toml and 77.0.3
    builds it, both tried. The floor is 77 now. pip's isolated build always
    fetched a newer one, which is why it never showed.
  • The Python 3.9 leg moves to ubuntu-24.04. ubuntu-latest becomes
    26.04 from 2026-10-19, and there is no 3.9 build for it, so that leg would
    have gone red with nothing here changed. The other Linux legs still track
    latest.
  • actions/checkout v5 and actions/setup-python v6, the first majors on
    Node 24. Every run was carrying a warning that the v4 / v5 pair was being
    forced off the deprecated Node 20.

spacecost launch prints the whole band

It printed the headline and the LOW end of its range, which since v0.4.0 is
half a band around a centre, and all three payload columns whichever
destination was asked about. It now prints the destination's payload, then
_low, headline and _high, and price_basis, which is what says whether a
price is the launcher's own or somebody's estimate. New Glenn, cheapest on the
open market at $1,922/kg, reads reported there, and that is the whole reason
the delivery chains do not anchor on it. Output only; the ranking is
unchanged.

cheapest_launch_to with an unknown destination still raises KeyError, as
it did, but the message names the three it accepts instead of echoing the bad
key back.