Skip to content

Commit 45a60d0

Browse files
thodson-usgsclaude
andauthored
docs: copy edits across the ADRs, guides, and glossary (#417)
Copy edits only: say each thing in plain terms throughout the architecture records, user guides, CONTEXT.md, AGENTS.md, CONTRIBUTING.md, README, and NEWS. No decision, glossary term, or code changes. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent bb17656 commit 45a60d0

24 files changed

Lines changed: 548 additions & 534 deletions

‎AGENTS.md‎

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,44 @@
44
- **`CONTEXT.md` is the shared vocabulary** — getter, query, chunk, plan, fan-out,
55
page, adapter, facade, leaf, transport, collection, profile, effective
66
configuration, and the legacy names that are deliberately not renamed. Read it
7-
before writing code, docstrings, or commit messages. Two kinds of term live
8-
there and they bind differently (ADR 0013). A **core** term is ours and takes
9-
one spelling everywhere *including identifiers*, so a name in the code that
10-
conflicts with it is a defect. A **domain** term -- *monitoring location*,
11-
*collection* -- is fixed for prose only: each adapter keeps its own service's
12-
spelling in its parameters, so `nwis.get_record(service="dv")` and WQP's
13-
`Station` are that service's language rather than drift. Each entry states
14-
which kind it is.
7+
before writing code, docstrings, or commit messages. Two kinds of term are
8+
defined there and they bind differently (ADR 0013). A **core** term is ours
9+
and takes one spelling everywhere *including identifiers*, so a name in the
10+
code that conflicts with it is a defect. A **domain** term -- *monitoring
11+
location*, *collection* -- is fixed for prose only: each adapter keeps its
12+
own service's spelling in its parameters, so `nwis.get_record(service="dv")`
13+
and WQP's `Station` are that service's vocabulary rather than drift. Each
14+
entry states which kind it is.
1515
- Architectural decisions and their rationale: `docs/source/architecture/decisions/`
1616
(ADRs, referenced by number throughout the code and by `.importlinter`).
1717
- Contributor workflow, style, and the quality gates in detail: `CONTRIBUTING.md`.
1818

1919
## How the tree is organized
2020
Use `ls`/`grep` for the file list; what follows is the placement logic, so you
21-
can predict where a thing lives.
21+
can predict where a thing is defined.
2222

2323
- `dataretrieval/` — the public surface is one *adapter* module per service,
2424
named for the service (`nldi`, `nwdc`, `ngwmn`, `streamstats`, `wqp`, and
2525
legacy `nwis`); each owns that service's URLs, parameters, and response
2626
quirks. `waterdata/` is the one adapter large enough to be a package, split by
2727
collection family; its `api.py` is a compatibility facade holding no logic.
28-
Everything else in the package is shared machinery the adapters sit on top of
28+
Everything else in the package is shared code the adapters depend on
2929
— configuration, credentials, progress, exceptions, code tables, response
30-
formats. Shared machinery below the adapter layer must not know about any
30+
formats. Shared code below the adapter layer must not reference any
3131
particular service.
32-
- `dataretrieval/ogc/` — the OGC API protocol machinery (chunk planning,
32+
- `dataretrieval/ogc/` — the OGC API protocol code (chunk planning,
3333
filters, request building, response shaping). Shared by the two OGC services
3434
only; `.importlinter` refuses any other importer.
35-
- `dataretrieval/transport/` — service-neutral request machinery (HTTP, retry,
35+
- `dataretrieval/transport/` — service-neutral request code (HTTP, retry,
3636
pagination, fan-out). It names no service and no protocol, and is not public API.
3737
- Leading-underscore top-level modules are private; the dependency-free *leaves*
38-
sit at the floor of the stack so anything may use them without pulling in the
38+
are at the bottom of the stack so anything may use them without pulling in the
3939
rest of the package. Check for an existing leaf before writing a small helper.
40-
- **`.importlinter` is the map.** Its `layers` contract lists every top-level
41-
module in dependency order and is `exhaustive = True`, so it is both the
42-
authoritative statement of where a module sits and the thing that fails when a
43-
new module has no home. Read it before adding a module or an import.
40+
- **`.importlinter` records where every module belongs.** Its `layers` contract
41+
lists every top-level module in dependency order and is `exhaustive = True`,
42+
so it is both the authoritative statement of where a module belongs and the
43+
thing that fails when a new module has not been placed. Read it before adding
44+
a module or an import.
4445
- `tests/` — flat, one `*_test.py` per module or concern, organized into four
4546
dependency-oriented layers (public contract, adapter contract, component,
4647
cross-component) that `tests/contracts/README.md` defines and assigns files to.
@@ -74,10 +75,10 @@ can predict where a thing lives.
7475
- Tests: `coverage run -m pytest tests/ && coverage report`, or focused like
7576
`pytest tests/waterdata_test.py::test_mock_get_samples`. `coverage report` is
7677
a merge gate: branch coverage with a `fail_under` ratchet in
77-
`[tool.coverage.report]`. Chase the uncovered *branch*, not the number -- a
78-
test written to colour a line green catches nothing and costs a maintenance
79-
slot. If a path is genuinely unreachable, add it to `exclude_also` with a
80-
reason, or leave the ratchet alone.
78+
`[tool.coverage.report]`. Cover the uncovered *branch*, not the number -- a
79+
test written only to mark a line as covered catches nothing and adds
80+
maintenance. If a path is genuinely unreachable, add it to `exclude_also`
81+
with a reason, or leave the ratchet alone.
8182
- Types: `mypy` (`strict = true` in `pyproject.toml`; CI runs it over the
8283
PR-merged-into-main, so bare `dict`/`list` annotations fail there even if they
8384
pass on your branch).
@@ -88,7 +89,7 @@ can predict where a thing lives.
8889

8990
## Testing gotchas
9091
- The suite is offline by default: `addopts = "-m 'not live'"`. Tests marked
91-
`@pytest.mark.live` hit real USGS services and run on a schedule
92+
`@pytest.mark.live` call real USGS services and run on a schedule
9293
(`.github/workflows/live-api.yml`); run them locally with `pytest tests/ -m live`.
9394
- HTTP is mocked with `pytest-httpx`'s `httpx_mock` fixture plus fixtures under
9495
`tests/data/`; keep new API tests offline.
@@ -101,23 +102,23 @@ can predict where a thing lives.
101102
## Error messages
102103
Most callers here are programs — a script, a pipeline stage, an agent — so a
103104
message is the only channel through which a caller can correct itself. Every
104-
raise states the problem and then the move that fixes it, in that order.
105+
raise states the problem and then the action that fixes it, in that order.
105106

106107
- Name the remedy, not just the fault. `"Service not recognized"` gives a caller
107108
nothing to try next; listing the services it does accept does. For a transport
108109
failure the remedy is whether to retry, and `transport.pagination.
109110
paginated_failure_message()` is the model: cause, then `To recover: …`.
110111
- Don't invent a phrasing for a check that recurs. `dataretrieval/_validation.py`
111-
owns the wording for the shared shapes — bad value in a closed vocabulary
112+
owns the wording for the shared cases — bad value in a closed vocabulary
112113
(`require_one_of`), missing argument (`require_argument`), incomplete group
113114
(`require_together`), no filter at all (`require_any_of`), and conflicting
114-
arguments (`require_exactly_one`, `reject_together`). Reach for one before
115+
arguments (`require_exactly_one`, `reject_together`). Use one before
115116
hand-writing a message. A service-specific pointer is not a reason to
116-
hand-write: every check takes a `remedy=` for the move it cannot derive. Every
117-
check raises `ValueError` -- one class for a bad argument value, so a caller
118-
catches by shape rather than by which module rejected it.
117+
hand-write: every check takes a `remedy=` for the action it cannot derive.
118+
Every check raises `ValueError` -- one class for a bad argument value, so a
119+
caller catches by error class rather than by which module rejected it.
119120
- `require_argument` returns the narrowed value and `require_exactly_one` the
120-
winning `(name, value)` pair, so use their results rather than re-testing for
121+
selected `(name, value)` pair, so use their results rather than re-testing for
121122
`None` to satisfy mypy — a second, unreachable message beside the first is
122123
how the two drift apart.
123124
- **Paste the remedy back before trusting it.** Whatever a message names must be
@@ -127,9 +128,9 @@ raise states the problem and then the move that fixes it, in that order.
127128
private local no getter accepts, `configure(Configuration(...))` was a silent
128129
no-op because `configure` is a context manager, `pip install
129130
dataretrieval[nldi]` globs in zsh, and a navigation missing its `data_source`
130-
spelled `None` into the URL and returned an empty frame. Run the corrected
131+
wrote `None` into the URL and returned an empty frame. Run the corrected
131132
call against the real service; wording review does not catch these.
132-
- Shared checks take the caller's spelling. `_validate_data_source`,
133+
- Shared checks take the caller's argument name. `_validate_data_source`,
133134
`_format_api_dates`, and `require_one_of` all accept a `name=` so the subject
134135
of the message is the argument that was actually passed. A helper that hard-codes
135136
one noun reports the wrong parameter the moment a second call site reuses it.
@@ -151,7 +152,7 @@ raise states the problem and then the move that fixes it, in that order.
151152
- The `API_USGS_PAT` credential is owned by the `credentials` leaf and applied as
152153
the `X-Api-Key` header by `transport.http.default_headers()`, which sends it
153154
only to the host it belongs to. Never hard-code tokens in examples or tests.
154-
- Water Data request builders translate Python kwargs to API spellings
155+
- Water Data request builders translate Python kwargs to API names
155156
(`skip_geometry` -> `skipGeometry`, `filter_lang` -> `filter-lang`); tests
156157
assert exact URLs and query params.
157158
- Multi-value OGC params are comma-joined GETs, except `monitoring-locations`

0 commit comments

Comments
 (0)