argleton 0.2.0__tar.gz → 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.
- {argleton-0.2.0 → argleton-0.3.0}/CITATION.cff +1 -1
- {argleton-0.2.0 → argleton-0.3.0}/PKG-INFO +13 -12
- {argleton-0.2.0 → argleton-0.3.0}/README.md +12 -11
- {argleton-0.2.0 → argleton-0.3.0}/adapters/engine_geopandas.py +28 -0
- {argleton-0.2.0 → argleton-0.3.0}/adapters/engine_naive.py +18 -0
- {argleton-0.2.0 → argleton-0.3.0}/adapters/mapsmith.py +44 -0
- argleton-0.3.0/clean/c023-longitude-first/build.py +44 -0
- argleton-0.3.0/clean/c023-longitude-first/probe.toml +43 -0
- {argleton-0.2.0 → argleton-0.3.0}/docs/FAMILIES.md +5 -4
- {argleton-0.2.0 → argleton-0.3.0}/pyproject.toml +1 -1
- argleton-0.3.0/results/2026-08-29-axis-order/adapters-geopandas.json +575 -0
- argleton-0.3.0/results/2026-08-29-axis-order/adapters-mapsmith.json +574 -0
- argleton-0.3.0/results/2026-08-29-axis-order/adapters-naive.json +574 -0
- argleton-0.3.0/results/2026-08-29-axis-order/adapters-rasterio.json +574 -0
- argleton-0.3.0/results/2026-08-29-axis-order/adapters-whitebox.json +575 -0
- argleton-0.3.0/results/LATEST +1 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/README.md +44 -1
- {argleton-0.2.0 → argleton-0.3.0}/tests/test_showcase.py +7 -2
- argleton-0.3.0/traps/023-axis-order/README.md +130 -0
- argleton-0.3.0/traps/023-axis-order/build.py +57 -0
- argleton-0.3.0/traps/023-axis-order/probe.toml +110 -0
- argleton-0.2.0/results/LATEST +0 -1
- {argleton-0.2.0 → argleton-0.3.0}/.gitattributes +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/.github/workflows/ci.yml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/.github/workflows/pages.yml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/.gitignore +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/LICENSE +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/adapters/__init__.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/adapters/engine_rasterio.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/adapters/engine_whitebox.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/adapters/gis_mcp.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/argleton/__init__.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/argleton/model.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/argleton/published.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/argleton/run.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/argleton/score.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c001-raster-mean/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c001-raster-mean/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c002-projected-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c002-projected-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c003-raster-mean-nodata/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c003-raster-mean-nodata/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c004-points-in-polygon/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c004-points-in-polygon/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c005-polygon-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c005-polygon-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c006-named-layer/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c006-named-layer/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c007-distance-in-metres/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c007-distance-in-metres/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c008-equal-area-crs/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c008-equal-area-crs/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c009-native-resolution-classes/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c009-native-resolution-classes/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c010-physical-values/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c010-physical-values/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c011-solid-parcel/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c011-solid-parcel/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c012-disjoint-concessions/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c012-disjoint-concessions/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c013-flat-pipeline/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c013-flat-pipeline/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c014-convex-parcel/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c014-convex-parcel/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c015-wells-off-the-seam/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c015-wells-off-the-seam/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c016-decimal-degrees/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c016-decimal-degrees/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c017-equal-populations/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c017-equal-populations/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c018-plain-keys/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c018-plain-keys/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c019-fully-contained/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c019-fully-contained/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c020-one-owner-each/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c020-one-owner-each/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c021-greenwich-variant/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c021-greenwich-variant/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c022-two-gauges/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/clean/c022-two-gauges/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/docs/ADDING-A-TRAP.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/docs/METHOD.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-23-first-run/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-23-first-run/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-23-first-run/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-23-first-run/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-23-first-run/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-24-five-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-24-five-families/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-24-five-families/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-24-five-families/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-24-five-families/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-eight-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-eight-families/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-eight-families/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-eight-families/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-eight-families/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-six-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-six-families/engine-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-six-families/engine-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-six-families/engine-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-25-six-families/engine-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-eighteen-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-eighteen-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-eighteen-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-eighteen-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-eighteen-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nine-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nine-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nine-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nine-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nine-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nineteen-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nineteen-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nineteen-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nineteen-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-nineteen-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-ten-families/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-ten-families/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-ten-families/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-ten-families/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-26-ten-families/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-27-datum-shift-fixed/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-27-datum-shift-fixed/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-27-datum-shift-fixed/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-27-datum-shift-fixed/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-27-datum-shift-fixed/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-thiessen-pairing/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-thiessen-pairing/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-thiessen-pairing/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-thiessen-pairing/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-thiessen-pairing/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-timings/adapters-geopandas.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-timings/adapters-mapsmith.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-timings/adapters-naive.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-timings/adapters-rasterio.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/results/2026-08-28-timings/adapters-whitebox.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/schema/probe.schema.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/schema/result.schema.json +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/site/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/site/index.template.html +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/tests/test_contracts.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/001-tiff-predictor/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/001-tiff-predictor/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/001-tiff-predictor/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/002-feet-as-metres/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/002-feet-as-metres/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/002-feet-as-metres/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/003-nodata-in-statistics/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/003-nodata-in-statistics/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/003-nodata-in-statistics/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/004-mismatched-crs-join/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/004-mismatched-crs-join/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/004-mismatched-crs-join/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/005-bowtie-area/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/005-bowtie-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/005-bowtie-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/006-default-layer/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/006-default-layer/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/006-default-layer/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/007-buffer-in-degrees/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/007-buffer-in-degrees/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/007-buffer-in-degrees/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/008-web-mercator-area/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/008-web-mercator-area/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/008-web-mercator-area/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/009-resampled-classes/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/009-resampled-classes/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/009-resampled-classes/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/010-scale-offset/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/010-scale-offset/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/010-scale-offset/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/011-polygon-holes/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/011-polygon-holes/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/011-polygon-holes/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/012-double-counting/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/012-double-counting/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/012-double-counting/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/013-z-dimension/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/013-z-dimension/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/013-z-dimension/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/014-centroid-outside/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/014-centroid-outside/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/014-centroid-outside/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/015-boundary-semantics/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/015-boundary-semantics/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/015-boundary-semantics/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/016-coordinate-parsing/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/016-coordinate-parsing/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/016-coordinate-parsing/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/017-aggregation-weighting/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/017-aggregation-weighting/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/017-aggregation-weighting/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/018-join-key-typing/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/018-join-key-typing/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/018-join-key-typing/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/019-partial-overlap/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/019-partial-overlap/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/019-partial-overlap/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/020-join-cardinality/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/020-join-cardinality/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/020-join-cardinality/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/021-ballpark-datum/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/021-ballpark-datum/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/021-ballpark-datum/probe.toml +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/022-thiessen-pairing/README.md +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/022-thiessen-pairing/build.py +0 -0
- {argleton-0.2.0 → argleton-0.3.0}/traps/022-thiessen-pairing/probe.toml +0 -0
|
@@ -7,7 +7,7 @@ authors:
|
|
|
7
7
|
repository-code: "https://github.com/argleton/argleton"
|
|
8
8
|
url: "https://argleton.org"
|
|
9
9
|
license: Apache-2.0
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.3.0"
|
|
11
11
|
date-released: "2026-08-29"
|
|
12
12
|
abstract: >-
|
|
13
13
|
Probes whose right answer is known by construction — derived on paper, never
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: argleton
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Probes whose right answer is known by construction, and whose wrong answer looks fine
|
|
5
5
|
Project-URL: Source, https://github.com/argleton/argleton
|
|
6
6
|
Author: Argleton contributors
|
|
@@ -67,8 +67,8 @@ machine, so there is nothing to download and nothing to take on trust:
|
|
|
67
67
|
pip install "argleton[fixtures]"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
The probes ship with the runner, so that is the whole setup — release 0.
|
|
71
|
-
|
|
70
|
+
The probes ship with the runner, so that is the whole setup — release 0.3.0 carries the
|
|
71
|
+
23 traps and 26 families the results below were produced from. When the checkout runs ahead
|
|
72
72
|
of the release this paragraph says so, because a reader who cannot reproduce the table on
|
|
73
73
|
this page has been told something untrue.
|
|
74
74
|
|
|
@@ -109,8 +109,8 @@ every task it was given (completion 1.0) *and* gets this file silently wrong
|
|
|
109
109
|
(the 0.5).
|
|
110
110
|
|
|
111
111
|
There is a third adapter, `engine:naive` — read the file, take the statistic,
|
|
112
|
-
report it — and it is the most useful one here. It scores **0.
|
|
113
|
-
answers every clean probe correctly, falls into twenty-
|
|
112
|
+
report it — and it is the most useful one here. It scores **0.9565 / 1.0**: it
|
|
113
|
+
answers every clean probe correctly, falls into twenty-two of the twenty-three traps, and
|
|
114
114
|
**passes the remaining one**, because rasterio undoes the predictor on its
|
|
115
115
|
behalf. Careless code is not uniformly wrong. It is correct until the data stops
|
|
116
116
|
having the shape it usually has, which is what makes the exceptions so hard to
|
|
@@ -134,7 +134,7 @@ Side by side, one glance tells you which you are looking at.
|
|
|
134
134
|
|
|
135
135
|
## What is covered
|
|
136
136
|
|
|
137
|
-
Twenty families of twenty-
|
|
137
|
+
Twenty-one families of twenty-six, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
|
|
138
138
|
number from here can never be read as broader than it is. A low silent-error
|
|
139
139
|
rate means a system did not fail silently *on these probes*.
|
|
140
140
|
|
|
@@ -160,19 +160,20 @@ rate means a system did not fail silently *on these probes*.
|
|
|
160
160
|
| `aggregation-weighting` | 13.67% instead of 1.38% | averaging rates treats a village as equal to a city |
|
|
161
161
|
| `tabular-join` | 62000 people instead of 100000 | a CSV reader turns "001" into 1 and four municipalities leave the join |
|
|
162
162
|
| `positional-pairing` | 554 mm of rainfall instead of 268 | the Thiessen cells are all valid and tile the extent; only the row each one carries is wrong |
|
|
163
|
+
| `axis-order` | 16261 m² instead of 14042 | EPSG:4326 declares latitude first and every geometry library expects longitude first; both readings of a corner schedule stay in range |
|
|
163
164
|
|
|
164
165
|
## Results
|
|
165
166
|
|
|
166
|
-
Engine tier, twenty families, `spec_commit` pinned — [every run, and what the
|
|
167
|
+
Engine tier, twenty-one families, `spec_commit` pinned — [every run, and what the
|
|
167
168
|
numbers do not say](results/).
|
|
168
169
|
|
|
169
170
|
| system | silent error rate | completion rate | traps run | not applicable |
|
|
170
171
|
|---|---|---|---|---|
|
|
171
|
-
| MapSmith (main) | 0.00 | 1.00 |
|
|
172
|
-
| rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 4 |
|
|
173
|
-
| GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 |
|
|
174
|
-
| whitebox-workflows 2.0.6 | 0.50 | 1.00 | 2 |
|
|
175
|
-
| naive composition | 0.
|
|
172
|
+
| MapSmith (main) | 0.00 | 1.00 | 23 | 0 |
|
|
173
|
+
| rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 4 | 38 |
|
|
174
|
+
| GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 9 | 28 |
|
|
175
|
+
| whitebox-workflows 2.0.6 | 0.50 | 1.00 | 2 | 42 |
|
|
176
|
+
| naive composition | 0.9565 | 1.00 | 23 | 0 |
|
|
176
177
|
|
|
177
178
|
The last two columns are not decoration. A rate over two traps and a rate over
|
|
178
179
|
eight are different claims, and an adapter that could only be asked one question
|
|
@@ -31,8 +31,8 @@ machine, so there is nothing to download and nothing to take on trust:
|
|
|
31
31
|
pip install "argleton[fixtures]"
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The probes ship with the runner, so that is the whole setup — release 0.
|
|
35
|
-
|
|
34
|
+
The probes ship with the runner, so that is the whole setup — release 0.3.0 carries the
|
|
35
|
+
23 traps and 26 families the results below were produced from. When the checkout runs ahead
|
|
36
36
|
of the release this paragraph says so, because a reader who cannot reproduce the table on
|
|
37
37
|
this page has been told something untrue.
|
|
38
38
|
|
|
@@ -73,8 +73,8 @@ every task it was given (completion 1.0) *and* gets this file silently wrong
|
|
|
73
73
|
(the 0.5).
|
|
74
74
|
|
|
75
75
|
There is a third adapter, `engine:naive` — read the file, take the statistic,
|
|
76
|
-
report it — and it is the most useful one here. It scores **0.
|
|
77
|
-
answers every clean probe correctly, falls into twenty-
|
|
76
|
+
report it — and it is the most useful one here. It scores **0.9565 / 1.0**: it
|
|
77
|
+
answers every clean probe correctly, falls into twenty-two of the twenty-three traps, and
|
|
78
78
|
**passes the remaining one**, because rasterio undoes the predictor on its
|
|
79
79
|
behalf. Careless code is not uniformly wrong. It is correct until the data stops
|
|
80
80
|
having the shape it usually has, which is what makes the exceptions so hard to
|
|
@@ -98,7 +98,7 @@ Side by side, one glance tells you which you are looking at.
|
|
|
98
98
|
|
|
99
99
|
## What is covered
|
|
100
100
|
|
|
101
|
-
Twenty families of twenty-
|
|
101
|
+
Twenty-one families of twenty-six, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
|
|
102
102
|
number from here can never be read as broader than it is. A low silent-error
|
|
103
103
|
rate means a system did not fail silently *on these probes*.
|
|
104
104
|
|
|
@@ -124,19 +124,20 @@ rate means a system did not fail silently *on these probes*.
|
|
|
124
124
|
| `aggregation-weighting` | 13.67% instead of 1.38% | averaging rates treats a village as equal to a city |
|
|
125
125
|
| `tabular-join` | 62000 people instead of 100000 | a CSV reader turns "001" into 1 and four municipalities leave the join |
|
|
126
126
|
| `positional-pairing` | 554 mm of rainfall instead of 268 | the Thiessen cells are all valid and tile the extent; only the row each one carries is wrong |
|
|
127
|
+
| `axis-order` | 16261 m² instead of 14042 | EPSG:4326 declares latitude first and every geometry library expects longitude first; both readings of a corner schedule stay in range |
|
|
127
128
|
|
|
128
129
|
## Results
|
|
129
130
|
|
|
130
|
-
Engine tier, twenty families, `spec_commit` pinned — [every run, and what the
|
|
131
|
+
Engine tier, twenty-one families, `spec_commit` pinned — [every run, and what the
|
|
131
132
|
numbers do not say](results/).
|
|
132
133
|
|
|
133
134
|
| system | silent error rate | completion rate | traps run | not applicable |
|
|
134
135
|
|---|---|---|---|---|
|
|
135
|
-
| MapSmith (main) | 0.00 | 1.00 |
|
|
136
|
-
| rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 4 |
|
|
137
|
-
| GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 |
|
|
138
|
-
| whitebox-workflows 2.0.6 | 0.50 | 1.00 | 2 |
|
|
139
|
-
| naive composition | 0.
|
|
136
|
+
| MapSmith (main) | 0.00 | 1.00 | 23 | 0 |
|
|
137
|
+
| rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 4 | 38 |
|
|
138
|
+
| GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 9 | 28 |
|
|
139
|
+
| whitebox-workflows 2.0.6 | 0.50 | 1.00 | 2 | 42 |
|
|
140
|
+
| naive composition | 0.9565 | 1.00 | 23 | 0 |
|
|
140
141
|
|
|
141
142
|
The last two columns are not decoration. A rate over two traps and a rate over
|
|
142
143
|
eight are different claims, and an adapter that could only be asked one question
|
|
@@ -207,3 +207,31 @@ class Adapter:
|
|
|
207
207
|
"assignment is not unique"
|
|
208
208
|
)
|
|
209
209
|
return Outcome(answer=float(gauges.loc[nearest, field]))
|
|
210
|
+
|
|
211
|
+
def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
212
|
+
import csv
|
|
213
|
+
|
|
214
|
+
from pyproj import Geod
|
|
215
|
+
from shapely.geometry import Polygon
|
|
216
|
+
|
|
217
|
+
# The header is the contract. A corner schedule can be written either
|
|
218
|
+
# way round — EPSG:4326 declares latitude first and every geometry
|
|
219
|
+
# library expects longitude first, and both conventions are current —
|
|
220
|
+
# so the column NAMES decide, not their positions.
|
|
221
|
+
with (workdir / probe.arguments[0]).open(encoding="utf-8") as handle:
|
|
222
|
+
rows = list(csv.DictReader(handle))
|
|
223
|
+
names = {name.strip().lower() for name in rows[0]}
|
|
224
|
+
for lon_name, lat_name in (("longitude", "latitude"), ("lon", "lat"), ("x", "y")):
|
|
225
|
+
if {lon_name, lat_name} <= names:
|
|
226
|
+
break
|
|
227
|
+
else:
|
|
228
|
+
return Outcome(
|
|
229
|
+
error="the corner schedule does not name its coordinate columns, "
|
|
230
|
+
"so which one is longitude cannot be established"
|
|
231
|
+
)
|
|
232
|
+
ring = [(float(row[lon_name]), float(row[lat_name])) for row in rows]
|
|
233
|
+
# Geodesic on the ellipsoid the CRS names: no map plane, so no
|
|
234
|
+
# projection distortion enters the answer.
|
|
235
|
+
return Outcome(answer=abs(Geod(ellps="WGS84").geometry_area_perimeter(
|
|
236
|
+
Polygon(ring)
|
|
237
|
+
)[0]))
|
|
@@ -238,3 +238,21 @@ class Adapter:
|
|
|
238
238
|
if hit.empty:
|
|
239
239
|
return Outcome(error="no Thiessen cell contains the site")
|
|
240
240
|
return Outcome(answer=float(hit[field].iloc[0]))
|
|
241
|
+
|
|
242
|
+
def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
243
|
+
import csv
|
|
244
|
+
|
|
245
|
+
from pyproj import Geod
|
|
246
|
+
from shapely.geometry import Polygon
|
|
247
|
+
|
|
248
|
+
# Read the corner schedule and build the ring: take the two coordinate
|
|
249
|
+
# columns in the order they appear, which is what `Polygon(df.values)`,
|
|
250
|
+
# `np.loadtxt` past the header and every csv.reader loop do.
|
|
251
|
+
#
|
|
252
|
+
# The header names both columns. Nobody asks it which one is x.
|
|
253
|
+
with (workdir / probe.arguments[0]).open(encoding="utf-8") as handle:
|
|
254
|
+
rows = list(csv.reader(handle))
|
|
255
|
+
ring = [(float(row[1]), float(row[2])) for row in rows[1:]]
|
|
256
|
+
return Outcome(answer=abs(Geod(ellps="WGS84").geometry_area_perimeter(
|
|
257
|
+
Polygon(ring)
|
|
258
|
+
)[0]))
|
|
@@ -531,3 +531,47 @@ class Adapter:
|
|
|
531
531
|
answer=float(values.iloc[0]),
|
|
532
532
|
warnings=self._warnings(cells) + self._warnings(joined),
|
|
533
533
|
)
|
|
534
|
+
|
|
535
|
+
def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
|
|
536
|
+
import pandas as pd
|
|
537
|
+
from mapsmith.engines import vector
|
|
538
|
+
|
|
539
|
+
# `parse_coordinates` has no positional path: `latitude_columns` and
|
|
540
|
+
# `longitude_columns` are both required and neither has a default, so
|
|
541
|
+
# the caller cannot hand it two columns and let it guess which is which.
|
|
542
|
+
# That was written for the DMS trap — "the caller says which, because the
|
|
543
|
+
# file cannot" — and it closes this one for the same reason rather than
|
|
544
|
+
# by luck.
|
|
545
|
+
table = workdir / probe.arguments[0]
|
|
546
|
+
columns = {name.strip().lower() for name in pd.read_csv(table, nrows=1).columns}
|
|
547
|
+
for lon_name, lat_name in (("longitude", "latitude"), ("lon", "lat")):
|
|
548
|
+
if {lon_name, lat_name} <= columns:
|
|
549
|
+
break
|
|
550
|
+
else:
|
|
551
|
+
return Outcome(
|
|
552
|
+
error="the corner schedule does not name its coordinate columns"
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
points = workdir / "_argleton_corners.parquet"
|
|
556
|
+
gathered = workdir / "_argleton_gathered.parquet"
|
|
557
|
+
ring = workdir / "_argleton_ring.parquet"
|
|
558
|
+
measured = workdir / "_argleton_area.parquet"
|
|
559
|
+
try:
|
|
560
|
+
vector.parse_coordinates(
|
|
561
|
+
str(table), str(points),
|
|
562
|
+
latitude_columns=lat_name, longitude_columns=lon_name,
|
|
563
|
+
)
|
|
564
|
+
# Dissolve first: `hull` works per feature, so five corner rows
|
|
565
|
+
# would give five hulls of one point each and an area of zero.
|
|
566
|
+
# MapSmith says so rather than returning the zero quietly —
|
|
567
|
+
# "0/5 features have polygonal geometry ... check whether the layer
|
|
568
|
+
# you meant is the polygon one" — which is how this composition got
|
|
569
|
+
# fixed instead of published.
|
|
570
|
+
vector.dissolve(str(points), str(gathered))
|
|
571
|
+
# The corners are convex and given in order, so the hull is the
|
|
572
|
+
# parcel. `hull` states which hull it took.
|
|
573
|
+
vector.hull(str(gathered), str(ring), kind="convex")
|
|
574
|
+
result = vector.measure_area(str(ring), str(measured), method="geodesic")
|
|
575
|
+
except Exception as failure: # noqa: BLE001 - the adapter reports, it does not raise
|
|
576
|
+
return Outcome(error=f"{type(failure).__name__}: {failure}")
|
|
577
|
+
return Outcome(answer=float(result["total_area_m2"]))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""The same parcel, the same square, the columns in the machine's order.
|
|
2
|
+
|
|
3
|
+
Identical footprint to 023-axis-order — the same 0.0012 degree square with its
|
|
4
|
+
south-west corner at longitude 23.7300, latitude 37.9800 — so the right answer
|
|
5
|
+
is the same number. The only thing that changed is which column comes first,
|
|
6
|
+
which is the one thing the trap is about.
|
|
7
|
+
|
|
8
|
+
A reader that takes the two columns in the order it finds them is correct here
|
|
9
|
+
and wrong there. A reader that reads the header is correct in both.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
LON0 = 23.7300
|
|
18
|
+
LAT0 = 37.9800
|
|
19
|
+
SIDE = 0.0012
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(destination: Path) -> int:
|
|
23
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
|
|
25
|
+
ring = [
|
|
26
|
+
(LON0, LAT0),
|
|
27
|
+
(LON0 + SIDE, LAT0),
|
|
28
|
+
(LON0 + SIDE, LAT0 + SIDE),
|
|
29
|
+
(LON0, LAT0 + SIDE),
|
|
30
|
+
(LON0, LAT0),
|
|
31
|
+
]
|
|
32
|
+
# Longitude first: the order shapefiles, GeoJSON and every geometry library
|
|
33
|
+
# use, and the order OGC calls CRS84. Just as declared as the other one, and
|
|
34
|
+
# just as common — which is the whole difficulty.
|
|
35
|
+
lines = ["corner,longitude,latitude"]
|
|
36
|
+
lines += [
|
|
37
|
+
f"{index + 1},{lon:.4f},{lat:.4f}" for index, (lon, lat) in enumerate(ring)
|
|
38
|
+
]
|
|
39
|
+
(destination / "parcel.csv").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
id = "c023-longitude-first"
|
|
2
|
+
population = "clean"
|
|
3
|
+
family = "axis-order"
|
|
4
|
+
title = "The same corner schedule, longitude first"
|
|
5
|
+
surface = ["engine", "agent"]
|
|
6
|
+
|
|
7
|
+
[task]
|
|
8
|
+
call = "parcel_area_m2(parcel.csv)"
|
|
9
|
+
prompt = """
|
|
10
|
+
parcel.csv is a corner schedule for one land parcel: one row per corner, in
|
|
11
|
+
order, closing the ring. The coordinates are in WGS 84 (EPSG:4326). How large
|
|
12
|
+
is the parcel on the ground, in square metres? Answer with the number."""
|
|
13
|
+
|
|
14
|
+
[truth]
|
|
15
|
+
kind = "scalar"
|
|
16
|
+
value = 14047.0
|
|
17
|
+
tolerance = 8.0
|
|
18
|
+
derivation = """
|
|
19
|
+
The same parcel as 023-axis-order and therefore the same number: an axis-aligned
|
|
20
|
+
square of 0.0012 deg with its south-west corner at longitude 23.7300, latitude
|
|
21
|
+
37.9800, whose ellipsoidal area is 14042.346 m2 by the zone integral, 14042.346
|
|
22
|
+
by pyproj's geodesic area and 14051.010 in the UTM 34N plane. The tolerance of 8
|
|
23
|
+
around 14047 admits all three.
|
|
24
|
+
|
|
25
|
+
What changed is the column order in the file: longitude first, as the header
|
|
26
|
+
says. Reading the two columns in the order they appear is correct here."""
|
|
27
|
+
|
|
28
|
+
[provenance]
|
|
29
|
+
source = """
|
|
30
|
+
Clean twin of 023-axis-order: the same question, the same footprint, the same
|
|
31
|
+
five rows, the same header line naming both columns. One thing differs, and it
|
|
32
|
+
is the thing under test — which of the two declared conventions the file uses.
|
|
33
|
+
|
|
34
|
+
Without this twin a silent-error rate on the family could not be told apart from
|
|
35
|
+
a system that cannot read a corner schedule at all. A system that answers this
|
|
36
|
+
and fails 023 mishandles axis order; one that fails both cannot do the task, and
|
|
37
|
+
that is a different and lesser finding."""
|
|
38
|
+
found_by = "mapsmith"
|
|
39
|
+
date = "2026-08-29"
|
|
40
|
+
|
|
41
|
+
[provenance.engine_versions]
|
|
42
|
+
pyproj = "3.7.2/PROJ 9.5.1"
|
|
43
|
+
shapely = "2.1.2"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Families, and what is actually covered
|
|
2
2
|
|
|
3
|
-
Twenty-
|
|
3
|
+
Twenty-six families are on the list — twelve planned at the start, fourteen added
|
|
4
4
|
from reproductions and from a survey of what the archives and the libraries
|
|
5
|
-
themselves warn about. **Twenty are implemented.** This page exists so that
|
|
5
|
+
themselves warn about. **Twenty-one are implemented.** This page exists so that
|
|
6
6
|
a number from Argleton can never be read as broader than it is: a low
|
|
7
7
|
silent-error rate means a system did not fail silently *on these probes*, and
|
|
8
8
|
this is the list of what that sentence covers.
|
|
@@ -18,6 +18,7 @@ cannot read as ten independent findings.
|
|
|
18
18
|
| 3 | `linear-units` | [002](../traps/002-feet-as-metres/) + [c002](../clean/c002-projected-area/) | 100 ha instead of 9.29 ha | Both are ordinary parcels; they differ by 3.28², a factor nothing downstream questions |
|
|
19
19
|
| 4 | `mismatched-crs` | [004](../traps/004-mismatched-crs-join/) + [c004](../clean/c004-points-in-polygon/) | 0 points in the zone instead of 12 | An empty spatial result is a finding, not an error — "no wells inside the zone" reads as good news, and nothing downstream questions an empty join |
|
|
20
20
|
| 25 | `positional-pairing` | [022](../traps/022-thiessen-pairing/) + [c022](../clean/c022-two-gauges/) | 554 mm of rainfall instead of 268 | The cells are all valid, they tile the extent, the count is right and the map is a Thiessen diagram — only the pairing between a cell and the row it carries is wrong, and the set of cells is identical either way, so nothing about the geometry can show it |
|
|
21
|
+
| 26 | `axis-order` | [023](../traps/023-axis-order/) + [c023](../clean/c023-longitude-first/) | 16261 m² instead of 14042 | EPSG:4326 declares latitude first and every geometry library expects longitude first — both conventions are current, both are declared, and a corner schedule read in the order it is written puts the parcel 14 degrees away without leaving the valid range for either coordinate |
|
|
21
22
|
| 24 | `datum-ballpark` | [021](../traps/021-ballpark-datum/) + [c021](../clean/c021-greenwich-variant/) | latitude 45.5 instead of 45.500669074, which is 74 m | The longitude is right, the output CRS is right, and the transformation the library chose reports its accuracy as -1 only if you ask after the fact |
|
|
22
23
|
| 6 | `nodata` | [003](../traps/003-nodata-in-statistics/) + [c003](../clean/c003-raster-mean-nodata/) | mean 945.005 instead of 1000.0 | 5.5% out — too small to question, too large to ignore in a volume or a flood level |
|
|
23
24
|
| 7 | `invalid-geometry` | [005](../traps/005-bowtie-area/) + [c005](../clean/c005-polygon-area/) | 2400 m² instead of 5100 m² | The signed-shoelace artifact of a self-crossing ring: no exception, no warning, and both numbers are ordinary parcels |
|
|
@@ -93,8 +94,8 @@ See [ADDING-A-TRAP.md](ADDING-A-TRAP.md).
|
|
|
93
94
|
|
|
94
95
|
## What this list does not claim
|
|
95
96
|
|
|
96
|
-
That these twenty-
|
|
97
|
-
argue for, each with a source. A twenty-
|
|
97
|
+
That these twenty-six are the complete set. They are the families we can currently
|
|
98
|
+
argue for, each with a source. A twenty-seventh that meets the four conditions
|
|
98
99
|
belongs here, and the fastest way to improve this suite is to bring one — the
|
|
99
100
|
same survey that produced the last nine has more candidates in it than this
|
|
100
101
|
list has entries, and being able to say which ones failed the conditions is
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "argleton"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Probes whose right answer is known by construction, and whose wrong answer looks fine"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11" # tomllib, so the runner needs nothing installed
|