Preparing a SNe, FRB and GRB load¶
ned-transients does the copying part of the monthly transient load: it
fetches the lists from the Transient Name Server and Swift XRT, converts the
coordinates, and writes the loadstatus file, the ptables, the directory tree
and the Jira ticket body.
It loads nothing, and it decides nothing. loadstatus, ptbl.py, lbl.py and
the second-pass checks stay where they are and stay yours.
One command per step¶
The procedure has five steps and each is its own command, so you can run the parts that help and do the rest your way.
| Command | What it does |
|---|---|
scaffold |
Makes the SIP3 directory tree |
fetch |
Downloads the lists into _raw/ |
ptable |
Builds the .mod files from what fetch cached |
loadstatus |
Writes the .ls file registering the refcodes |
jira |
Prints the author-information ticket |
prepare |
Runs all five in order |
There is also refcodes, which only prints the strings and writes nothing,
which is why it is the one command taking no --root or --batch:
python3 $NT refcodes --obtained 2026-03-31
--obtained is what sets the refcode month, so pass it to see what an older
batch was registered under.
Nothing needs installing. The tool is standard-library Python 3.9, so copying
the python/ directory out of a
clone of the repository onto a
machine is the whole setup. There is no ned-transients on your PATH to run,
though: you hand the file to python3 where it sits, so the examples here keep
its path in $NT and set that first.
Run the steps one at a time:
NT=~/nedkit/python/ned-transients # wherever you copied python/ to
cd /nedefs/Project/Production/dev/data.tables
python3 $NT scaffold --root . --batch a
python3 $NT fetch --root . --batch a --since 2025-08-01 --until 2026-02-05
python3 $NT ptable --root . --batch a
python3 $NT loadstatus --root . --batch a
python3 $NT jira --root . --batch a
Or all at once, which is the same five functions in the same order:
python3 $NT prepare --root . --batch a --since 2025-08-01 --until 2026-02-05
They chain through the directory, not through each other¶
Each step reads what the last one left on disk, which is why any of them can be re-run alone and any of them can be skipped and done by hand:
ptablereads the responsesfetchcached, and the window it recorded.loadstatusreads which.modfiles exist to decide which refcodes to register. A source with no ptable gets no refcode, because that would point the reference database at a load that never happened.ptableskips objects already named in any.modunder the year, so a re-run finds nothing new rather than duplicating work.
A step that has not got what it needs says so and stops:
$ python3 $NT ptable --root . --batch a
no window recorded for this batch. Run 'fetch' first, or pass --since / --month.
Dates¶
fetch takes the range, and every other step picks it up from there.
| Flag | What it means |
|---|---|
--since |
Window start, YYYY-MM-DD |
--until |
Window end, YYYY-MM-DD. Defaults to today |
--month |
Shorthand for a whole calendar month, YYYY-MM |
--obtained |
The download date. Defaults to today |
python3 $NT fetch --root . --batch a --since 2025-08-01 --until 2026-02-05
python3 $NT fetch --root . --batch a --month 2026-01
fetch writes the range to _raw/window.txt, and ptable uses it without
being told. Passing --since to ptable overrides that, which is how you cut
a narrower ptable out of a wider fetch:
python3 $NT fetch --root . --batch a --since 2025-08-01 --until 2026-02-05
python3 $NT ptable --root . --batch a --since 2026-01-01 # last month only
--obtained is a different date from the window and it is the one that
sets the refcode month. See below.
The other flags¶
| Flag | What it means |
|---|---|
--root |
The data.tables directory |
--batch |
Batch letter, a for the year's first load. Load sequence, not month |
--only |
A subset: --only frb,grb. sne also works for the TNS list |
--dry-run |
Print everything, write nothing. The right first move |
--force |
Overwrite files that already exist |
--tns-csv |
Use a CSV you downloaded by hand instead of fetching (fetch only) |
What it writes¶
2026/SNe+FRB+GRB/SNe+FRB+GRB-a-SIP3/
2026a.TNS.FRB.GRB.ls the NED-only refcodes
TNS/TNS.2026.03.31.mod classified supernovae
FRB/FRB.2026.03.31.mod fast radio bursts
GRB/GRB.2026.03.31.mod gamma-ray bursts
flt/ lbl/ for the second-pass checks
_raw/ each response exactly as it arrived
_raw/ is not part of the procedure. It holds the unparsed responses so
ptable never needs the network, a re-run costs nothing, and anything
surprising in a ptable can be traced back to what the server actually said.
_raw/window.txt is the range fetch covered.
The window is not a month¶
The real files show loads covering much more than the month they were prepared
in: GRB.2026.03.31.mod spans August 2025 to February 2026, and
FRB.2026.03.31.mod spans December 2024 to September 2025. That is why
--since is the primary flag.
The refcode month is separate, and comes from the day you prepared the
batch rather than from the window. FRB.2026.03.31.mod loads under
2026FRB...C...0000., where C is March, the month it was built. Use
--obtained to rebuild an old batch with its original refcodes.
The three sources need different amounts of judgement¶
This is the thing to understand before trusting any output.
| Source | Selection needed |
|---|---|
| GRB | None. Load everything |
| FRB | A lot. Roughly a quarter of the candidates are kept |
| TNS | Unknown; no sample to check against |
GRBs are automatic. Checking the real GRB.2026.03.31.mod against the
Swift table, it holds every burst between its oldest and newest, in the same
order, with no gaps. All 66 rows reproduce byte for byte. Take what the tool
gives you.
FRBs are not. The real FRB.2026.03.31.mod keeps 33 of the 142 candidates
in its window, and nothing in the TNS export explains which 33. These were all
tried and none of them is the rule:
| Rule | Why it is not the rule |
|---|---|
| Discovery-date window | Kept and dropped objects interleave |
| TNS id above a threshold | Same |
| Sits in a contiguous id run | 23 kept objects sit in one |
| Reporting group | Both sets are mostly CHIME |
Internal name lacks chimefrb_ |
Misses 20 of the 33 |
| Has a discovery bibcode | Would keep 82 that were dropped |
So the decision draws on something the export does not carry, and the tool does not guess. Every candidate goes into the ptable and you delete rows.
What it does do is group them. TNS hands out ids in insertion order, so a catalogue uploaded in one go lands as a contiguous block, and the report collapses those into one line each:
FRB: 146 candidates
130 of these arrived in 9 consecutive-id runs, which is what a bulk
catalogue upload looks like. All of them are in the ptable; delete the
rows you do not want.
ids 172456-172469 14 objects CHIMEFRB 2025-01-04 .. 2025-02-02
ids 177067-177093 27 objects CHIMEFRB 2025-02-07 .. 2025-04-05
That turns scanning 146 rows into scanning nine. It is a reading aid, not a filter.
The TNS supernova layout is unverified. There is no real TNS.*.mod to
check against, so its metadata block is inferred from the FRB and GRB files.
The tool prints a warning every time it writes one. Check it against a real
file before loading, and if you find one, it belongs in
tests/fixtures/transients/.
Coordinates are not rounded¶
Both sources publish sexagesimal positions, and the conversion only strips the colons and makes the declination's sign explicit:
| Source | From the source | In the ptable |
|---|---|---|
| TNS | 20:31:06.360 |
203106.360 |
| TNS | +53:50:56.40 |
+535056.40 |
| Swift | 13:40:25.49 |
134025.49 |
| Swift | +01:55:50.7 |
+015550.7 |
Precision is whatever the source published, which is why the FRB file carries three decimals of right ascension and the GRB file two. Rounding to any fixed number of places would be wrong for one of them.
Getting the lists¶
Neither source needs an account.
Swift's position table is available as plain text and needs nothing special.
TNS answers 403 to some clients, and it is worth being precise about which, because the obvious conclusion is wrong. Measured against the live search endpoint:
User-Agent |
What TNS answers |
|---|---|
curl/8.7.1 |
403 |
python-requests/2.32 |
403 |
Python-urllib/3.x, the default |
200 |
nedkit/0.1 (+…), what the tool sends |
200 |
| a Chrome string | 200 |
So it is a blocklist of a few well-known tool names rather than a demand to
look like a browser, and an honest identifier passes. The tool says who it is
and gives TNS something to contact if the traffic ever becomes a problem. A
plain curl command against the same URL will 403, which is worth knowing
before concluding the site is down.
TNS also applies a request quota over a 60-second window and answers 429 past it. Three requests a month is nowhere near that; a loop under development is. The tool names the quota rather than reporting a bare HTTP error.
The sanctioned route for heavier use is a registered bot account, which also
unlocks the bulk daily-delta files and would be worth setting up. If the
current approach ever stops working, --tns-csv is the fallback: export the
CSV from the search page yourself and point fetch at the file.
Other ways of getting the data¶
Checked, and none of them replaces the two sources in use:
| Alternative | Verdict |
|---|---|
TNS bulk tns_public_objects files |
403 without credentials. Genuinely needs a bot account, unlike the search export |
| TNS results page | Adopted, as the fallback route. See above |
| TNS per-object pages, as GOATS scrapes | One object per request, so no way to ask "what is new" |
HEASARC swiftgrb via TAP |
Stops at December 2012. 872 rows against swift.ac.uk's 1765 |
| Other HEASARC GRB tables | All historical or mission-specific. No live XRT position feed |
| FRBSTATS | Gone. The domain is parked and for sale |
| Alert brokers (ALeRCE, Lasair, Fink) | Not evaluated in depth, because of provenance. See below |
Provenance is the reason this list is short. The refcodes record where the
data came from: 2026TNS...C......0. is credited to the "Transient Name Server
Collaboration" and its Jira ticket says obtained from wis-tns.weizmann.ac.il,
and the GRB refcode credits the "Neil Gehrels Swift Observatory Science Data
Centre". Pulling the same objects from a broker would make what NED records
about their origin untrue. So the question is not really which site to read,
it is how best to read the two that the refcodes already name.
For Swift there is nothing better: swift.ac.uk/xrt_positions is the UK Swift
Science Data Centre's own live table of enhanced positions, it needs no
account, and it is the thing the refcode cites.
For TNS, scraping is the route, and it is made as sturdy as scraping gets by having two independent ways in rather than one.
Would a bot account remove the need to scrape?¶
Only through one of its two doors, and not the one you would expect.
The TNS API is built for "tell me about this object": /api/get/search
takes a name or a position and hands back identifiers, and /api/get/object
returns the detail for one object at a time. There is no "every object
discovered between these dates, with coordinates". Getting a month that way
would be one request per object against a quota, where the search export is one
request for the lot. The API is the wrong shape for this job, which is why
tns-api on PyPI does single-object lookups and why GOATS does too.
What would replace scraping is the bulk daily-delta files, which do carry
full rows and answer "what changed since yesterday" directly. Those need the
bot account. If one ever appears, that is the route to take, and it is a
smaller change than it sounds: a different fetch, the same parser.
Libraries that already do this¶
| Library | Why it does not fit |
|---|---|
transientNamer |
Scrapes the same /search endpoint. Maintained, last release February 2025 |
tns-api |
Wraps the official API. Needs bot credentials, single-object lookups |
tom_tns |
Report submission, which is the opposite direction |
transientNamer is the closest match and independently validates the approach:
it reads the same page, sends its own honest User-Agent, and asks for columns
with display[...]=1 rather than trusting the default view, which is a trick
worth copying and now is. Its changelog carries a "fix TNS search after TNS API
update" entry, which is a fair warning about how durable any of this is.
It still would not do this job, for reasons that have nothing to do with whether packages can be installed:
- No object-type filter. Its search sends
ra,decl,radius,name,internal_nameand a period, and that is all. It cannot ask for FRBs (objtype[]=130) or restrict to classified supernovae, which are exactly the two queriesfetchmakes. - Relative windows only. It takes
discInLastDays, so "the last 90 days", not "1 December 2024 to 30 September 2025". A load window that moves with the calendar cannot rebuild an old batch, and--obtainedexists precisely so an old batch can be rebuilt. - It parses with one large regex over the row, which fixes the column order. The parser here keys on each cell's class, so a reordered table still reads correctly; there is a test for that.
For the record, its TNS search does not use BeautifulSoup at all: requests
and re. BeautifulSoup appears elsewhere in the package, for a different
feature.
So the hand-rolled eighty lines are not a workaround for a missing dependency. They do something the library does not.
TNS also paginates. fetch follows every page, because stopping at the first
one silently truncates the list, and a truncated list looks exactly like a
quiet month.
Only fetch goes to the network. Every other step works from _raw/, so once
a batch is fetched you can rebuild it offline as many times as you like.
Two routes in, because there is no API key¶
Without a bot account the only way to get the list is to read the site, so the
guard against TNS changing is knowing two ways in rather than one. fetch
tries them in order:
| Order | Route | What it costs |
|---|---|---|
| 1 | The CSV export, &format=csv |
Structured, and about 50 KB for a month |
| 2 | The ordinary results page | Same query, same rows, about 3.9 MB |
The results page marks every cell with class="cell-name", cell-ra,
cell-decl and so on, and those carry the same values as the CSV columns.
Checked against each other over a live month: 132 records each, identical
record for record. The fixture test keeps them honest offline.
The CSV route is tried first because it is a tenth of a percent of the size.
If it fails, fetch says so and carries on:
! TNS CSV export failed (ValueError: ...). Falling back to the results
page. The run is fine; the CSV route needs looking at.
fetched FRB 17 objects -> .../_raw/tns-frb.html
The fallback is never silent. The run succeeding does not mean nothing is
wrong, and the cached file's extension records which route answered, so
_raw/tns-frb.html is itself the sign that route 1 needs attention.
A rate limit does not trigger the fallback, since the quota covers both routes and asking the same server the same question a different way would only spend more of it.
--tns-csv accepts either format too, so a page saved out of a browser works
as well as a CSV export.
This is roughly what the GOATS project
does for TNS, and its client is worth knowing about: it scrapes
wis-tns.org/object/<name> for one object at a time and identifies itself as
GOATS.TNSClient/1.0, which is independent confirmation that a plain honest
User-Agent is all TNS wants. Its per-object route does not answer "everything
in this date range", which is why it is not what fetch uses.
After it runs¶
Nothing has been loaded. From here the procedure is unchanged:
loadstatuson the.lsfile, check the.msg, thenloadstatus -load_ok.- File the Jira ticket the tool printed.
- Edit the ptables: delete the FRB rows you do not want.
ptbl.pyandlbl.py.- Copy the
.fltfiles intoflt/and run the SecondPassOfChecking steps.
Step 3 is the one worth budgeting time for, and it is why the steps are
separate: run ptable, edit the file, then carry on with loadstatus.