ned-transients API¶
Generated from the docstrings in python/nedtransients/. For how to run the
tool, see Preparing a SNe, FRB and GRB load.
Python 3.9 and the standard library only, because that is what the NED team's machines have.
Coordinates¶
nedtransients.coords ¶
Sexagesimal coordinates in the form NED's ptable files want.
The conversion is smaller than it looks. Both upstream sources already publish positions in sexagesimal, so there is no arithmetic to do: strip the colons, make the declination's sign explicit, and leave every digit alone.
That last part matters. Checking the real FRB.2026.03.31.mod and
GRB.2026.03.31.mod against their sources, all 97 rows carry exactly the
precision the source published, which is three decimals of right ascension from
TNS and two from Swift. Rounding to a fixed number of places would have been the
obvious guess and would have been wrong for one of the two files whichever
number was picked.
ra_to_ned ¶
ra_to_ned(value)
Convert a right ascension to NED's compact form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Sexagesimal right ascension, |
required |
Returns:
| Type | Description |
|---|---|
str
|
The same value without colons, e.g. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the input holds anything other than digits, colons and a decimal
point. A right ascension is never signed, so a leading |
Examples:
>>> ra_to_ned("20:31:06.360")
'203106.360'
>>> ra_to_ned("13:40:25.49")
'134025.49'
dec_to_ned ¶
dec_to_ned(value)
Convert a declination to NED's compact form, with an explicit sign.
A declination between 0 and -1 degrees is the case worth care: the degrees
field is 00 and the sign is the only thing distinguishing north from
south. Sources are not consistent about writing + on a positive value,
so this adds one when it is missing rather than passing the input through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Sexagesimal declination, |
required |
Returns:
| Type | Description |
|---|---|
str
|
The same value without colons and always signed, e.g. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the input is not a signed or unsigned run of digits and colons. |
Examples:
>>> dec_to_ned("+53:50:56.40")
'+535056.40'
>>> dec_to_ned("-00:21:48.71")
'-002148.71'
>>> dec_to_ned("01:55:50.7")
'+015550.7'
Refcodes, loadstatus and the Jira ticket¶
nedtransients.refcodes ¶
NED-only refcodes, the loadstatus file that registers them, and the Jira text.
Before any transient can be loaded, the three sources it came from need
refcodes, and those refcodes do not exist at ADS: NED makes them up. The shape
is documented in SNeFRBGRBProcedures.pdf and confirmed by the real
2026a.TNS.FRB.GRB.ls.
The month is encoded twice over, differently:
============ ================================== =======================
Source Pattern March 2025
============ ================================== =======================
TNS YYYYTNS...M......0. 2025TNS...C......0.
FRB YYYYFRB...M...0000. 2025FRB...C...0000.
GRB YYYYGRBmm.C...0000. 2025GRB03.C...0000.
============ ================================== =======================
M is the month as a letter with A for January, so March is C. GRB uses
the two-digit month instead, and the C sitting in its refcode is a literal
standing for "Catalog" rather than a month. Getting those two confused produces
a refcode that looks right and points at the wrong month.
month_letter ¶
month_letter(month)
Return the letter TNS and FRB refcodes use for a month.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
month
|
int
|
Month number, 1 through 12. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A single upper-case letter, |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> month_letter(3)
'C'
>>> month_letter(7)
'G'
refcode ¶
refcode(kind, year, month)
Build the NED-only refcode for one source and month.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
One of |
required |
year
|
int
|
Four-digit year. |
required |
month
|
int
|
Month number, 1 through 12. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A 19-character refcode. The trailing character is |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> refcode("TNS", 2025, 3)
'2025TNS...C......0.'
>>> refcode("FRB", 2025, 3)
'2025FRB...C...0000.'
>>> refcode("GRB", 2025, 3)
'2025GRB03.C...0000.'
loadstatus_file ¶
loadstatus_file(year, month, kinds=KINDS)
Build the contents of a .ls loadstatus file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
year
|
int
|
Four-digit year the refcodes belong to. |
required |
month
|
int
|
Month number the refcodes belong to. |
required |
kinds
|
tuple of str
|
Which sources to register, defaulting to all three. |
KINDS
|
Returns:
| Type | Description |
|---|---|
str
|
The whole file, ending in a newline. The heading line is not padded; every data row is, and comes to exactly 92 characters. |
Examples:
>>> print(loadstatus_file(2026, 3, ("GRB",)))
bibcode|atype|ack|Date|basic|phot|diam|class|astro|kinem|image|spec|otype|msg
2026GRB03.C...0000.|O | | |Y |...
loadstatus_name ¶
loadstatus_name(year, batch, kinds=KINDS)
Return the filename a .ls file goes under.
The batch letter is load sequence rather than month: the real 2026a
file carries March refcodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
year
|
int
|
Four-digit year. |
required |
batch
|
str
|
The batch letter, |
required |
kinds
|
tuple of str
|
Which sources the file covers. They appear in the name in order. |
KINDS
|
Returns:
| Type | Description |
|---|---|
str
|
For example |
Examples:
>>> loadstatus_name(2026, "a")
'2026a.TNS.FRB.GRB.ls'
jira_body ¶
jira_body(year, month, obtained, kinds=KINDS)
Build the description for the author-information Jira ticket.
These refcodes are NED-made and have no match at ADS, so the author field has to be filled in by hand afterwards. The ticket is the same every month apart from the refcodes and the date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
year
|
int
|
Four-digit year the refcodes belong to. |
required |
month
|
int
|
Month number the refcodes belong to. |
required |
obtained
|
str
|
The date the lists were downloaded, as |
required |
kinds
|
tuple of str
|
Which sources are covered. |
KINDS
|
Returns:
| Type | Description |
|---|---|
str
|
The ticket body, ready to paste. |
Sources¶
nedtransients.sources ¶
Fetch and parse the two upstream lists.
Two very different sources behind one record type:
- TNS (
<https://www.wis-tns.org/>_) covers supernovae and fast radio bursts. It exports the search page as CSV, filtered by discovery date. - Swift XRT (
<https://www.swift.ac.uk/xrt_positions/>_) covers gamma-ray bursts, as one pipe-delimited ASCII table with no filtering at all.
Neither needs an account. TNS blocks a short list of well-known tool names by
User-Agent, which an honest identifier is not on. See :data:USER_AGENT.
Transient ¶
Bases: NamedTuple
One object from one of the upstream lists.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
|
name |
str
|
The object designation, e.g. |
ra |
str
|
Right ascension, sexagesimal, exactly as the source published it. |
dec |
str
|
Declination, sexagesimal, exactly as the source published it. |
discovered |
date
|
Discovery date, used for the window filter. |
tns_id |
str
|
TNS's numeric id, or |
obj_type |
str
|
The classification, e.g. |
redshift |
str
|
Redshift if the source published one, otherwise empty. |
host |
str
|
Host galaxy name if the source published one, otherwise empty. |
group |
str
|
Reporting group, e.g. |
uncertainty |
str
|
Positional uncertainty. For a GRB this is Swift's 90% error radius in arcseconds; the other kinds carry theirs in the ptable header instead. |
Cluster ¶
Bases: NamedTuple
A run of consecutive TNS ids from one reporting group.
TNS hands out ids in insertion order, so a catalogue uploaded in one go lands as a contiguous block. Collapsing twenty-seven near-identical CHIME entries into one line is what makes a 140-row candidate list scannable.
This groups, it does not decide. The real FRB.2026.03.31.mod keeps
33 of the 142 candidates in its window, and none of these separates the 33
from the 109:
========================================== ===========================
Rule tried Why it is not the rule
========================================== ===========================
Discovery-date window Kept and dropped 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 Keeps 82 that were dropped
========================================== ===========================
So the decision draws on something the TNS export does not carry, and the tool must not pretend otherwise. Every candidate is written to the ptable and a human deletes rows. A tool confident enough to drop three quarters of the list on a rule inferred from one sample would be wrong the first month CHIME changed how it reports, and wrong silently.
fetch ¶
fetch(url, timeout=60)
Retrieve a URL as text, looking enough like a browser for TNS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The address to fetch. |
required |
timeout
|
int
|
Seconds to wait, default 60. |
60
|
Returns:
| Type | Description |
|---|---|
str
|
The response body, decoded as UTF-8 with replacement. NED data is not reliably UTF-8 and a mangled character is better than a crash here, because the raw response is cached to disk for inspection either way. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
On 429, with what to do about it. Left as a plain |
tns_url ¶
tns_url(
since,
until,
frb=False,
page=0,
page_size=TNS_PAGE_SIZE,
as_csv=True,
)
Build the TNS search URL for one window, kind and page.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
since
|
date
|
Inclusive discovery-date bounds. TNS applies these server-side. |
required |
until
|
date
|
Inclusive discovery-date bounds. TNS applies these server-side. |
required |
frb
|
bool
|
Ask for fast radio bursts rather than classified supernovae. |
False
|
page
|
int
|
Zero-based page number. |
0
|
page_size
|
int
|
Rows per page. |
TNS_PAGE_SIZE
|
as_csv
|
bool
|
Ask for the CSV export. With |
True
|
Returns:
| Type | Description |
|---|---|
str
|
A URL whose response is CSV, or HTML if |
fetch_tns ¶
fetch_tns(
since,
until,
frb=False,
page_size=TNS_PAGE_SIZE,
as_csv=True,
)
Fetch every page of a TNS search and return them as one document.
TNS paginates, and a window wider than a month easily exceeds one page. Taking only the first page would silently drop the rest, which reads exactly like there being nothing more to load.
There is no API key here, so this reads the site the way a person would.
That means two routes to the same query, and :func:fetch_tns_resilient
uses the second when the first stops working.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
since
|
date
|
Inclusive discovery-date bounds. |
required |
until
|
date
|
Inclusive discovery-date bounds. |
required |
frb
|
bool
|
Ask for fast radio bursts rather than classified supernovae. |
False
|
page_size
|
int
|
Rows per page. |
TNS_PAGE_SIZE
|
as_csv
|
bool
|
Use the CSV export. With |
True
|
Returns:
| Type | Description |
|---|---|
str
|
One CSV with the heading row once, or the concatenated results pages.
Either is something :func: |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the page limit is reached, which means the window is far wider than this tool expects and the result would be truncated. |
fetch_tns_resilient ¶
fetch_tns_resilient(
since,
until,
frb=False,
page_size=TNS_PAGE_SIZE,
on_fallback=None,
)
Fetch a TNS search, falling back to the results page if the CSV fails.
Without an API key the only way in is to read the site, so the guard against it changing is to know two ways in rather than one. The CSV export is tried first because it is structured and cheap; the results page is the same query rendered for a person, and its cells carry the same fields. Both were checked against each other and agree row for row.
A fallback is not silent. It means the primary route has broken and somebody should look, even though the run itself succeeded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
since
|
date
|
Inclusive discovery-date bounds. |
required |
until
|
date
|
Inclusive discovery-date bounds. |
required |
frb
|
bool
|
Ask for fast radio bursts rather than classified supernovae. |
False
|
page_size
|
int
|
Rows per page. |
TNS_PAGE_SIZE
|
on_fallback
|
callable
|
Called with a message when the CSV route fails and the page route is used instead. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Whichever document was obtained. |
Raises:
| Type | Description |
|---|---|
Exception
|
If both routes fail, the second route's error is raised. A rate limit is not retried on the other route, since the quota covers both. |
parse_tns ¶
parse_tns(text, kind)
Parse a TNS response into records, in either format it comes in.
TNS serves the same query two ways, and this reads both: the CSV export,
and the ordinary results page. Which one arrived is sniffed from the
content rather than tracked alongside it, so a cached response is
self-describing and --tns-csv keeps working whichever a person saved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
A CSV body including its heading row, or a search results page. |
required |
kind
|
str
|
|
required |
Returns:
| Type | Description |
|---|---|
list of Transient
|
In the order TNS returned them, which is newest first. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the text is neither format, or the expected columns are missing. That is how a change at TNS surfaces, rather than as an empty list that reads like a quiet month. |
parse_swift ¶
parse_swift(text)
Parse the Swift XRT position table into records.
The table has no date column. Every GRB designation encodes its own date,
which is what :func:grb_date reads.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The ASCII table, one burst per line, five pipe-delimited fields. |
required |
Returns:
| Type | Description |
|---|---|
list of Transient
|
In the order Swift returned them, which is newest first. Rows whose position could not be determined are skipped, since there is nothing to load for them. |
grb_date ¶
grb_date(name)
Read the burst date out of a GRB designation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
A designation such as |
required |
Returns:
| Type | Description |
|---|---|
date or None
|
The burst date, or |
Examples:
>>> grb_date("GRB 260204A")
datetime.date(2026, 2, 4)
>>> grb_date("GRB 991231A")
datetime.date(1999, 12, 31)
in_window ¶
in_window(records, since, until)
Keep the records discovered within an inclusive date window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
sequence of Transient
|
What came back from a source. |
required |
since
|
date
|
Inclusive bounds. |
required |
until
|
date
|
Inclusive bounds. |
required |
Returns:
| Type | Description |
|---|---|
list of Transient
|
In the input order. |
without ¶
without(records, already)
Drop records whose names are already loaded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
sequence of Transient
|
Candidates. |
required |
already
|
sequence of str
|
Names found in the ptable files already on disk. |
required |
Returns:
| Type | Description |
|---|---|
list of Transient
|
In the input order. |
clusters ¶
clusters(records, minimum=CLUSTER_MINIMUM)
Find runs of consecutive TNS ids sharing a reporting group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
sequence of Transient
|
Candidates. Records without a TNS id, such as GRBs, are ignored. |
required |
minimum
|
int
|
Smallest run worth reporting. |
CLUSTER_MINIMUM
|
Returns:
| Type | Description |
|---|---|
list of Cluster
|
Ordered by first id. |
Ptable files¶
nedtransients.ptable ¶
Write the .mod ptable files the NED loaders read.
The format is the one samples/A13L.mod.after demonstrates: a ##refcode
line, a block of ## metadata, a pipe-delimited heading row, then the data.
The three sources do not share a layout, and the differences are not
cosmetic. Each kind's real file dictates its own metadata block, its own
columns, and its own padding, so each gets an entry in :data:LAYOUTS copied
from a file that actually loaded rather than from a template that looked
reasonable.
Verified against the real files:
- Every one of the 33 FRB rows and 66 GRB rows reproduces exactly from live source data.
- The GRB file is a contiguous slice of the Swift table with no gaps and in the
same order, so GRBs need no selection at all. FRBs do; see
:class:
~nedtransients.sources.Cluster.
Column ¶
Bases: NamedTuple
One column of a ptable.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The heading, e.g. |
value |
str
|
Which :class: |
Layout ¶
Bases: NamedTuple
Everything that differs between one source's ptable and another's.
Attributes:
| Name | Type | Description |
|---|---|---|
meta |
tuple of str
|
The |
columns |
tuple of Column
|
The columns, in order. |
padded |
bool
|
Whether data rows are aligned into columns. The real GRB file is; the real FRB file is not. Padding puts one space on each side of every |
render ¶
render(kind, refcode, records)
Build a complete ptable file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
required |
refcode
|
str
|
The NED-only refcode the objects load under. |
required |
records
|
sequence of Transient
|
The objects, in the order they should appear. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The whole file, ending in a newline. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
ValueError
|
If |
name_for ¶
name_for(kind, obtained)
Return the filename a ptable goes under.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
required |
obtained
|
date
|
The date the list was downloaded. |
required |
Returns:
| Type | Description |
|---|---|
str
|
For example |
loaded_names ¶
loaded_names(text)
Read the object names out of an existing ptable.
Used to skip objects already loaded, so the tree on disk is the record of what has been done and no separate state file is needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The contents of a |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Every value found in the |
Directory layout¶
nedtransients.layout ¶
The SIP3 directory tree a batch gets prepared in.
Mirrors what the procedure says to build by hand::
YYYY/SNe+FRB+GRB/SNe+FRB+GRB-<batch>-SIP3/
flt/
lbl/
_raw/
TNS/ FRB/ GRB/
flt and lbl are what the second-pass checks read. _raw is ours: it
holds each upstream response exactly as it arrived, so a re-run costs nothing
and anything surprising in the output can be traced back to what the server
actually said.
raw_name ¶
raw_name(kind, text)
Return the filename a freshly fetched response should be saved under.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
required |
text
|
str
|
The response, so the extension can describe it. |
required |
Returns:
| Type | Description |
|---|---|
str
|
For example |
write_window ¶
write_window(root, year, batch, since, until)
Record the window a fetch covered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
The |
required |
year
|
int
|
Four-digit year. |
required |
batch
|
str
|
Batch letter. |
required |
since
|
date
|
Inclusive bounds. |
required |
until
|
date
|
Inclusive bounds. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The path written. |
read_window ¶
read_window(root, year, batch)
Read back the window a fetch covered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
The |
required |
year
|
int
|
Four-digit year. |
required |
batch
|
str
|
Batch letter. |
required |
Returns:
| Type | Description |
|---|---|
tuple of datetime.date, or None
|
The bounds, or |
cached ¶
cached(root, year, batch, kind)
Return the path of a cached response, or None if it is not there.
Looks under every extension, since which one is present records which route answered when the batch was fetched.
ptables ¶
ptables(root, year, batch)
Return the sources a batch has already built a ptable for.
This is how loadstatus knows which refcodes to register without being
told: a source with no ptable has nothing to load, and registering a
refcode for it would point the reference database at an empty load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
The |
required |
year
|
int
|
Four-digit year. |
required |
batch
|
str
|
Batch letter. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Source names, in |
batch_dir ¶
batch_dir(root, year, batch)
Return the path of a batch's SIP3 directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
The |
required |
year
|
int
|
Four-digit year. |
required |
batch
|
str
|
Batch letter, |
required |
Returns:
| Type | Description |
|---|---|
str
|
The full path. Nothing is created. |
Examples:
>>> batch_dir("/data.tables", 2026, "a")
'/data.tables/2026/SNe+FRB+GRB/SNe+FRB+GRB-a-SIP3'
planned ¶
planned(root, year, batch, kinds)
List every directory a batch needs, in creation order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
The |
required |
year
|
int
|
Four-digit year. |
required |
batch
|
str
|
Batch letter. |
required |
kinds
|
sequence of str
|
Which sources are being loaded. Each gets its own subdirectory. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Full paths, parents before children. |
create ¶
create(paths)
Create directories, skipping any that already exist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
sequence of str
|
Full paths, parents first. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Only the paths that were actually created, so the caller can report the difference between a fresh batch and a resumed one. |
existing_names ¶
existing_names(root, year)
Collect object names from every ptable already under a year.
This is how a re-run avoids reloading what is already done. The tree is the record, so there is no separate state file to keep in step with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
The |
required |
year
|
int
|
Four-digit year to search under. |
required |
Returns:
| Type | Description |
|---|---|
list of str
|
Every name found, with duplicates left in. |