Skip to content

Releases: loggger101/spacecost

spacecost 0.6.0

Choose a tag to compare

@loggger101 loggger101 released this 06 Oct 20:49

0.6.0 - 2026-10-06

Data contract 1.17.3 → 1.18.0: four values re-pinned to their sources.
Every one is a General_Research revision candidate whose source the registry
read and whose number the row's own method, applied correctly, moves. Each is
named.

  • Atlas V 551 (rc-051, launch_vehicles.csv): $153M is ULA's 2016
    RocketBuilder base, and the row is labelled 2026 dollars. Carried by CPI-U
    (Dec 2016 to Aug 2026, x1.3771) it is $211M, $11,215/kg to LEO where it
    was $8,132.
  • Minotaur IV (rc-050): the high end carried ~$50M (2010) forward at x1.45,
    an annual CPI chain that stopped at 2024. CPI-U to Aug 2026 is x1.5155, so
    the high end is $76M and the band centre $61.6M where it was $60M.
  • Pegasus XL (rc-052): $40M (2017), unescalated, becomes $54M at CPI-U
    x1.3484, $121,076/kg. The row is retired, so nothing searching operational
    vehicles reads it.
  • Deep Space Network time (rc-030, operational_costs.csv): the row
    carried a superseded FY09 MOCS rate ($1,057) forward to $1,530/hr. JPL's DSN
    Services Catalog 820-100 Rev H (Jun 2022) gives the hourly rate base as
    $1,792; at CPI-U to Aug 2026 that is $2,082/hr for one 34-m antenna and
    ~$6,245 for a 70-m. The 1,000-4,000 band already held it and stays.

A column-by-column comparison against 0.5.3 differs in those four rows (prices,
the $/kg columns derived from them, and their notes), in 35
summary_sample.csv rows that fly Atlas V or Minotaur IV, and in
pipeline_version. Nothing else moved. The summary hash was re-recorded on the
reference platform (Windows AMD64, Python 3.13, numpy 2.2.6, pandas 2.3.3).

validate()'s flying-fleet launch band rises from $100-$100,000 to
$100-$200,000 per kg to LEO. Pegasus XL at its 2026 price, $121,076/kg, is a
real vehicle at a real price (NASA paid $127,088/kg for ICON in 2017 dollars),
and a band that fires on a true row stops being read. The concept band is
unchanged.

