Skip to content

Refactor/structure tests and docs - #17

Merged
danielvogler merged 3 commits into
mainfrom
refactor/structure-tests-and-docs
Sep 9, 2026
Merged

danielvogler merged 3 commits into
mainfrom
refactor/structure-tests-and-docs

Conversation

@danielvogler

Copy link
Copy Markdown
Owner

No description provided.

No matching behaviour changes. Every elapsed time, match flag and matched
trackpoint the library produced before, it produces after -- verified by
differential testing against the previous implementation: 4061 checks over a
parameter sweep, run on both the old dependency stack and the new one, with
zero mismatches. With the RNG seeded identically the warping distances come out
bit-identical, not merely close.

BREAKING CHANGE: requires Python 3.11 and Shapely 2.

Tests first
-----------
The suite was five tests over two matches, which is enough to notice a crash
and not enough to notice a numerical drift. It is now 134 tests at 98%
coverage, written and made green against the *previous* implementation before
any code moved, so the restructuring had something to be measured against.

Times, flags and trackpoints are asserted exactly. Only the warping distance
carries a tolerance: interpolation jitters coordinates from the unseeded global
RNG, six consecutive runs spread over 0.1% of the mean, so DTW_RTOL is 1%.

Structure
---------
One 747-line geopard.py becomes ten modules under src/geopard/, the largest 329
lines. Geopard keeps every method it had and delegates to them, so the class
stays the documented interface while each stage becomes importable and testable
on its own. No public method was added or removed; nine of twelve signatures are
unchanged and three gained optional arguments.

`from geopard.geopard import Geopard` -- the only import path that worked before
-- still resolves, and a test guards it.

Adds geopard/__init__.py, so the `import geopard; geopard.Geopard()` the README
has always documented actually works. It never did.

Tooling
-------
uv and hatchling replace Poetry, setup.py, setup.cfg and requirements.txt. ruff
replaces black, isort, flake8, pylint, pydocstyle and autoflake. mypy is added
and passes. The version now has one home, src/geopard/version.py, read
dynamically by pyproject.toml, with a test that fails if that wiring breaks.

Shapely was pinned <2, which has no wheels for any supported Python -- a fresh
install fell back to compiling from source and usually failed. Unpinned to 2.x
and verified numerically identical across the upgrade.

Fixes
-----
- nearest_neighbours logged a list with %f, failing on every call. Invisible,
  because the logging module swallows formatting errors.
- parse_response raised a formatting error on an unmatched response, where dtw
  is None, and logged the bound method rather than calling is_success().
- The PyPI workflow ran on every push and every pull request to main rather
  than on a release.
- GeopardResponse declared six methods permanently shadowed by instance
  attributes of the same name, so none could ever be called. It is now a frozen
  dataclass; is_success() stays a method, because the examples call it.
- nearest_neighbours had `centroid=list` as a default -- the type object.
- requirements.txt listed haversine and similaritymeasures, neither imported.

Plots
-----
A single palette, and aspect-ratio correction for latitude: at 47 degrees north
a degree of longitude is two-thirds of a degree of latitude, so the previous
raw scatter squashed every route by a third and made a good match look bad.
Legends moved outside the data, marker sizes are readable rather than 500
points, and style() returns rcParams instead of applying them globally, so
importing geopard no longer changes anyone else's plots. plot_track_comparison
gained show= and save_to=, so figures can be produced in a script or in CI.

Example scripts take real --flags with defaults and exit with a status code.
The README explained how to install Poetry before it explained what the library
does, and the usage it documented -- `import geopard; geopard.Geopard()` -- did
not work. Rewritten around the problem the library actually solves: an activity
is longer than the segment inside it, a start line is not a point, and an
activity may cover a segment several times, so the answer has to be the fastest
of many candidate attempts rather than the first one found.

Adds a light/dark banner drawing the one idea in the library -- two tracks tied
point to point, the ties deliberately not one-to-one, because that stretching
is the mechanism -- and a mermaid flowchart of the match loop. The example
figures are regenerated with the new plotting style.

AGENTS.md is the source of truth, split into using the library and changing it.
The half worth having is A5, what to do when a match is not what you expected:
work down the list rather than reaching for dtw_threshold, because loosening
the threshold is the one change that makes every result less trustworthy.
CLAUDE.md and GEMINI.md point at it and hold no content of their own.

docs/architecture.md records the decisions that look wrong until you know why:
why candidates are warped shortest-first and the loop stops at the first match,
why the cost matrix stays a Python loop, why the interpolation count is a
constant rather than an argument, why failures are returned instead of raised,
and why elevation is loaded and never used.

Also adds CHANGELOG.md and SECURITY.md, and drops README.rst, which duplicated
the README and documented an API that had since moved.
The 3.12 and 3.13 jobs were failing, and not on the tests -- pytest passed on
all three. `[tool.mypy] python_version = "3.11"` made mypy reject its own
dependencies' stubs when run on a newer interpreter:

    numpy/__init__.pyi:737: error: Type statement is only supported in
    Python 3.12 and greater  [syntax]

numpy's stubs use PEP 695 `type` statements, which are legal on the 3.12 and
3.13 runners but refused by a 3.11 target. Unset, mypy targets whichever
interpreter it runs under. That also unbreaks `make check` for anyone working
on 3.12 or 3.13, which the pin broke locally in exactly the same way.

The matrix drops to 3.11 alone. It would pass on all three now, so restoring
the other two is a one-line change if the wider coverage is ever wanted.
@danielvogler
danielvogler merged commit ac8a631 into main Sep 9, 2026
2 checks passed
@danielvogler
danielvogler deleted the refactor/structure-tests-and-docs branch September 9, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant