Orbital data tooling · NEOGY LLC
On 11 July 2026 the US Space Force catalog assigned number 100000 (to the Portuguese CubeSat SARAMAGO) after exhausting the five-digit range, which ends at 69999 (CelesTrak); provider data now carries six-digit and nine-digit catalog numbers, Space-Track serves the lettered (Alpha-5) form, and libraries reading them may not expect these forms.
gpconf is a free test kit that tells you whether your satellite software handles catalog numbers above 99,999 — built from real CelesTrak data, with every expected answer traced to its source. It is the GP/OMM conformance corpus: eighteen cases with no invented element sets, a runner that installs with pip install gpconf, presets that test a library with no adapter written, and a GitHub Action for CI.
20 libraries so far, each run at the version and on the date its report names, and every finding reproduced on the library's own code before it was reported. These are results for a specific version on a specific date, not verdicts on the projects. Many of the first findings are Alpha-5 TLE failures, but CelesTrak emits no Alpha-5 at all, so users who take their TLEs from CelesTrak don't meet those failures today. States and releases as of 2026-11-10; the whole record, with who fixed what, is on the upstream record.
37 findings: 9 fixed by my pull requests, 9 fixed by maintainers and contributors after my report, 16 still open, and 3 that ended another way.
- python-sgp4: an empty
OBJECT_IDin OMM XML raisedTypeError. #171, PR #172, merged 2026-09-24, not yet released. - satellite.js: OMM JSON epochs truncated to the millisecond. #185, PR #186, merged 2026-09-26, not yet released.
- satellite.js: no decoder for Alpha-5 catalog fields. PR #187, merged 2026-09-28, not yet released.
- satellite.js: a stray letter in a TLE field read with no error. #190, PR #197, merged 2026-10-14, not yet released.
- Gpredict: the mean motion lost its last digit on every record. #426, PR #428, merged 2026-10-08, released in v2.7.
- gods-eye-view: Alpha-5 satellites collapsed onto one entry keyed NaN, the rest dropped. #751, PR #767, merged 2026-10-17, released in v0.3.0.
- tle.js: Alpha-5 fields gave NaN; two-digit years pivoted at 50. #62, PR #64, merged 2026-10-19, released in v6.0.0.
- SGP.NET: Alpha-5 TLEs refused. #35, PR #47, merged 2026-11-10, not yet released.
- SGP.NET:
OmmKvnParserrefused a day-of-year EPOCH and a unit in brackets, two forms CCSDS 502.0-B-3 allows. #48, PR #49, merged 2026-11-10, not yet released.
- astroz: Alpha-5 decoded without skipping I and O. #97, PR #99, fixed by ATTron, the maintainer, released in v0.13.0.
- astroz:
Tle.epochoff by hundreds of days. #98, PR #99, fixed by ATTron, the maintainer, released in v0.13.0. - astroz: seconds passed through a half-precision float, up to 15.5 ms off. #102, PR #104, fixed by ATTron, the maintainer, released in v0.14.0.
- libsgp4: every Alpha-5 field refused (results posted on the open PR). PR #42, PR #46, fixed by dnwrnr, the maintainer, released in v3.0.
- Gpredict: a letter in the epoch field accepted. #427, PR #430, fixed by Wahaj18, a contributor, released in v2.7.
- gods-eye-view: a set missing a line dropped the rest of its group. #906, PR #936, fixed by xiehuanyi, a contributor, released in v0.3.0.
- SGPKit (swift-sgp4): satellites numbered 100000 and up refused. #4, fixed by csanfilippo, the maintainer, released in 4.1.0.
- satvis:
ommToTlewrote element set 999 forELEMENT_SET_NO0. #84, PR #85, fixed by anttikuosmanen-rgb, the maintainer, on master; the project has no releases. - akhenakh/sgp4: Alpha-5 catalog numbers refused; a type 4 TLE propagated as SGP4. #16, fixed by Fabrice Aneche (akhenakh), the maintainer, released in v1.0.4.
- PyEphem: Alpha-5 fields read as catalog number 0, silently. #296, open.
- python-sgp4:
Satrec.sgp4initwrites I and O as Alpha-5 letters. #173, open. - python-sgp4: a type 4 (SGP4-XP) TLE propagated as plain SGP4. #178, open.
- python-sgp4: line 2's catalog number not checked against line 1's. #179, open; the maintainer keeps this check out of scope.
- Vallado's SGP4 code (CelesTrak's repository):
twoline2rvreads an Alpha-5 field beginning with I or O as a five-digit number. #172, open. - Vallado's SGP4 code (CelesTrak's repository):
twoline2rvreturns the element set and revolution numbers with the checksum digit attached. #174, open. - strf: no range check in
number_to_alpha5above 339999. #88, PR #89, open; a fix by ppapadeas in review. - libsgp4: epoch rounded by 8 µs;
FromCsvreads stray microseconds. #45, open. - tle.js:
getEpochDay()reads up to a stray letter andisValidTLE()calls the TLE valid. #63, open. - Basilisk:
satOmm2elemskipped CelesTrak's KVN (MEAN_ELEMENT_THEORY = SGP/SGP4). #1673, PR #1675, open; a fix by schaubh in review. - satkit:
TLE.from_linesdrops a whole file for one bad set. #286, open. - SatelliteKit: B* read from a TLE as e-53 instead of e-05. #16, PR #17, open; a fix by djsincla in review.
- SatelliteKit: deep-space Lyddane check on
abs(inclination)where Vallado's is signed. #24, open. - SGPKit (swift-sgp4):
TLEDecoder.decodeCollectiondrops every TLE in a file for one bad set. #16, open. - brahe:
OMM.from_strrefuses CelesTrak's ndm-wrapped XML and KVN with an empty CREATION_DATE;parse_norad_id("A+123")accepted; Alpha-5 encoding below 100000 unpadded. #620, open. - sgp4 crate (Rust): a type 4 (SGP4-XP) TLE propagated as plain SGP4. #23, open.
- PyEphem: a letter in the epoch field accepted and the epoch read up to it. #297, closed by the maintainer without a change.
- SatDump: Alpha-5 sets dropped silently; SupGP CSV rejected whole on master. #1221, closed by me after the maintainers said both parts were already planned.
- python-sgp4: nine-digit NORAD_CAT_ID refused by the OMM reader: someone else's report (michaelbaisch), where I tested the open pull request. #169, open.
| library | fixed by my PR | fixed by others | open | other |
|---|---|---|---|---|
| akhenakh/sgp4 | 1 | |||
| astroz | 3 | |||
| Basilisk | 1 | |||
| brahe | 1 | |||
| gods-eye-view | 1 | 1 | ||
| Gpredict | 1 | 1 | ||
| libsgp4 | 1 | 1 | ||
| PyEphem | 1 | 1 | ||
| python-sgp4 | 1 | 3 | 1 | |
| SatDump | 1 | |||
| satellite.js | 3 | |||
| SatelliteKit | 2 | |||
| satkit | 1 | |||
| satvis | 1 | |||
| SGP.NET | 2 | |||
| sgp4 crate (Rust) | 1 | |||
| SGPKit (swift-sgp4) | 1 | 1 | ||
| strf | 1 | |||
| tle.js | 1 | 1 | ||
| Vallado's SGP4 code (CelesTrak's repository) | 2 |
gpconf is at v0.8.0, on PyPI and archived on Zenodo under the concept DOI 10.5281/zenodo.22867654, which always resolves to the latest release; 0.8.0's own is 10.5281/zenodo.23853937. From 0.7.0 it is under Apache-2.0; releases up to 0.6.2 stay MIT. Since it became installable with pip at 0.3.0:
- 0.4.0 added the eighteenth case, the first to hand a parser corrupt input.
- 0.5.0 let an adapter say "unsupported" for an operation it does not have, counted each record once, and made the package builds reproducible.
- 0.5.1 cut a user's fetch from 61 requests and 12.5 MB to 50 requests and 3.2 MB, and made it stop at any response it does not expect.
- 0.6.0 gave the JSON report a summary a script can rely on, took the name gpconf, and cut the fetch to 46 requests.
- 0.6.1 made the fetch name the kit and its version in its
User-Agent. - 0.6.2 fixed two things an audit found: a mistyped case name passed silently, and
check-tleon a missing file printed a traceback. - 0.7.0 is about the grader itself. SM-26 answered my audit ask with seven wrong-on-purpose parsers that passed 0.6.1, and two blind audits wrote twenty-one more. What got through is closed, all of them are now tests, and more is graded before any fetch. No published case verdict moved.
- 0.8.0 grades every copy of a catalog number: when a file holds the same id more than once, each copy a parser returns is checked against the record it matches, so a parser that returns the last copy twice no longer passes. Before the release I ran a mutation test of the grading code; the tests caught 75.5% of the changes, and the survivors are listed for 0.8.1. One published verdict moved: python-sgp4's mean-motion derivative case now passes within tolerance.
pip install gpconf
gpconf fetch # once; fetches the fixtures under CelesTrak's usage policy
gpconf run --preset reference # the control
gpconf presets # libraries testable with no adapter written
gpconf run --adapter mypkg.gpconf_adapter:Parser --json report.json # your parserGPKit reads GP data — OMM and TLE, six-digit catalog numbers included — and passes gpconf: 17 of its 18 cases, none failing (the eighteenth checks data and runs no parser). v1.3.0, MIT, on the Swift Package Index. Each release also carries a playground module, GPKit built for WebAssembly, held by CI to the native build's answers; it runs the playground, a page that reads a TLE or an OMM and works out the next day's passes in the browser. Reports to the other Swift readers are listed above, under SatelliteKit and SGPKit.
NEOGY LLC, orbital data tooling. Results and a live tracker at gpconf.neogy.dev · gpconf · neogy.dev