Not taken, and recorded as owner decisions in the registry: raising Falcon
Heavy (expendable)'s band to NASA's $178M Europa Clipper price (rc-022) and
anchoring Pegasus on NASA's ICON price (rc-078) would move a commercial list
price toward a government full-service one, which this table keeps apart
elsewhere (Atlas V's note). The lunar descent row (rc-016) is right to move and
moves every archived lunar price with it, so it waits for a release that
versions the delivery chain.

spacecost 0.5.3

Choose a tag to compare

@loggger101 loggger101 released this 06 Oct 19:56

0.5.3 - 2026-10-06

Data contract 1.17.2 → 1.17.3: four notes cells now say what their sources
actually hold. No value moved.
Only notes cells, the CITATIONS table and
the pipeline_version stamp changed; every number in every table is the one
0.5.0 shipped. economicspace repins to this tag only to carry the corrected
notes. Each item names its General_Research revision candidate.

  • 16 Psyche (rc-068, environments.csv): 2.29e19 kg is the Kretlow (2020)
    SiMDA perturber mass, as Kretlow 2022 (A&A 668:A141) Table 2 lists it, not a
    Siltala & Granvik 2021 figure. Their GM of 1.482 ± 0.052 km3/s2 (2.22e19 kg,
    3% lower) is now quoted beside it, and the ~111 km radius stays attributed to
    them, from the 222 ± 4 km diameter they adopt.
  • 65803 Didymos (rc-067, environments.csv): neither 5.28e11 kg nor 390 m
    is in Daly et al. 2023, whose Table 1 gives (5.6 ± 0.5)e11 kg for the binary
    system and a 761 ± 26 m volume-equivalent diameter. Both cells sit inside
    that 1-sigma, so they stay, and the note now says their own source is not
    recorded rather than naming one that does not hold them.
  • 1-sol Mars orbit → Earth (TEI) (rc-070, delta_v_segments.csv): 900 m/s
    is now labelled the minimum-energy Hohmann floor. NASA DRA 5.0's
    all-propulsive TEI from the same orbit is 1.563 km/s in all eight 2031-2046
    opportunities (Fig 4-2), because DRA flies a fast return; the floor is kept,
    so a return from this orbit is priced at its cheapest.
  • Mars entry → surface (retroprop) (rc-072, delta_v_segments.csv): DRA
    5.0 Table 4-3 (595 m/s descent plus 15 m/s deorbit for a 110-t lander) is
    now cited as the institutional figure. It sits inside the note's own 0.5-1.0
    km/s band, so 800 stays as the conservative side of it.

The summary hash was re-recorded on the reference platform (Windows AMD64,
Python 3.13, numpy 2.2.6, pandas 2.3.3), which reproduced the 1.17.2 hash
first. A column-by-column comparison of all seven committed files against
0.5.2 differs in notes (4 cells) and pipeline_version only.

spacecost 0.5.2

Choose a tag to compare

@loggger101 loggger101 released this 06 Oct 16:18

Data contract 1.17.1 → 1.17.2: twenty-one notes cells now say what their
sources actually hold. No value moved.
Only notes cells, one code comment
and the pipeline_version stamp changed; every number in every table is the
one 0.5.0 shipped. economicspace repins to this tag only to carry the
corrected notes.

All of it comes from the General_Research evidence registry, which read the
cited documents and found the attribution, not the number, wrong. Each item
names its revision candidate.

A figure attributed to a document that does not contain it

  • NASA DRA 5.0, four times (delta_v_segments.csv). The 2.65 km/s Mars
    arrival v-infinity on the MOI row (rc-017) and the 2,100 m/s LMO
    trans-Earth injection (rc-073) are two-body derivations this table already
    shows its working for; DRA carries neither, and its only TEI is 1.563 km/s
    from the 1-sol orbit. The 4,100 m/s Mars ascent (rc-074) appears nowhere in
    DRA, which sizes its ascent vehicle by mass and engines, so that row is now
    labelled an unsourced estimate. And the TMI note's "3.6-4.3 km/s" swing
    (rc-071) is DRA's Fig 4-2 read wrongly: 3.545-4.103 km/s over 2028-2046,
    departing a 407-km orbit rather than the row's 200 km. The 3,600 cell is the
    low end and stays.
  • NASA SP-125 (rc-060) is an engine-design handbook with no ascent
    delta-v in it; the 9,400 m/s surface-to-LEO note now cites Curtis alone.
  • arXiv:1406.5027 (rc-015) is a 13-object targeted survey with no
    population statistic, so "median NEA Δv" pointed at nothing. The 6,500 m/s
    row now cites the 6.65 km/s peak of the Benner compilation in Elvis et
    al. 2011, and says the population median runs higher (8.88 km/s, ssoBFT).
  • ITU-R S.1003 (rc-079) is a disposal-orbit recommendation that never
    says "eclipse". The GEO row's 72-minute maximum now cites ECSS-E-HB-31-01
    Part 15A, and the ~45-day season is stated as geometry.
  • NASA ISS Facts and Figures (rc-069) says ~90 minutes and 16 sunrises a
    day, not 92.7 min and ~36 min of shadow. Those are now attributed to orbital
    mechanics for a 400-km circular orbit, which is what the cells encode.
  • The ESA Ariane 6 overview (rc-080) states payloads and no price. Both
    Ariane 6 rows now say their prices are estimates with no institutional
    source, since Arianespace publishes no list price.

A note whose own fact was off

  • Launch insurance (rc-031): the 2023 rate reset followed the Viasat-3
    and Inmarsat 6-F2 losses, not Intelsat 33e (Plane Talking Q4-2023).
  • RTG specific power (rc-041, rc-043): the MMRTG is 110 We at 44 kg,
    2.5 W/kg (NASA OIG IG-23-010 Table 1), in both operational_costs.csv and
    storage_systems.csv; and 1.5 kg/yr of Pu-238 is DOE's goal for 2026,
    where actual output 2018-2021 was 0.77 kg against 1.5 kg planned. The
    row's allocation-not-cost argument gets stronger.
  • Electric propulsion efficiency (rc-040): Hall thrusters run ~0.51-0.69,
    the AEPS ETU-2 measurement across its throttle range. The 0.60 value and its
    0.45-0.72 band already contain it.
  • Mission operations (rc-047): OSIRIS-REx operations obligated $210.0M
    over FY2017-FY2023 ($30.0M/yr) per the Planetary Society budget dataset;
    the $283M over 9 years the row was set from was the pre-return plan. 31.4 is
    within 5% of the prime-mission rate and stays.
  • Nuclear thermal (rc-042): NERVA reactor tests through early 1968
    demonstrated ~800 s; 825 s was the engine rating, and the XE engine tests had
    not yet run.
  • ALICE (rc-044): 210 s is near the ideal value; static motors delivered
    56-133 s, 27-64% of ideal. The row is gated out of runs either way.
  • Aerobraked LEO return (rc-053): Mars Global Surveyor flew it first, and
    "~6 months" matched no campaign (Odyssey ~2.5, MRO ~5, MGS ~10).
  • MARS_LANDED_MASS_FRACTION's comment (rc-039): on JPL's
    best-estimated entry masses, MSL is 28.5% and Perseverance 30.5%, not 27.6%
    and 29.8%. The constant, 0.30, is unaffected; README and CITATIONS.md quote
    the new pair.

A citation that named no document

  • Space Shuttle (rc-021): "Pielke & Byerly" now names its venue, Nature
    472:38 (2011), with NASA OIG IG-24-001's ~$1.45B a launch as a cross-check.
  • Drilling / excavation energy (rc-026): "Zacny et al." now names
    Metzger, Zacny & Morrison 2020 and Zeng et al. 2007.
  • In-space propellant transfer loss (rc-028): the 1% low end now cites a
    ground LH2 transfer study, flagged as a preprint that assumes space-rated
    pumps.

The summary hash was re-recorded on the reference platform (Windows AMD64,
Python 3.13, numpy 2.2.6, pandas 2.3.3, AVX512 off), which reproduced the
1.17.1 hash first. A column-by-column comparison of all seven committed files
against 0.5.1 differs in notes (21 cells) and pipeline_version only.

spacecost 0.5.1

Choose a tag to compare

@loggger101 loggger101 released this 06 Oct 16:18

Data contract 1.17.0 → 1.17.1: two citations that could not back their
numbers are withdrawn, and the numbers are now labelled unsourced. No value
moved.
Only three notes cells and the pipeline_version stamp changed;
every number in every table is the one 0.5.0 shipped. economicspace repins to
this tag only to carry the corrected notes.

Two rows cited sources that do not hold their numbers

Both were found by the General_Research evidence registry (its revision
candidates rc-048 and rc-061).

  • Beneficiation / on-site processing energy (operational_costs.csv,
    500 Wh/kg of refined product, range 100-2,000) cited "NASA
    Money-Mass-ematics 2023". The only NASA document of that name is a 4-page
    classroom worksheet for grades 7-8 about laser communications, and it never
    mentions beneficiation, ISRU or processing energy. The row now says it is an
    unsourced engineering estimate. No published per-kg beneficiation energy
    figure has been found to replace it.
  • HTP-98 (component price ~$5/kg, which feeds the HTP and
    HTP / RP-1 rows of propellants.csv) cited "Evonik / Peroxide Propulsion
    propellant-grade quotes 2024" on peroxidepropulsion.com. That domain now
    serves unrelated content and the quote is gone. Three NASA peroxide-program
    reports were read in its place and none carries a price. The component
    comment and both rows' notes now say the price is unsourced and awaits
    re-sourcing.

⚠️ Unsourced is not the same as wrong. Both values stay because nothing
better has been found, not because anything confirmed them. Treat them as
judgement, the same as the other rows whose notes say so.

The summary hash was re-recorded on the reference platform (Windows AMD64,
Python 3.13, numpy 2.2.6, pandas 2.3.3, AVX512 off). That environment
reproduced the 1.17.0 hash before the change, and the new hash differs only
because of the pipeline_version column.

Also in this release, tests only: REEXPORTED_ON_CONSUMER_MAIN is retired from
tests/test_consumer_contract.py. It held ten names economicspace's Stage 3
adapter re-exported at import and nothing read, kept "until v1.36.0 merges
there". It has, and economicspace's main (master v1.37.0) references none of
the ten anywhere. CONSUMER_SURFACE was re-checked against that main by the
same grep and matches it name for name. The 0.5.0 entry below still describes
the tuple as it shipped.

spacecost 0.5.0

Choose a tag to compare

@loggger101 loggger101 released this 29 Sep 01:27

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()...

Read more

spacecost 0.4.0

Choose a tag to compare

@loggger101 loggger101 released this 29 Sep 01:27

The launch table, re-audited and more than doubled: 36 rows to 76, every
figure given a stated range with the headline at its centre, and a new
delivered-price model built on it.
Data contract 1.15.0 → 1.16.0, and
VALUES moved, so this is the direction of the rule that obliges the bump.
operational_costs gains one row (below). The other four tables are
value-for-value identical apart from their catalog_date and
pipeline_version stamps.

🚨 This release re-prices economicspace in BOTH Stage 2 and Stage 3.
Stage 2: every in-space price comes from delivered_cost_usd_per_kg, and that
model changed (next section). Stage 3: the launch CSV, the cross-join summary
(39,852 → 84,132 rows) and every $/kg a Module 4 ranking reads have moved. The
20-cell campaign and its frozen campaign/stage2/ prices were measured under
0.3.x, so nothing measured there compares across this release until it is
re-run. delivered_cost_usd_per_kg(dest, 4253.0, stage_hardware=False)
reproduces every 0.3.x delivered price bit for bit, which is what re-deriving
an old result needs. downleg_cost_usd_per_kg did not move.

Delivered prices: a new model, chosen for today's market

delivered_cost_usd_per_kg is the launch cost a kilogram already in space
avoids, and it is the miner's REVENUE. Two things were wrong with it. Both are
fixed against today's market, reading the same central figures as the
rest of the package: each launch row's headline and each cost row's value.

  • The LEO price was Falcon 9 (reusable) at $4,253/kg, chosen as "the
    cheapest operational figure", which it never was.
    It is now whatever a
    stated rule selects: operational, open, priced by the launcher itself
    (published or contract), lowest headline usd_per_kg_to_leo. That is
    Falcon Heavy (expendable), $2,414/kg, SpaceX's $150-159M for 63.8 t. New
    Glenn's $1,922 is excluded because the bottom of its range is a rival's
    estimate; Starship because it does not fly. The rule is asserted at import, so a table edit
    that changes the winner fails loudly instead of re-pricing every
    destination. New exports: LEO_LAUNCH_VEHICLE, leo_anchor_candidates().
  • Every stage a chain expends was charged for its launch and never for
    being built.
    delivery_hardware_usd_per_kg() now charges tug dry mass at
    the new Expendable upper stage recurring cost row, lander dry mass at
    Surface lander recurring cost, what an entry discards at the TPS rate, and
    the propellant at the hydrolox price, each at its row's value.
destination 0.3.x 0.4.0 of which hardware
leo 4,253 2,414 0
geo 12,526 8,046 937
cislunar 10,810 6,878 742
mars_orbit 13,496 8,706 1,046
lunar_surface 21,210 42,635 30,597
mars_surface 45,105 184,811 159,209

Orbital destinations fall about a third; the two surfaces rise steeply,
because a lander ($200k/kg) and an aeroshell ($50k/kg) were being thrown away
for free. Two new tests hold the result
against the market: GEO may not exceed buying GTO directly and flying only the
apogee burn, and cislunar may not exceed the cheapest direct escape launch.
delivered_cost_usd_per_kg gains stage_hardware=True; with it False the
function is linear in leo_usd_per_kg again, as before.

⚠️ Still a lower bound, and meant to be. No NRE, no programme overhead,
and a hydrolox tug nobody sells as a product yet. Lunar surface at $42,635/kg
sits against real CLPS delivery at roughly $1M/kg.

New operational_costs row Expendable upper stage recurring cost,
$4,800/kg of stage dry mass: the geometric centre of a range from $1,750
(Falcon 9's mass-produced kerolox stage, $7-10M on ~4 t) to $13,400 (Centaur
III, the hydrolox stage the chains actually fly, built at a low rate).

Falcon Heavy (reusable)'s optimistic 57 t LEO is removed. It had no
source, and a headline centred on an unsourced end would carry the error into
every figure derived from it. Its band collapses onto 30 t.

Errors fixed in existing rows

These were wrong rather than stale. Each row's notes now says what it used
to carry.

  • SLS Block 1B was operational; it never flew, and NASA cancelled it
    with the Exploration Upper Stage in Feb 2026. Now concept. Its $4.1B
    was NASA OIG's per-Artemis-flight figure including Orion; it now carries
    the launch-only $2.5-2.8B. Its 41 t GTO had been scaled, not published, and
    is gone. SLS Block 1, which has flown twice, is added.
  • Zhuque-3 carried 21 t / 18.3 t LEO, which are the figures for the
    enlarged ZQ-3E. The ZQ-3 that flies lifts 8 t reusable (11.8 t expendable).
    Promoted to operational after two successful orbital flights.
  • Terran R paired its 33.5 t expendable LEO with a reusable price; now
    23.5 t. Nova carried 5 t as fully reusable; the figure is 3 t.
  • Vulcan VC6 was priced at $110M, which is ULA's starting price for the
    smallest configuration. Its 7,200 kg "escape" was the GEO payload. And it
    has never flown (every Vulcan to date was VC2S or VC4S), so it drops from
    TRL 9 to 8 while staying operational as a configuration of a flying
    vehicle.
  • PSLV-XL carried 1,100 kg to escape. Chandrayaan-1 and the Mars Orbiter
    Mission were both put into Earth orbit and raised themselves; now 0.
  • H3 (24L) cited the ¥5B target, which is for the H3-30.
  • Soyuz-2.1b GTO was the Kourou figure; Soyuz has not flown from Kourou
    since 2022.
  • Atlas V is still operational but ULA stopped selling it in 2021; it is
    now marked sold_out.
  • Prose: LVM3 was called human-rated (Gaganyaan has not flown crew); H-IIA was
    "49 flights" (50); New Glenn's "3 successful flights" omitted that NG-3 left
    its payload in the wrong orbit.

Every figure is a range, and the headline is its centre

Where a source gave a range, the row used whichever end was to hand, which for
New Glenn was the LOW price. Every row now states either one figure, where the
source gives one, or the low and high ends of a credible range, and a ranged
headline price and payload are DERIVED as the range's geometric centre,
sqrt(low x high) to three significant figures (vehicles.band_centre). A
narrow range barely moves; a wide one lands in the middle rather than at an
end. Geometric rather than arithmetic, because launch prices are uncertain by
factors: the Shuttle's $450M-$2.1B centres at $972M, not $1.28B.

The ranges themselves lean conservative, and that is where the conservatism
now lives: a target (anything not yet flying) is always the optimistic end,
never the headline. The largest LEO moves: Zhuque-3 $1,429 → $4,425/kg,
Starship $900 → $1,603 (a 35-100 t range, from what Block 2 delivered to the V3
target), Falcon Heavy reusable $1,702 → $3,333 (30 t, since SpaceX publishes no
LEO figure for that mode), Vega C $11,212 → $16,581, New Glenn $1,511 → $1,922.
Starship's tanker_flights_for_escape stays at 12, the centre of the 8-16
quoted. The full value-by-value list is recoverable with a git diff of
reference/launch_vehicles.csv.

40 new rows

Configurations people actually price: Falcon 9 and Falcon Heavy expendable,
Vulcan VC2 and VC4, H3-30. Flying vehicles that were missing: SLS Block 1,
Minotaur IV, Soyuz-2.1a, Angara A5, Proton-M, Spectrum, GSLV Mk II, SSLV, Nuri,
Long March 2C, 2D, 3B/E, 4C, 6A, 8A, 10B and 12, Kuaizhou-1A and -11, Ceres-1,
Gravity-1, Kinetica-1 and -2, Jielong-3, Zhuque-2E, Pallas-1. In development:
New Glenn 9x4, Soyuz-5, Epsilon S, Hyperbola-3. Retired, for the historical
figures a reader will meet: Pegasus XL, Ariane 5 ECA, Vega, Space Shuttle,
Saturn V.

Deliberately not added, for want of a price or payload worth citing:
Angara-1.2, Antares 330, Long March 11, 12A and 12B, Hyperbola-1, Ceres-2,
RFA One, Astra Rocket 4, and the Iranian and North Korean launchers, which are
not a market.

Schema: 20 new columns on launch_vehicles.csv

Appended after the existing 18, so no existing column moved position.

  • country, availability (open / restricted / government_only /
    sold_out / unavailable), reusability, core_propellant,
    first_flight_year, price_basis.
  • list_price_usd_low / _high and payload_{leo,gto,escape}_kg_low /
    _high.
  • usd_per_kg_to_{leo,gto,escape}_low / _high, derived.

⚠️ usd_per_kg_to_* is now DERIVED at import, not typed. A row that
types one raises. So does a headline outside its own band, a vocabulary typo,
an unknown key, or a row that does not state availability.

⚠️ payload_gto_kg and payload_escape_kg are now float columns. A
payload of NaN means "no figure published", which is not the same as 0, "does
not go there", and a column holding NaN cannot be int64. The CSV writes
5500.0 where it wrote 5500. first_flight_year is nullable Int64, so it
writes 2010, or nothing for a vehicle that has not flown.

API

  • cheapest_launch_to(..., purchasable_only=False) and spacecost launch --open-only: keep only availability == "open". Off by default.
  • validate holds the launch price and payload bands to the same
    inside-its-own-range rule as operational_costs.
  • tests/test_delivery.py pinned DATA_VERSION == "1.15.0" as a stand-in for
    "delivery is not an output". It now checks that directly.

spacecost 0.3.2

Choose a tag to compare

@loggger101 loggger101 released this 29 Sep 01:27

A test fix, and it is a fix to the CONTRACT rather than to a number. No
package code changed, no value moved, the data contract stays 1.15.0.

🚨 0.3.1's tests claimed a cross-platform guarantee this package declines to make

tests/test_delivery.py asserted == on every platform. CI went red on the
first push, on lunar_surface and nothing else: Linux returns
21209.958393766807 where Windows returns 21209.9583937668. One ULP.

That was not a defect in delivery.py. It was the wrong contract, and this
package already had the right one written down. The output is a
contract
says the six reference tables
are byte identical on every platform BECAUSE they pass values through, and the
composite summary is promised only "the same values, to a few ULP" BECAUSE it
runs through exp() -- the platform libm, which IEEE 754 does not require to
be correctly rounded. Every number in delivery.py is a mass ratio, so
every number in it is exp().
The tests were written to the consumer's
bit-identity standard and imported it into a package that says, in its own
README, that it cannot promise that here.

⚠️ lunar_surface failing ALONE is the tell. It is the only chain with
two burns at different dry-mass fractions, so it compounds the most rounding;
the single-burn chains happened to land on the same float. A cross-platform
claim that holds for six of seven cases is a claim that has not been tested.

✅ Fixed with test_parity.py's own pattern rather than a new one: exact
on the platform reference/summary_meta.json was recorded on, and a 1e-12
relative tolerance elsewhere. Not a relaxation -- on the recording platform it
is the same exact comparison, and the tolerance only applies where an exact
one would assert something exp() cannot deliver.

⚠️ The tolerance was chosen against the smallest REAL change this module
could suffer, not by eye.
Deriving TUG_ISP_S is the smallest at 2.96%;
1e-12 sits nine orders of magnitude below that and four above the observed
libm spread. Proved by feeding the checker the actual value CI returned (it
passes), the value the Isp swap would give (it fails), a 1e-9 drift (fails)
and a 1e-15 one (passes) -- and by confirming the exact comparison still bites
on the reference platform.

⚠️ economicspace is unaffected and does not have to move for this. It
runs on the reference platform, its own bit-identity is unchanged and
independently verified, and it does not run this package's test suite. The
repin to 0.3.2 is so the tag it pins has a green suite, not because anything
it computes changed.

spacecost 0.3.1

Choose a tag to compare

@loggger101 loggger101 released this 29 Sep 01:27

Data contract unchanged at 1.15.0, no value moved, every delivery output
bit-identical to 0.3.0. An audit of what the module DERIVES against what it
merely states.

Four more lookups

The downleg departure burns were all typed at 0.3.0 on the argument that two
of six had no row. Four of them do, so four of them are lookups now:
cislunar off TLI -> NRHO insertion, mars_orbit off 1-sol Mars orbit -> Earth (TEI), mars_surface off the ascent-plus-TEI pair, and the 1,870 m/s
ascent component of lunar_surface off the descent row it is symmetric with.

Deriving what agrees and typing what does not is right; typing all six
because two could not be derived was not.
That is the sharper version of the
0.3.0 note, and the correction is worth having: a blanket exception is how a
register stops being a decision.

🚨 TUG_ISP_S is 465 s and this package's hydrolox row says 452

The chains fly a 465 s cryogenic upper stage. PROPELLANTS_REFERENCE
carries hydrolox at 452 s, the RS-25 / RL-10 datasheet figure. 465 is the
top of the 450-465 s band an upper STAGE is quoted over, which is the right
figure for a tug and is what the consumer's published in-space prices were
computed with.

⚠️ Deriving it is a model change, not a refactor. Measured: swapping 465
for 452 moves the delivered price +2.96% cislunar, +3.29% geo, +3.63%
mars_orbit, +3.65% mars_surface, +5.21% lunar_surface
. Those are published
downstream. Left as it is; the discrepancy is asserted at import instead,
so the row cannot move under the comment that describes it.

The same treatment is now applied to the GEO deorbit burn, which is 1,490 m/s
against a row of 1,488: typed, and pinned to the row it disagrees with.

A register, because a derivation claim decays one literal at a time

tests/test_delivery.py reads the module's source and requires every
numeric literal to be either algebra (0 and 1) or a row on TYPED saying
which table cannot supply it. No test of the outputs can catch this: a
hardcoded 3,600 and a looked-up 3,600 give identical numbers and identical
hashes right up until the row moves and only one follows.

⚠️ Both halves are findings. A literal with no row is a value that
stopped being derived; a row with no literal is a permission still granted for
a number that has gone. Proved by planting one of each, and by planting a
literal that changes no value at all -- the case the import assertions cannot
reach.

spacecost 0.3.0

Choose a tag to compare

@loggger101 loggger101 released this 29 Sep 01:27

Data contract pipeline_version unchanged at 1.15.0, and that is the
headline rather than a footnote: build_catalog() writes the same seven CSVs
byte for byte, reference/ needed no regeneration, and economicspace does
not have to re-run Stage 3 for this.
A repin is enough. The rule is
one-directional and this release is the other direction: new code, no new data.

A seventh module: delivery, the staged leg chains

Moved out of economicspace's modules/mineral_value.py (Stage 2), where it had
lived since that module's v1.2.0. It answers two questions:

  • delivered_cost_usd_per_kg(dest) — the launch cost a kilogram already in
    space avoids, walked backwards through a chain of real stages.
  • downleg_cost_usd_per_kg(dest) — capsule, TPS, a share of the recovery
    campaign, and the departure burn that has to carry all three.

plus delivery_mass_ratio and stage_mass_ratio, and the constants behind
them: DELIVERY_CHAINS, DOWNLEG_DEPARTURE_DV_M_S, LEO_LAUNCH_USD_PER_KG,
MARS_LANDED_MASS_FRACTION.

Why here. The block it came from opened by admitting what it was: "Constants
below are cross-referenced to Module 3. They are duplicated rather than
imported because Module 2 runs BEFORE Module 3 in the pipeline order … If you
change one of these, change it in Module 3 too."
Nine numbers retyped by hand
under a manual-sync instruction. That justification was concatenation order,
and it stopped applying the moment Stage 3 became this package: a pip-installed
package has no position in a pipeline. Nothing in the block knows what an
asteroid is.

Every chain delta-v is now a lookup, not a literal. DELTA_V_REFERENCE is
the single authority for all nine, and tests/test_delivery.py fails if a
literal creeps back in.

⚠️ The downleg delta-v are typed, and that is deliberate. Four of six
agree with a row exactly; a LEO deorbit burn has no row at all, and the GEO
figure disagrees with GEO -> Earth (deorbit to entry) by 2 m/s. Deriving all
six uniformly would have moved two published prices under a release that claims
to move none. They stay as literals with the mismatch tabulated beside them.
Reconciling those two is a real question and a separate release, because it
changes what a kilogram of platinum is worth at GEO and in LEO.

⚠️ math.exp, not np.exp. rocket.py is the vectorised entry point and
this is the scalar one, and they are not interchangeable: the consumer argues
its releases from bit-identity and the two libraries may round the last bit
differently. Do not unify them.

Pinned bit-exact. tests/test_delivery.py holds all fourteen headline
values to full repr precision, captured by running the original
implementation before a line was edited — not a regression net taken
afterwards. An exact ==, because a tolerance would defeat the purpose.
Proved able to fail by perturbing the arithmetic, typing a literal into a chain,
and collapsing the None/[] distinction.

⚠️ CONSUMER SURFACE WIDENED, and it is a new shape. Four names joined
tests/test_consumer_contract.py, and they are imported by economicspace's
Stage 2, not its Stage 3 adapter. Stage 2 is the first stage to reach this
package, so a name dropped here now fails a pricing stage that runs before the
adapter does, and the traceback will not mention Stage 3.

⚠️ REPIN economicspace, in the FIVE places it now types the tag:
requirements.txt, _MASTER_PIP_SPEC in build_master.py, _PIP_SPEC in
modules/transportation.py, the same in modules/mineral_value.py (new with
this release), and the README sentence naming it.

spacecost 0.2.0

Choose a tag to compare

@loggger101 loggger101 released this 19 Sep 18:38

Adds a sixth reference table and corrects a ranking that had always been wrong.

Data contract: pipeline_version 1.14.0 → 1.15.0. Every output CSV changed, because the stamp is a column on all of them. No pre-existing value moved: the five inherited tables are byte identical to the 1.14.0 build once the two provenance columns are stripped.

⚠️ Repin economicspace. It installs this package from a pinned tag, and until that tag moves it keeps building against 1.14.0.

A sixth table: environments, 23 destinations

Solar flux, array mass factor, blackbody temperature, cycle period and eclipse fraction per destination — the power, thermal and comms penalty for being somewhere other than Earth orbit.

Behaviour change: propellantless rows are excluded by default

This moves the top of the ranking. A sail has no propellant, so its Isp is infinite, so the mass ratio is 1, so any Δv costs exactly $0.00 — and the three sail and tether rows swept the top three places of every ranking this package has ever returned, including the one spacecost propellant 6500 printed. cheapest_propellant_for now reads the two flags that exist to stop the arithmetic being believed. Pass include_propellantless=True to bring them back.

Three figures stopped being typed twice

RTG specific power, the eclipse fraction and the volatile containment mass each existed as a literal in both storage_systems and operational_costs, agreeing only because nobody had edited one yet. storage.py now reads all three from the ops rows, ops being what Stage 4 consumes and therefore the authority.

Also: validate can be heard and can fail, new schema and live-price tests, a reference row no longer reads a mutable config singleton, and dead imports removed.

Reference platform for the summary hash moved to Python 3.13 / numpy 2.2.6 / pandas 2.3.3, because that is the machine this release was cut on. It does not affect the six reference tables, which are byte identical on every platform.

Full detail in CHANGELOG.md.

pip install git+https://github.com/loggger101/spacecost@v0.2.0