spectrum-wrangler 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spectrum_wrangler-0.3.0/AGENTS.md +38 -0
- spectrum_wrangler-0.3.0/CHANGELOG.md +45 -0
- spectrum_wrangler-0.3.0/LICENSE +21 -0
- spectrum_wrangler-0.3.0/MANIFEST.in +7 -0
- spectrum_wrangler-0.3.0/PKG-INFO +194 -0
- spectrum_wrangler-0.3.0/README.md +164 -0
- spectrum_wrangler-0.3.0/docs/DEPENDENCIES.md +23 -0
- spectrum_wrangler-0.3.0/docs/MIGRATION.md +47 -0
- spectrum_wrangler-0.3.0/docs/QUERYING.md +191 -0
- spectrum_wrangler-0.3.0/docs/RESEARCH-2026.md +65 -0
- spectrum_wrangler-0.3.0/docs/img/radiowave.png +0 -0
- spectrum_wrangler-0.3.0/pyproject.toml +56 -0
- spectrum_wrangler-0.3.0/setup.cfg +4 -0
- spectrum_wrangler-0.3.0/skills/spectrum-wrangler/SKILL.md +131 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/__init__.py +3 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/__main__.py +5 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/agent.py +22 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/cli.py +610 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/db.py +373 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/query.py +614 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/uls.py +622 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler/uls_schema.json +1648 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler.egg-info/PKG-INFO +194 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler.egg-info/SOURCES.txt +29 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler.egg-info/dependency_links.txt +1 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler.egg-info/entry_points.txt +3 -0
- spectrum_wrangler-0.3.0/spectrum_wrangler.egg-info/top_level.txt +1 -0
- spectrum_wrangler-0.3.0/tests/fixtures.py +82 -0
- spectrum_wrangler-0.3.0/tests/test_agent.py +253 -0
- spectrum_wrangler-0.3.0/tests/test_cli.py +352 -0
- spectrum_wrangler-0.3.0/tests/test_core.py +214 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Agent notes
|
|
2
|
+
|
|
3
|
+
Spectrum Wrangler is one read-only CLI over a local SQLite build of the FCC's
|
|
4
|
+
public radio licensing data (ULS). People and agents use the same commands.
|
|
5
|
+
|
|
6
|
+
## Using the tool
|
|
7
|
+
|
|
8
|
+
* Read [skills/spectrum-wrangler/SKILL.md](skills/spectrum-wrangler/SKILL.md)
|
|
9
|
+
before querying — it carries the six dataset traps that produce confidently
|
|
10
|
+
wrong answers (row-multiplying joins, `MM/DD/YYYY` text dates, the missing
|
|
11
|
+
organization key, and the rest).
|
|
12
|
+
* `spectrum-wrangler capabilities` describes every command, argument, format,
|
|
13
|
+
and exit code as JSON in one call.
|
|
14
|
+
* Run `spectrum-wrangler status` first and cite the FCC publication dates it
|
|
15
|
+
reports. An empty result means "not in the loaded snapshots", never "no such
|
|
16
|
+
authorization exists".
|
|
17
|
+
* Exit codes: `0` success, `1` understood but nothing matched, `2` bad request.
|
|
18
|
+
Errors are JSON on stderr in machine formats.
|
|
19
|
+
* The database resolves from `--database`, then `$SPECTRUM_WRANGLER_DB`, then
|
|
20
|
+
`./data/spectrum-wrangler.sqlite3` if already built, then the per-user data
|
|
21
|
+
directory. If it is missing it must be built with `spectrum-wrangler refresh`
|
|
22
|
+
(a 1.25 GB download; `--archive paging` is a small start).
|
|
23
|
+
|
|
24
|
+
## Working on the code
|
|
25
|
+
|
|
26
|
+
* Tests: `python3 -m unittest discover -s tests -v` — hermetic, no network,
|
|
27
|
+
under a second. Also run `python3 -m compileall -q spectrum_wrangler tests`.
|
|
28
|
+
* Every query operation is declared once in `OPERATIONS` in
|
|
29
|
+
`spectrum_wrangler/cli.py`; the argparse tree, help text, and `capabilities`
|
|
30
|
+
manifest are generated from it. Never wire a subcommand by hand.
|
|
31
|
+
* There are no third-party runtime dependencies, deliberately. Read
|
|
32
|
+
[docs/DEPENDENCIES.md](docs/DEPENDENCIES.md) before adding, updating, or even
|
|
33
|
+
temporarily executing a package.
|
|
34
|
+
* `load.py` and `sample-fcc.csv` are legacy Python 2 artifacts kept for
|
|
35
|
+
historical reference. Do not run, modernize, or delete them.
|
|
36
|
+
* [docs/QUERYING.md](docs/QUERYING.md) has the join semantics and worked
|
|
37
|
+
investigations; [docs/RESEARCH-2026.md](docs/RESEARCH-2026.md) is an archived
|
|
38
|
+
record of the 2026 source migration, not a live status page.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — 2026-08-30
|
|
4
|
+
|
|
5
|
+
The rewrite release: same mission as 2017, current FCC publication, nothing to
|
|
6
|
+
install. First release published to PyPI.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
* **Source.** FCC License View is retired; the tool now reads the FCC's ULS
|
|
10
|
+
weekly complete archives directly. See [docs/RESEARCH-2026.md](docs/RESEARCH-2026.md).
|
|
11
|
+
* **Storage.** PostgreSQL and PostGIS replaced by SQLite with FTS5 and RTree —
|
|
12
|
+
Python 3.11+ standard library only, no services, no third-party packages.
|
|
13
|
+
See [docs/MIGRATION.md](docs/MIGRATION.md).
|
|
14
|
+
* The database, download cache, and provenance manifest now default to a
|
|
15
|
+
per-user data directory (`~/Library/Application Support/spectrum-wrangler`
|
|
16
|
+
on macOS, `$XDG_DATA_HOME/spectrum-wrangler` on Linux,
|
|
17
|
+
`%LOCALAPPDATA%\spectrum-wrangler` on Windows), so the installed CLI works
|
|
18
|
+
from any directory. A database already built under `./data` keeps working,
|
|
19
|
+
and `$SPECTRUM_WRANGLER_DB` or `--database` overrides both.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
* One CLI for people and agents: structured commands, a `capabilities`
|
|
23
|
+
manifest generated from the same declarations as the argparse tree, output
|
|
24
|
+
as table/JSON/NDJSON/CSV, distinct exit codes, and JSON errors on stderr.
|
|
25
|
+
* Read-only enforcement at the SQLite connection, with bounded single-statement
|
|
26
|
+
SQL (`SELECT`/`WITH`/`EXPLAIN`, timeout, 1,000-row cap).
|
|
27
|
+
* Full-fidelity import: all 89 documented ULS record layouts kept verbatim in
|
|
28
|
+
`raw_*` tables, common records normalized and indexed, publication drift
|
|
29
|
+
preserved in `extra_fields_json`.
|
|
30
|
+
* Per-archive provenance: URL, FCC publication time, retrieval time, ETag,
|
|
31
|
+
SHA-256, byte size, and record counts, written to `source-manifest.json`.
|
|
32
|
+
* An agent skill ([skills/spectrum-wrangler/SKILL.md](skills/spectrum-wrangler/SKILL.md))
|
|
33
|
+
carrying the dataset judgment a tool schema cannot.
|
|
34
|
+
* Byte-count download progress on an interactive terminal.
|
|
35
|
+
* CI across Python 3.11–3.14 on Linux, macOS, and Windows.
|
|
36
|
+
|
|
37
|
+
### Removed
|
|
38
|
+
* The Model Context Protocol server, in favour of the CLI plus the skill.
|
|
39
|
+
* The PostgreSQL/PostGIS/Docker requirement.
|
|
40
|
+
|
|
41
|
+
## 0.2.x and earlier — 2017
|
|
42
|
+
|
|
43
|
+
The original License View loader: Python 2, `requests`, `psycopg2`, and a
|
|
44
|
+
PostGIS-backed import of the FCC's now-retired denormalized CSV. Kept for
|
|
45
|
+
reference as `load.py`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2026 Marc DaCosta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Ships the docs, the agent skill, and the hermetic test suite in the sdist so
|
|
2
|
+
# a distribution packager can read and verify what they are building.
|
|
3
|
+
include AGENTS.md
|
|
4
|
+
include CHANGELOG.md
|
|
5
|
+
recursive-include docs *.md *.png
|
|
6
|
+
recursive-include skills *.md
|
|
7
|
+
recursive-include tests *.py
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spectrum-wrangler
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A provenance-tracked local database and query interface for FCC spectrum licenses
|
|
5
|
+
Author: Marc DaCosta
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/marcdacosta/spectrum-wrangler
|
|
8
|
+
Project-URL: Documentation, https://github.com/marcdacosta/spectrum-wrangler/blob/master/docs/QUERYING.md
|
|
9
|
+
Project-URL: Changelog, https://github.com/marcdacosta/spectrum-wrangler/blob/master/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/marcdacosta/spectrum-wrangler/issues
|
|
11
|
+
Keywords: fcc,uls,spectrum,radio,licensing,callsign,sqlite,investigative-journalism
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Intended Audience :: Legal Industry
|
|
16
|
+
Classifier: Intended Audience :: Other Audience
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Communications :: Ham Radio
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
[](https://github.com/marcdacosta/spectrum-wrangler/actions/workflows/tests.yml)
|
|
34
|
+
[](https://pypi.org/project/spectrum-wrangler/)
|
|
35
|
+
|
|
36
|
+
# What is Spectrum Wrangler?
|
|
37
|
+
Spectrum Wrangler will download the most recent version of the FCC's public licensing data, parse it into a local database, clean up the geo columns so they can be used to query on, and produce a spatial index on them.
|
|
38
|
+
|
|
39
|
+
# About the FCC licensing data
|
|
40
|
+
In the United States the Federal Communication Commission is responsible for regulating the use of the radio waves. The FCC issues licenses for radio transmitters that exceed certain power levels or which operate on certain frequencies. Those licenses are public, and they say who is allowed to transmit, where, on what frequency, and with how much power. Taken together they are a map of who owns the air.
|
|
41
|
+
|
|
42
|
+
The original version of this project used the [License View](http://reboot.fcc.gov/license-view/) database, which harmonized the schema of the various parts of the [Universal Licensing System](http://wireless.fcc.gov/uls/index.htm?job=transaction&page=weekly) (ULS) into one flat CSV. The FCC no longer maintains License View. Spectrum Wrangler now reads ULS directly, which is the same data with more detail and no denormalization: a license header, its licensee, its locations, its antennas, its frequency assignments, and its emissions all arrive as separate related records.
|
|
43
|
+
|
|
44
|
+
The FCC snapshot published August 23, 2026 holds 5.2M licenses, 2.8M of them active, across 129 radio services — 6.6M licensee records, 3.3M transmitter locations (2.4M with usable coordinates), and 11.4M frequency assignments. Your own numbers will differ; `status` reports what you actually loaded.
|
|
45
|
+
|
|
46
|
+
# How to use
|
|
47
|
+
* Install it: `uv tool install spectrum-wrangler`, `pipx install spectrum-wrangler`, or `pip install spectrum-wrangler`. Python 3.11 or newer is the only requirement, and a clone of this repository works identically without installing (see below).
|
|
48
|
+
* Run `spectrum-wrangler refresh` to download and index every current weekly archive.
|
|
49
|
+
* Run `spectrum-wrangler refresh --archive paging` first if you want a small database to try.
|
|
50
|
+
* Query it with `spectrum-wrangler <command>`, or point an AI agent at it (see below).
|
|
51
|
+
* Read [the querying guide](docs/QUERYING.md) for how the records join and what to watch out for.
|
|
52
|
+
|
|
53
|
+
The complete download is about 1.2 GB compressed and produces a database of roughly 23 GB once every source field is retained and indexed. Downloads are cached and content-hashed, so re-running `refresh` re-imports only what actually changed. Use `--normalized-only` for a much smaller database when you do not need every raw FCC field.
|
|
54
|
+
|
|
55
|
+
The database, download cache, and provenance manifest live in a per-user data directory: `~/Library/Application Support/spectrum-wrangler` on macOS, `$XDG_DATA_HOME/spectrum-wrangler` (usually under `~/.local/share`) on Linux, `%LOCALAPPDATA%\spectrum-wrangler` on Windows. A database already built under `./data` in a repository checkout keeps being used, and `--database` or the `SPECTRUM_WRANGLER_DB` environment variable points anywhere else.
|
|
56
|
+
|
|
57
|
+
Generated archives and databases stay out of Git. [data/source-manifest.json](data/source-manifest.json) is committed instead, and records each archive's official URL, FCC publication time, retrieval time, ETag, SHA-256, byte size, record counts, and any observed schema drift.
|
|
58
|
+
|
|
59
|
+
# Do I need Docker?
|
|
60
|
+
No. A project to Dockerize `spectrum-wrangler` can be found [here](https://github.com/brannondorsey/spectrum-wrangler-docker), and it made sense when this tool required a PostgreSQL server with the PostGIS extension installed and configured. That was the hard part, and a container solved it.
|
|
61
|
+
|
|
62
|
+
Version 0.3 has no services and no dependencies, so a container would add a step rather than remove one — and the expensive artifact is the 23 GB database, which has to live on your disk either way. The Docker wrapper targets the retired PostgreSQL/License View workflow and is kept as a historical reference, not the current path.
|
|
63
|
+
|
|
64
|
+
# Requirements
|
|
65
|
+
* **Python 3.11 or newer, and nothing else.** SQLite, FTS5, and RTree ship with Python. PostgreSQL, PostGIS, and Docker are no longer needed.
|
|
66
|
+
* **Disk: about 25 GB** for a full load — a 23 GB database plus a 1.25 GB download cache. `--normalized-only` cuts the database to roughly a third of that.
|
|
67
|
+
* **Memory: under 600 MB.** The importer streams, so memory tracks the widest record rather than the size of the archive. Measured peaks: 77 MB for the smallest archive, 537 MB for the largest (35.4M records). An 8 GB laptop is comfortable.
|
|
68
|
+
* **Time: roughly 15 minutes** of import for the full set on a modern laptop once downloaded, plus the download itself. Importing runs at about 130,000 records per second.
|
|
69
|
+
|
|
70
|
+
Install it from PyPI:
|
|
71
|
+
|
|
72
|
+
uv tool install spectrum-wrangler # or: pipx install spectrum-wrangler
|
|
73
|
+
|
|
74
|
+
Or clone the repository and run it in place, no install step at all:
|
|
75
|
+
|
|
76
|
+
git clone https://github.com/marcdacosta/spectrum-wrangler.git
|
|
77
|
+
cd spectrum-wrangler
|
|
78
|
+
python3 -m spectrum_wrangler sources
|
|
79
|
+
|
|
80
|
+
The two are the same program: `python3 -m spectrum_wrangler` from a checkout accepts every command written as `spectrum-wrangler` in the examples here.
|
|
81
|
+
|
|
82
|
+
## Start small
|
|
83
|
+
One archive is enough to learn the shape of the data, and the smallest is a four-second import:
|
|
84
|
+
|
|
85
|
+
| Archive | Download | Records | Database |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `--archive paging` | 6.5 MB | 750K | 142 MB |
|
|
88
|
+
| `--archive amateur` | 198 MB | 10.5M | 2.3 GB (710 MB with `--normalized-only`) |
|
|
89
|
+
| everything | 1.25 GB | 105.9M | 23 GB |
|
|
90
|
+
|
|
91
|
+
Run `spectrum-wrangler sources` to see the full list of 14 archives.
|
|
92
|
+
|
|
93
|
+
# Examples
|
|
94
|
+
Example raw data extract from the original License View publication can be found in `sample-fcc.csv`. [The querying guide](docs/QUERYING.md) has worked investigations and the join semantics behind these.
|
|
95
|
+
|
|
96
|
+
## Query to check what is loaded and how current it is
|
|
97
|
+
|
|
98
|
+
spectrum-wrangler status
|
|
99
|
+
|
|
100
|
+
## Look up a call sign
|
|
101
|
+
|
|
102
|
+
spectrum-wrangler callsign W1AW
|
|
103
|
+
|
|
104
|
+
[
|
|
105
|
+
{
|
|
106
|
+
"callsign": "W1AW",
|
|
107
|
+
"display_name": "ARRL HQ OPERATORS CLUB",
|
|
108
|
+
"radio_service_code": "HA",
|
|
109
|
+
"state": "CT",
|
|
110
|
+
"license_status": "A",
|
|
111
|
+
"grant_date": "12/08/2020",
|
|
112
|
+
"expired_date": "02/26/2031",
|
|
113
|
+
"unique_system_id": 780866
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
## Ranged queries
|
|
118
|
+
Query to search within 1000m of [40.7253319,-74.0076834](https://www.google.com/maps/search/40.7253319,-74.0076834?sa=X&ved=0ahUKEwjj06n16p_WAhWR8oMKHUMfA9oQ8gEIJzAA) and export the results:
|
|
119
|
+
|
|
120
|
+
spectrum-wrangler --format csv \
|
|
121
|
+
nearby 40.7253319 -74.0076834 --radius-km 1 > /tmp/antennas.csv
|
|
122
|
+
|
|
123
|
+
Or in SQL, against the same database:
|
|
124
|
+
|
|
125
|
+
spectrum-wrangler sql \
|
|
126
|
+
"SELECT radio_service_code, count(*) AS licenses
|
|
127
|
+
FROM licenses GROUP BY 1 ORDER BY 2 DESC"
|
|
128
|
+
|
|
129
|
+
# How agents use it
|
|
130
|
+
Spectrum Wrangler is one CLI, used the same way by a person and by an agent. Every operation is declared once in the code, so the commands, the help text, and the machine-readable manifest cannot drift apart.
|
|
131
|
+
|
|
132
|
+
spectrum-wrangler capabilities
|
|
133
|
+
|
|
134
|
+
`capabilities` describes every command, argument, output format, and exit code as JSON, so an agent can discover the whole surface in one call. Output adapts to the consumer — a table on a terminal, JSON when piped — and `--format table|json|ndjson|csv` always wins.
|
|
135
|
+
|
|
136
|
+
# everything about one licence, in one call
|
|
137
|
+
spectrum-wrangler license --callsign W1AW
|
|
138
|
+
|
|
139
|
+
# who else transmits from this building
|
|
140
|
+
spectrum-wrangler nearby 40.748444 -73.985694 --radius-km 0.1
|
|
141
|
+
|
|
142
|
+
# who holds a band, and who holds a name
|
|
143
|
+
spectrum-wrangler band 462 468 --group-by licensee
|
|
144
|
+
spectrum-wrangler organization --name "NEW YORK CITY POLICE"
|
|
145
|
+
|
|
146
|
+
# what expires this quarter, as CSV
|
|
147
|
+
spectrum-wrangler --format csv expirations --start 2026-10-01 --end 2026-12-31 --state NY
|
|
148
|
+
|
|
149
|
+
Exit codes separate the cases a program must tell apart: `0` success, `1` understood but nothing matched, `2` bad request. Errors are JSON on stderr in machine formats. The `spectrum-wrangler-agent` entry point is the same CLI with output pinned to JSON whether or not it is attached to a terminal, and [AGENTS.md](AGENTS.md) is the in-repository briefing for agents working on or with the code.
|
|
150
|
+
|
|
151
|
+
The CLI is read-only and enforces it at the SQLite connection rather than by convention. Custom SQL accepts one `SELECT`, `WITH`, or `EXPLAIN`, runs with a timeout, and returns at most 1,000 rows.
|
|
152
|
+
|
|
153
|
+
## The skill
|
|
154
|
+
[`skills/spectrum-wrangler/SKILL.md`](skills/spectrum-wrangler/SKILL.md) is the companion to the CLI, and it carries the part a tool schema cannot: what will make you confidently wrong about this dataset. Joining on `unique_system_id` alone turns one call sign into 1.8 million rows. Dates are `MM/DD/YYYY` text, so sorting them sorts by month. Counting what one organization holds has no exact answer. Point any agent at it.
|
|
155
|
+
|
|
156
|
+
To install the skill for Claude Code, link it into your skills directory:
|
|
157
|
+
|
|
158
|
+
mkdir -p ~/.claude/skills
|
|
159
|
+
ln -s "$PWD/skills/spectrum-wrangler" ~/.claude/skills/spectrum-wrangler
|
|
160
|
+
|
|
161
|
+
Earlier versions shipped a Model Context Protocol server. It was removed in favour of the CLI plus the skill: it duplicated every parameter declaration in a third place, needed a client restart to pick up changes, and had nowhere to put the judgment that actually matters here. If you need to query this from a client with no shell, that is the one thing the MCP server did better.
|
|
162
|
+
|
|
163
|
+
# Current state
|
|
164
|
+
Version 0.3 is a rewrite of the ingest and query layers. The purpose has not changed: get the public licensing dataset, clean its geographic fields, index it, and make it useful to query.
|
|
165
|
+
|
|
166
|
+
What changed:
|
|
167
|
+
|
|
168
|
+
* **Source.** FCC License View is retired; ULS weekly complete files replace it. See [the 2026 publication research](docs/RESEARCH-2026.md).
|
|
169
|
+
* **Storage.** PostgreSQL and PostGIS are gone. SQLite with FTS5 and an RTree index does the same work with nothing to install. See [migration notes](docs/MIGRATION.md).
|
|
170
|
+
* **Fidelity.** Every field of all 89 documented ULS record layouts is preserved in `raw_*` tables; the common ones are also normalized into typed, indexed tables (`licenses`, `entities`, `locations`, `antennas`, `frequencies`, `emissions`, `amateur`). Raw records join on `unique_system_identifier`. Undocumented trailing fields are kept in `extra_fields_json` rather than dropped, so FCC publication drift is visible instead of silent.
|
|
171
|
+
* **Provenance.** Every import records where the file came from, when the FCC published it, when it was fetched, and its hash.
|
|
172
|
+
|
|
173
|
+
Known limits:
|
|
174
|
+
|
|
175
|
+
* Full-power AM/FM/TV station licensing moved to the FCC's separate Licensing and Management System (LMS) and is **not** in this database. ULS still carries broadcast auxiliary. LMS is deliberately not mixed in.
|
|
176
|
+
* Rebuilds use the weekly complete files. Daily transaction deltas are not applied yet.
|
|
177
|
+
* Coordinates come from the FCC as published; about a quarter of location records have no usable coordinate.
|
|
178
|
+
* `load.py` and `sample-fcc.csv` are the original Python 2 loader and a License View sample. They are kept for reference and do not run against current data.
|
|
179
|
+
|
|
180
|
+
## What this data contains
|
|
181
|
+
Everything here is FCC public record, published for anyone to download, and Spectrum Wrangler queries all of it. There is no column gate: the database is a local file you can open with `sqlite3`, so a gate would restrict nothing while breaking legitimate lookups.
|
|
182
|
+
|
|
183
|
+
Be aware of what that means in bulk. Entity records carry licensee contact details, and the amateur archive alone is roughly 1.7 million individual licensees with names and mailing addresses. Public record, but a bulk extract of private individuals is a different thing from a single lookup — worth a thought before you republish one.
|
|
184
|
+
|
|
185
|
+
Read-only *is* enforced, at the SQLite connection rather than by convention: a write is refused by the engine.
|
|
186
|
+
|
|
187
|
+
# Development
|
|
188
|
+
|
|
189
|
+
python3 -m unittest discover -s tests -v
|
|
190
|
+
python3 -m compileall -q spectrum_wrangler tests
|
|
191
|
+
|
|
192
|
+
The suite is hermetic — no network, no fixtures on disk, under a second — and CI runs it on Python 3.11 through 3.14 across Linux, macOS, and Windows.
|
|
193
|
+
|
|
194
|
+
The only build dependency is the pinned `setuptools==80.9.0`. The application itself has no third-party runtime dependencies, and [the dependency policy](docs/DEPENDENCIES.md) explains what it takes to add one.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://github.com/marcdacosta/spectrum-wrangler/actions/workflows/tests.yml)
|
|
4
|
+
[](https://pypi.org/project/spectrum-wrangler/)
|
|
5
|
+
|
|
6
|
+
# What is Spectrum Wrangler?
|
|
7
|
+
Spectrum Wrangler will download the most recent version of the FCC's public licensing data, parse it into a local database, clean up the geo columns so they can be used to query on, and produce a spatial index on them.
|
|
8
|
+
|
|
9
|
+
# About the FCC licensing data
|
|
10
|
+
In the United States the Federal Communication Commission is responsible for regulating the use of the radio waves. The FCC issues licenses for radio transmitters that exceed certain power levels or which operate on certain frequencies. Those licenses are public, and they say who is allowed to transmit, where, on what frequency, and with how much power. Taken together they are a map of who owns the air.
|
|
11
|
+
|
|
12
|
+
The original version of this project used the [License View](http://reboot.fcc.gov/license-view/) database, which harmonized the schema of the various parts of the [Universal Licensing System](http://wireless.fcc.gov/uls/index.htm?job=transaction&page=weekly) (ULS) into one flat CSV. The FCC no longer maintains License View. Spectrum Wrangler now reads ULS directly, which is the same data with more detail and no denormalization: a license header, its licensee, its locations, its antennas, its frequency assignments, and its emissions all arrive as separate related records.
|
|
13
|
+
|
|
14
|
+
The FCC snapshot published August 23, 2026 holds 5.2M licenses, 2.8M of them active, across 129 radio services — 6.6M licensee records, 3.3M transmitter locations (2.4M with usable coordinates), and 11.4M frequency assignments. Your own numbers will differ; `status` reports what you actually loaded.
|
|
15
|
+
|
|
16
|
+
# How to use
|
|
17
|
+
* Install it: `uv tool install spectrum-wrangler`, `pipx install spectrum-wrangler`, or `pip install spectrum-wrangler`. Python 3.11 or newer is the only requirement, and a clone of this repository works identically without installing (see below).
|
|
18
|
+
* Run `spectrum-wrangler refresh` to download and index every current weekly archive.
|
|
19
|
+
* Run `spectrum-wrangler refresh --archive paging` first if you want a small database to try.
|
|
20
|
+
* Query it with `spectrum-wrangler <command>`, or point an AI agent at it (see below).
|
|
21
|
+
* Read [the querying guide](docs/QUERYING.md) for how the records join and what to watch out for.
|
|
22
|
+
|
|
23
|
+
The complete download is about 1.2 GB compressed and produces a database of roughly 23 GB once every source field is retained and indexed. Downloads are cached and content-hashed, so re-running `refresh` re-imports only what actually changed. Use `--normalized-only` for a much smaller database when you do not need every raw FCC field.
|
|
24
|
+
|
|
25
|
+
The database, download cache, and provenance manifest live in a per-user data directory: `~/Library/Application Support/spectrum-wrangler` on macOS, `$XDG_DATA_HOME/spectrum-wrangler` (usually under `~/.local/share`) on Linux, `%LOCALAPPDATA%\spectrum-wrangler` on Windows. A database already built under `./data` in a repository checkout keeps being used, and `--database` or the `SPECTRUM_WRANGLER_DB` environment variable points anywhere else.
|
|
26
|
+
|
|
27
|
+
Generated archives and databases stay out of Git. [data/source-manifest.json](data/source-manifest.json) is committed instead, and records each archive's official URL, FCC publication time, retrieval time, ETag, SHA-256, byte size, record counts, and any observed schema drift.
|
|
28
|
+
|
|
29
|
+
# Do I need Docker?
|
|
30
|
+
No. A project to Dockerize `spectrum-wrangler` can be found [here](https://github.com/brannondorsey/spectrum-wrangler-docker), and it made sense when this tool required a PostgreSQL server with the PostGIS extension installed and configured. That was the hard part, and a container solved it.
|
|
31
|
+
|
|
32
|
+
Version 0.3 has no services and no dependencies, so a container would add a step rather than remove one — and the expensive artifact is the 23 GB database, which has to live on your disk either way. The Docker wrapper targets the retired PostgreSQL/License View workflow and is kept as a historical reference, not the current path.
|
|
33
|
+
|
|
34
|
+
# Requirements
|
|
35
|
+
* **Python 3.11 or newer, and nothing else.** SQLite, FTS5, and RTree ship with Python. PostgreSQL, PostGIS, and Docker are no longer needed.
|
|
36
|
+
* **Disk: about 25 GB** for a full load — a 23 GB database plus a 1.25 GB download cache. `--normalized-only` cuts the database to roughly a third of that.
|
|
37
|
+
* **Memory: under 600 MB.** The importer streams, so memory tracks the widest record rather than the size of the archive. Measured peaks: 77 MB for the smallest archive, 537 MB for the largest (35.4M records). An 8 GB laptop is comfortable.
|
|
38
|
+
* **Time: roughly 15 minutes** of import for the full set on a modern laptop once downloaded, plus the download itself. Importing runs at about 130,000 records per second.
|
|
39
|
+
|
|
40
|
+
Install it from PyPI:
|
|
41
|
+
|
|
42
|
+
uv tool install spectrum-wrangler # or: pipx install spectrum-wrangler
|
|
43
|
+
|
|
44
|
+
Or clone the repository and run it in place, no install step at all:
|
|
45
|
+
|
|
46
|
+
git clone https://github.com/marcdacosta/spectrum-wrangler.git
|
|
47
|
+
cd spectrum-wrangler
|
|
48
|
+
python3 -m spectrum_wrangler sources
|
|
49
|
+
|
|
50
|
+
The two are the same program: `python3 -m spectrum_wrangler` from a checkout accepts every command written as `spectrum-wrangler` in the examples here.
|
|
51
|
+
|
|
52
|
+
## Start small
|
|
53
|
+
One archive is enough to learn the shape of the data, and the smallest is a four-second import:
|
|
54
|
+
|
|
55
|
+
| Archive | Download | Records | Database |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `--archive paging` | 6.5 MB | 750K | 142 MB |
|
|
58
|
+
| `--archive amateur` | 198 MB | 10.5M | 2.3 GB (710 MB with `--normalized-only`) |
|
|
59
|
+
| everything | 1.25 GB | 105.9M | 23 GB |
|
|
60
|
+
|
|
61
|
+
Run `spectrum-wrangler sources` to see the full list of 14 archives.
|
|
62
|
+
|
|
63
|
+
# Examples
|
|
64
|
+
Example raw data extract from the original License View publication can be found in `sample-fcc.csv`. [The querying guide](docs/QUERYING.md) has worked investigations and the join semantics behind these.
|
|
65
|
+
|
|
66
|
+
## Query to check what is loaded and how current it is
|
|
67
|
+
|
|
68
|
+
spectrum-wrangler status
|
|
69
|
+
|
|
70
|
+
## Look up a call sign
|
|
71
|
+
|
|
72
|
+
spectrum-wrangler callsign W1AW
|
|
73
|
+
|
|
74
|
+
[
|
|
75
|
+
{
|
|
76
|
+
"callsign": "W1AW",
|
|
77
|
+
"display_name": "ARRL HQ OPERATORS CLUB",
|
|
78
|
+
"radio_service_code": "HA",
|
|
79
|
+
"state": "CT",
|
|
80
|
+
"license_status": "A",
|
|
81
|
+
"grant_date": "12/08/2020",
|
|
82
|
+
"expired_date": "02/26/2031",
|
|
83
|
+
"unique_system_id": 780866
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
## Ranged queries
|
|
88
|
+
Query to search within 1000m of [40.7253319,-74.0076834](https://www.google.com/maps/search/40.7253319,-74.0076834?sa=X&ved=0ahUKEwjj06n16p_WAhWR8oMKHUMfA9oQ8gEIJzAA) and export the results:
|
|
89
|
+
|
|
90
|
+
spectrum-wrangler --format csv \
|
|
91
|
+
nearby 40.7253319 -74.0076834 --radius-km 1 > /tmp/antennas.csv
|
|
92
|
+
|
|
93
|
+
Or in SQL, against the same database:
|
|
94
|
+
|
|
95
|
+
spectrum-wrangler sql \
|
|
96
|
+
"SELECT radio_service_code, count(*) AS licenses
|
|
97
|
+
FROM licenses GROUP BY 1 ORDER BY 2 DESC"
|
|
98
|
+
|
|
99
|
+
# How agents use it
|
|
100
|
+
Spectrum Wrangler is one CLI, used the same way by a person and by an agent. Every operation is declared once in the code, so the commands, the help text, and the machine-readable manifest cannot drift apart.
|
|
101
|
+
|
|
102
|
+
spectrum-wrangler capabilities
|
|
103
|
+
|
|
104
|
+
`capabilities` describes every command, argument, output format, and exit code as JSON, so an agent can discover the whole surface in one call. Output adapts to the consumer — a table on a terminal, JSON when piped — and `--format table|json|ndjson|csv` always wins.
|
|
105
|
+
|
|
106
|
+
# everything about one licence, in one call
|
|
107
|
+
spectrum-wrangler license --callsign W1AW
|
|
108
|
+
|
|
109
|
+
# who else transmits from this building
|
|
110
|
+
spectrum-wrangler nearby 40.748444 -73.985694 --radius-km 0.1
|
|
111
|
+
|
|
112
|
+
# who holds a band, and who holds a name
|
|
113
|
+
spectrum-wrangler band 462 468 --group-by licensee
|
|
114
|
+
spectrum-wrangler organization --name "NEW YORK CITY POLICE"
|
|
115
|
+
|
|
116
|
+
# what expires this quarter, as CSV
|
|
117
|
+
spectrum-wrangler --format csv expirations --start 2026-10-01 --end 2026-12-31 --state NY
|
|
118
|
+
|
|
119
|
+
Exit codes separate the cases a program must tell apart: `0` success, `1` understood but nothing matched, `2` bad request. Errors are JSON on stderr in machine formats. The `spectrum-wrangler-agent` entry point is the same CLI with output pinned to JSON whether or not it is attached to a terminal, and [AGENTS.md](AGENTS.md) is the in-repository briefing for agents working on or with the code.
|
|
120
|
+
|
|
121
|
+
The CLI is read-only and enforces it at the SQLite connection rather than by convention. Custom SQL accepts one `SELECT`, `WITH`, or `EXPLAIN`, runs with a timeout, and returns at most 1,000 rows.
|
|
122
|
+
|
|
123
|
+
## The skill
|
|
124
|
+
[`skills/spectrum-wrangler/SKILL.md`](skills/spectrum-wrangler/SKILL.md) is the companion to the CLI, and it carries the part a tool schema cannot: what will make you confidently wrong about this dataset. Joining on `unique_system_id` alone turns one call sign into 1.8 million rows. Dates are `MM/DD/YYYY` text, so sorting them sorts by month. Counting what one organization holds has no exact answer. Point any agent at it.
|
|
125
|
+
|
|
126
|
+
To install the skill for Claude Code, link it into your skills directory:
|
|
127
|
+
|
|
128
|
+
mkdir -p ~/.claude/skills
|
|
129
|
+
ln -s "$PWD/skills/spectrum-wrangler" ~/.claude/skills/spectrum-wrangler
|
|
130
|
+
|
|
131
|
+
Earlier versions shipped a Model Context Protocol server. It was removed in favour of the CLI plus the skill: it duplicated every parameter declaration in a third place, needed a client restart to pick up changes, and had nowhere to put the judgment that actually matters here. If you need to query this from a client with no shell, that is the one thing the MCP server did better.
|
|
132
|
+
|
|
133
|
+
# Current state
|
|
134
|
+
Version 0.3 is a rewrite of the ingest and query layers. The purpose has not changed: get the public licensing dataset, clean its geographic fields, index it, and make it useful to query.
|
|
135
|
+
|
|
136
|
+
What changed:
|
|
137
|
+
|
|
138
|
+
* **Source.** FCC License View is retired; ULS weekly complete files replace it. See [the 2026 publication research](docs/RESEARCH-2026.md).
|
|
139
|
+
* **Storage.** PostgreSQL and PostGIS are gone. SQLite with FTS5 and an RTree index does the same work with nothing to install. See [migration notes](docs/MIGRATION.md).
|
|
140
|
+
* **Fidelity.** Every field of all 89 documented ULS record layouts is preserved in `raw_*` tables; the common ones are also normalized into typed, indexed tables (`licenses`, `entities`, `locations`, `antennas`, `frequencies`, `emissions`, `amateur`). Raw records join on `unique_system_identifier`. Undocumented trailing fields are kept in `extra_fields_json` rather than dropped, so FCC publication drift is visible instead of silent.
|
|
141
|
+
* **Provenance.** Every import records where the file came from, when the FCC published it, when it was fetched, and its hash.
|
|
142
|
+
|
|
143
|
+
Known limits:
|
|
144
|
+
|
|
145
|
+
* Full-power AM/FM/TV station licensing moved to the FCC's separate Licensing and Management System (LMS) and is **not** in this database. ULS still carries broadcast auxiliary. LMS is deliberately not mixed in.
|
|
146
|
+
* Rebuilds use the weekly complete files. Daily transaction deltas are not applied yet.
|
|
147
|
+
* Coordinates come from the FCC as published; about a quarter of location records have no usable coordinate.
|
|
148
|
+
* `load.py` and `sample-fcc.csv` are the original Python 2 loader and a License View sample. They are kept for reference and do not run against current data.
|
|
149
|
+
|
|
150
|
+
## What this data contains
|
|
151
|
+
Everything here is FCC public record, published for anyone to download, and Spectrum Wrangler queries all of it. There is no column gate: the database is a local file you can open with `sqlite3`, so a gate would restrict nothing while breaking legitimate lookups.
|
|
152
|
+
|
|
153
|
+
Be aware of what that means in bulk. Entity records carry licensee contact details, and the amateur archive alone is roughly 1.7 million individual licensees with names and mailing addresses. Public record, but a bulk extract of private individuals is a different thing from a single lookup — worth a thought before you republish one.
|
|
154
|
+
|
|
155
|
+
Read-only *is* enforced, at the SQLite connection rather than by convention: a write is refused by the engine.
|
|
156
|
+
|
|
157
|
+
# Development
|
|
158
|
+
|
|
159
|
+
python3 -m unittest discover -s tests -v
|
|
160
|
+
python3 -m compileall -q spectrum_wrangler tests
|
|
161
|
+
|
|
162
|
+
The suite is hermetic — no network, no fixtures on disk, under a second — and CI runs it on Python 3.11 through 3.14 across Linux, macOS, and Windows.
|
|
163
|
+
|
|
164
|
+
The only build dependency is the pinned `setuptools==80.9.0`. The application itself has no third-party runtime dependencies, and [the dependency policy](docs/DEPENDENCIES.md) explains what it takes to add one.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Dependency policy
|
|
2
|
+
|
|
3
|
+
Spectrum Wrangler 0.3 has no third-party runtime dependencies. Its build backend
|
|
4
|
+
is pinned to `setuptools==80.9.0`; PyPI registry metadata reports its release
|
|
5
|
+
files were uploaded on 2025-05-27, well outside the repository's 14-day minimum
|
|
6
|
+
age policy as checked on 2026-08-28.
|
|
7
|
+
|
|
8
|
+
Adding, updating, installing, or temporarily executing a package requires first
|
|
9
|
+
recording the exact version and official registry release timestamp. Existing
|
|
10
|
+
lockfile installs are acceptable only when they do not change resolved versions.
|
|
11
|
+
|
|
12
|
+
## Recorded release tooling
|
|
13
|
+
|
|
14
|
+
The release workflow builds distributions with `build==1.5.0`; PyPI registry
|
|
15
|
+
metadata reports its release files were uploaded on 2026-04-30, outside the
|
|
16
|
+
14-day minimum age as checked on 2026-08-30. At that check `build==1.5.1` was
|
|
17
|
+
yanked upstream and `build==1.6.0` was three days old, inside the window, so
|
|
18
|
+
neither was adopted.
|
|
19
|
+
|
|
20
|
+
GitHub Actions in CI are pinned by major version tag (`actions/checkout@v5`,
|
|
21
|
+
`actions/setup-python@v5`, `actions/upload-artifact@v4`,
|
|
22
|
+
`actions/download-artifact@v4`, `pypa/gh-action-pypi-publish@release/v1`).
|
|
23
|
+
They run only in CI and never ship in a release artifact.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Migration from FCC License View
|
|
2
|
+
|
|
3
|
+
This is for people who ran the 2017 loader. It covers what changed and what to
|
|
4
|
+
expect; the evidence behind the decision — the FCC's response, the ULS
|
|
5
|
+
publication format, the LMS boundary — is recorded once in
|
|
6
|
+
[the 2026 research notes](RESEARCH-2026.md) rather than repeated here.
|
|
7
|
+
|
|
8
|
+
The 2017 loader downloaded one `fcc_lic_vw.csv`, created a hand-written Postgres table, converted degree/minute/second coordinates in PL/pgSQL, and required PostGIS. That path cannot be repaired by changing its URL: the FCC has confirmed License View is no longer maintained.
|
|
9
|
+
|
|
10
|
+
Version 0.3 therefore changes both the publication and storage model.
|
|
11
|
+
|
|
12
|
+
| Legacy behavior | 2026 behavior |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Retired License View CSV | Live FCC ULS weekly license archives |
|
|
15
|
+
| One denormalized table | 89 lossless raw record tables plus normalized query tables |
|
|
16
|
+
| Python 2, `requests`, `psycopg2` | Python 3.11+ standard library only |
|
|
17
|
+
| Config embedded in `load.py` | CLI flags and stable defaults |
|
|
18
|
+
| Required Postgres/PostGIS | Portable SQLite with FTS5 and RTree |
|
|
19
|
+
| Destructive table replacement | Per-archive replacement with content-hash reuse |
|
|
20
|
+
| No source provenance | URL, timestamps, ETag, SHA-256, bytes, and counts |
|
|
21
|
+
| Human SQL only | Structured commands, bounded SQL, and machine-readable output |
|
|
22
|
+
|
|
23
|
+
## Compatibility boundaries
|
|
24
|
+
|
|
25
|
+
The normalized `licenses`, `entities`, `locations`, `antennas`, `frequencies`, and `emissions` tables cover the same practical query families as the old `fcclicenses` table. Their identifiers are intentionally closer to the official ULS names.
|
|
26
|
+
|
|
27
|
+
The old flat file could multiply one license across locations, antennas, frequencies, and emissions. The new model keeps those relations separate, so queries express the intended cardinality rather than inheriting accidental row multiplication.
|
|
28
|
+
|
|
29
|
+
Coordinates are converted to decimal WGS84 values during normalization and the original DMS components remain in `raw_lo`. RTree supplies the bounding-box phase of proximity queries and a Haversine calculation supplies exact great-circle distance; no spatial extension is required.
|
|
30
|
+
|
|
31
|
+
## Raw fidelity and schema drift
|
|
32
|
+
|
|
33
|
+
The importer keeps every field described by the FCC's published record definitions; the checked-in `uls_schema.json` defines all 89 layouts. Raw values remain text so identifiers, leading zeroes, source date formats, and precision are not altered.
|
|
34
|
+
|
|
35
|
+
An FCC record with fewer fields is padded with nulls. Newly added trailing fields are serialized in `extra_fields_json`, making publication drift visible and recoverable instead of shifting columns or dropping data. Completely new record types go to `raw_unknown` until their official definitions are reviewed. [The research notes](RESEARCH-2026.md) give a worked example of drift already observed in the published archives.
|
|
36
|
+
|
|
37
|
+
Some FCC free-text fields span unescaped physical lines. The logical-record parser recognizes the member's two-character record prefix, joins continuation text back into the preceding field with newlines, and skips physical blank separators. `raw_parser_version` in source provenance prevents an older line-oriented import from being mistaken for a current verified snapshot.
|
|
38
|
+
|
|
39
|
+
## Broadcast limitation
|
|
40
|
+
|
|
41
|
+
License View combined more than ULS. Its sample in this repository includes CDBS broadcast stations; the modern replacement for those station records is LMS, not ULS. ULS still contains broadcast auxiliary services (`l_LMbcast.zip`).
|
|
42
|
+
|
|
43
|
+
If you previously queried this database for full-power AM/FM/TV stations, that is the one query family version 0.3 cannot answer. It is labelled rather than silently approximated, and LMS is not mixed in.
|
|
44
|
+
|
|
45
|
+
## Legacy files
|
|
46
|
+
|
|
47
|
+
`load.py` remains available for historical comparison. It is Python 2 code, points at the retired publication, and must not be used for a current refresh. `sample-fcc.csv` is likewise a legacy License View sample rather than a current test fixture.
|