argleton 0.3.0__tar.gz → 0.4.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.
Files changed (260) hide show
  1. {argleton-0.3.0 → argleton-0.4.0}/.github/workflows/ci.yml +21 -2
  2. argleton-0.4.0/.zenodo.json +19 -0
  3. {argleton-0.3.0 → argleton-0.4.0}/CITATION.cff +2 -2
  4. {argleton-0.3.0 → argleton-0.4.0}/PKG-INFO +65 -33
  5. {argleton-0.3.0 → argleton-0.4.0}/README.md +64 -32
  6. {argleton-0.3.0 → argleton-0.4.0}/adapters/engine_geopandas.py +90 -28
  7. {argleton-0.3.0 → argleton-0.4.0}/adapters/engine_naive.py +115 -18
  8. {argleton-0.3.0 → argleton-0.4.0}/adapters/engine_rasterio.py +61 -0
  9. argleton-0.4.0/adapters/engine_whitebox.py +80 -0
  10. {argleton-0.3.0 → argleton-0.4.0}/adapters/mapsmith.py +191 -0
  11. argleton-0.4.0/clean/c024-pixel-is-area/build.py +82 -0
  12. argleton-0.4.0/clean/c024-pixel-is-area/probe.toml +52 -0
  13. argleton-0.4.0/clean/c025-single-hemisphere-zone/build.py +95 -0
  14. argleton-0.4.0/clean/c025-single-hemisphere-zone/probe.toml +46 -0
  15. argleton-0.4.0/clean/c026-north-up-grid/build.py +64 -0
  16. argleton-0.4.0/clean/c026-north-up-grid/probe.toml +42 -0
  17. argleton-0.4.0/clean/c027-lines-only/build.py +60 -0
  18. argleton-0.4.0/clean/c027-lines-only/probe.toml +44 -0
  19. argleton-0.4.0/clean/c028-projected-field/build.py +57 -0
  20. argleton-0.4.0/clean/c028-projected-field/probe.toml +48 -0
  21. argleton-0.4.0/clean/c029-coincident-boundaries/build.py +49 -0
  22. argleton-0.4.0/clean/c029-coincident-boundaries/probe.toml +49 -0
  23. argleton-0.4.0/clean/c030-single-georeferencing/build.py +36 -0
  24. argleton-0.4.0/clean/c030-single-georeferencing/probe.toml +39 -0
  25. {argleton-0.3.0 → argleton-0.4.0}/docs/FAMILIES.md +44 -4
  26. {argleton-0.3.0 → argleton-0.4.0}/pyproject.toml +1 -1
  27. argleton-0.4.0/results/2026-08-30-all-families/adapters-geopandas.json +719 -0
  28. argleton-0.4.0/results/2026-08-30-all-families/adapters-mapsmith.json +718 -0
  29. argleton-0.4.0/results/2026-08-30-all-families/adapters-naive.json +718 -0
  30. argleton-0.4.0/results/2026-08-30-all-families/adapters-rasterio.json +718 -0
  31. argleton-0.4.0/results/2026-08-30-all-families/adapters-whitebox.json +719 -0
  32. argleton-0.4.0/results/2026-08-30-antimeridian/adapters-geopandas.json +623 -0
  33. argleton-0.4.0/results/2026-08-30-antimeridian/adapters-mapsmith.json +622 -0
  34. argleton-0.4.0/results/2026-08-30-antimeridian/adapters-naive.json +622 -0
  35. argleton-0.4.0/results/2026-08-30-antimeridian/adapters-rasterio.json +622 -0
  36. argleton-0.4.0/results/2026-08-30-antimeridian/adapters-whitebox.json +623 -0
  37. argleton-0.4.0/results/2026-08-30-grid-registration/adapters-geopandas.json +599 -0
  38. argleton-0.4.0/results/2026-08-30-grid-registration/adapters-mapsmith.json +598 -0
  39. argleton-0.4.0/results/2026-08-30-grid-registration/adapters-naive.json +598 -0
  40. argleton-0.4.0/results/2026-08-30-grid-registration/adapters-rasterio.json +598 -0
  41. argleton-0.4.0/results/2026-08-30-grid-registration/adapters-whitebox.json +599 -0
  42. argleton-0.4.0/results/LATEST +1 -0
  43. {argleton-0.3.0 → argleton-0.4.0}/results/README.md +182 -2
  44. {argleton-0.3.0 → argleton-0.4.0}/site/index.template.html +3 -2
  45. {argleton-0.3.0 → argleton-0.4.0}/tests/test_showcase.py +188 -4
  46. argleton-0.4.0/traps/024-pixel-is-point/README.md +111 -0
  47. argleton-0.4.0/traps/024-pixel-is-point/build.py +78 -0
  48. argleton-0.4.0/traps/024-pixel-is-point/probe.toml +137 -0
  49. argleton-0.4.0/traps/025-antimeridian-zone/README.md +92 -0
  50. argleton-0.4.0/traps/025-antimeridian-zone/build.py +108 -0
  51. argleton-0.4.0/traps/025-antimeridian-zone/probe.toml +134 -0
  52. argleton-0.4.0/traps/026-south-up-grid/README.md +101 -0
  53. argleton-0.4.0/traps/026-south-up-grid/build.py +83 -0
  54. argleton-0.4.0/traps/026-south-up-grid/probe.toml +129 -0
  55. argleton-0.4.0/traps/027-mixed-geometry/README.md +60 -0
  56. argleton-0.4.0/traps/027-mixed-geometry/build.py +79 -0
  57. argleton-0.4.0/traps/027-mixed-geometry/probe.toml +107 -0
  58. argleton-0.4.0/traps/028-degrees-as-metres/README.md +82 -0
  59. argleton-0.4.0/traps/028-degrees-as-metres/build.py +71 -0
  60. argleton-0.4.0/traps/028-degrees-as-metres/probe.toml +122 -0
  61. argleton-0.4.0/traps/029-difference-order/README.md +66 -0
  62. argleton-0.4.0/traps/029-difference-order/build.py +61 -0
  63. argleton-0.4.0/traps/029-difference-order/probe.toml +112 -0
  64. argleton-0.4.0/traps/030-sidecar-georeferencing/README.md +79 -0
  65. argleton-0.4.0/traps/030-sidecar-georeferencing/build.py +93 -0
  66. argleton-0.4.0/traps/030-sidecar-georeferencing/probe.toml +95 -0
  67. argleton-0.3.0/adapters/engine_whitebox.py +0 -34
  68. argleton-0.3.0/results/LATEST +0 -1
  69. {argleton-0.3.0 → argleton-0.4.0}/.gitattributes +0 -0
  70. {argleton-0.3.0 → argleton-0.4.0}/.github/workflows/pages.yml +0 -0
  71. {argleton-0.3.0 → argleton-0.4.0}/.gitignore +0 -0
  72. {argleton-0.3.0 → argleton-0.4.0}/LICENSE +0 -0
  73. {argleton-0.3.0 → argleton-0.4.0}/adapters/__init__.py +0 -0
  74. {argleton-0.3.0 → argleton-0.4.0}/adapters/gis_mcp.py +0 -0
  75. {argleton-0.3.0 → argleton-0.4.0}/argleton/__init__.py +0 -0
  76. {argleton-0.3.0 → argleton-0.4.0}/argleton/model.py +0 -0
  77. {argleton-0.3.0 → argleton-0.4.0}/argleton/published.py +0 -0
  78. {argleton-0.3.0 → argleton-0.4.0}/argleton/run.py +0 -0
  79. {argleton-0.3.0 → argleton-0.4.0}/argleton/score.py +0 -0
  80. {argleton-0.3.0 → argleton-0.4.0}/clean/c001-raster-mean/build.py +0 -0
  81. {argleton-0.3.0 → argleton-0.4.0}/clean/c001-raster-mean/probe.toml +0 -0
  82. {argleton-0.3.0 → argleton-0.4.0}/clean/c002-projected-area/build.py +0 -0
  83. {argleton-0.3.0 → argleton-0.4.0}/clean/c002-projected-area/probe.toml +0 -0
  84. {argleton-0.3.0 → argleton-0.4.0}/clean/c003-raster-mean-nodata/build.py +0 -0
  85. {argleton-0.3.0 → argleton-0.4.0}/clean/c003-raster-mean-nodata/probe.toml +0 -0
  86. {argleton-0.3.0 → argleton-0.4.0}/clean/c004-points-in-polygon/build.py +0 -0
  87. {argleton-0.3.0 → argleton-0.4.0}/clean/c004-points-in-polygon/probe.toml +0 -0
  88. {argleton-0.3.0 → argleton-0.4.0}/clean/c005-polygon-area/build.py +0 -0
  89. {argleton-0.3.0 → argleton-0.4.0}/clean/c005-polygon-area/probe.toml +0 -0
  90. {argleton-0.3.0 → argleton-0.4.0}/clean/c006-named-layer/build.py +0 -0
  91. {argleton-0.3.0 → argleton-0.4.0}/clean/c006-named-layer/probe.toml +0 -0
  92. {argleton-0.3.0 → argleton-0.4.0}/clean/c007-distance-in-metres/build.py +0 -0
  93. {argleton-0.3.0 → argleton-0.4.0}/clean/c007-distance-in-metres/probe.toml +0 -0
  94. {argleton-0.3.0 → argleton-0.4.0}/clean/c008-equal-area-crs/build.py +0 -0
  95. {argleton-0.3.0 → argleton-0.4.0}/clean/c008-equal-area-crs/probe.toml +0 -0
  96. {argleton-0.3.0 → argleton-0.4.0}/clean/c009-native-resolution-classes/build.py +0 -0
  97. {argleton-0.3.0 → argleton-0.4.0}/clean/c009-native-resolution-classes/probe.toml +0 -0
  98. {argleton-0.3.0 → argleton-0.4.0}/clean/c010-physical-values/build.py +0 -0
  99. {argleton-0.3.0 → argleton-0.4.0}/clean/c010-physical-values/probe.toml +0 -0
  100. {argleton-0.3.0 → argleton-0.4.0}/clean/c011-solid-parcel/build.py +0 -0
  101. {argleton-0.3.0 → argleton-0.4.0}/clean/c011-solid-parcel/probe.toml +0 -0
  102. {argleton-0.3.0 → argleton-0.4.0}/clean/c012-disjoint-concessions/build.py +0 -0
  103. {argleton-0.3.0 → argleton-0.4.0}/clean/c012-disjoint-concessions/probe.toml +0 -0
  104. {argleton-0.3.0 → argleton-0.4.0}/clean/c013-flat-pipeline/build.py +0 -0
  105. {argleton-0.3.0 → argleton-0.4.0}/clean/c013-flat-pipeline/probe.toml +0 -0
  106. {argleton-0.3.0 → argleton-0.4.0}/clean/c014-convex-parcel/build.py +0 -0
  107. {argleton-0.3.0 → argleton-0.4.0}/clean/c014-convex-parcel/probe.toml +0 -0
  108. {argleton-0.3.0 → argleton-0.4.0}/clean/c015-wells-off-the-seam/build.py +0 -0
  109. {argleton-0.3.0 → argleton-0.4.0}/clean/c015-wells-off-the-seam/probe.toml +0 -0
  110. {argleton-0.3.0 → argleton-0.4.0}/clean/c016-decimal-degrees/build.py +0 -0
  111. {argleton-0.3.0 → argleton-0.4.0}/clean/c016-decimal-degrees/probe.toml +0 -0
  112. {argleton-0.3.0 → argleton-0.4.0}/clean/c017-equal-populations/build.py +0 -0
  113. {argleton-0.3.0 → argleton-0.4.0}/clean/c017-equal-populations/probe.toml +0 -0
  114. {argleton-0.3.0 → argleton-0.4.0}/clean/c018-plain-keys/build.py +0 -0
  115. {argleton-0.3.0 → argleton-0.4.0}/clean/c018-plain-keys/probe.toml +0 -0
  116. {argleton-0.3.0 → argleton-0.4.0}/clean/c019-fully-contained/build.py +0 -0
  117. {argleton-0.3.0 → argleton-0.4.0}/clean/c019-fully-contained/probe.toml +0 -0
  118. {argleton-0.3.0 → argleton-0.4.0}/clean/c020-one-owner-each/build.py +0 -0
  119. {argleton-0.3.0 → argleton-0.4.0}/clean/c020-one-owner-each/probe.toml +0 -0
  120. {argleton-0.3.0 → argleton-0.4.0}/clean/c021-greenwich-variant/build.py +0 -0
  121. {argleton-0.3.0 → argleton-0.4.0}/clean/c021-greenwich-variant/probe.toml +0 -0
  122. {argleton-0.3.0 → argleton-0.4.0}/clean/c022-two-gauges/build.py +0 -0
  123. {argleton-0.3.0 → argleton-0.4.0}/clean/c022-two-gauges/probe.toml +0 -0
  124. {argleton-0.3.0 → argleton-0.4.0}/clean/c023-longitude-first/build.py +0 -0
  125. {argleton-0.3.0 → argleton-0.4.0}/clean/c023-longitude-first/probe.toml +0 -0
  126. {argleton-0.3.0 → argleton-0.4.0}/docs/ADDING-A-TRAP.md +0 -0
  127. {argleton-0.3.0 → argleton-0.4.0}/docs/METHOD.md +0 -0
  128. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-23-first-run/adapters-mapsmith.json +0 -0
  129. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-geopandas.json +0 -0
  130. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-naive.json +0 -0
  131. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-rasterio.json +0 -0
  132. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-23-first-run/engine-whitebox.json +0 -0
  133. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-24-five-families/adapters-mapsmith.json +0 -0
  134. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-geopandas.json +0 -0
  135. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-naive.json +0 -0
  136. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-rasterio.json +0 -0
  137. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-24-five-families/engine-whitebox.json +0 -0
  138. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-eight-families/adapters-mapsmith.json +0 -0
  139. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-geopandas.json +0 -0
  140. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-naive.json +0 -0
  141. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-rasterio.json +0 -0
  142. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-eight-families/engine-whitebox.json +0 -0
  143. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-six-families/adapters-mapsmith.json +0 -0
  144. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-geopandas.json +0 -0
  145. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-naive.json +0 -0
  146. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-rasterio.json +0 -0
  147. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-25-six-families/engine-whitebox.json +0 -0
  148. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-geopandas.json +0 -0
  149. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-mapsmith.json +0 -0
  150. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-naive.json +0 -0
  151. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-rasterio.json +0 -0
  152. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-eighteen-families/adapters-whitebox.json +0 -0
  153. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-geopandas.json +0 -0
  154. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-mapsmith.json +0 -0
  155. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-naive.json +0 -0
  156. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-rasterio.json +0 -0
  157. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nine-families/adapters-whitebox.json +0 -0
  158. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-geopandas.json +0 -0
  159. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-mapsmith.json +0 -0
  160. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-naive.json +0 -0
  161. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-rasterio.json +0 -0
  162. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-nineteen-families/adapters-whitebox.json +0 -0
  163. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-geopandas.json +0 -0
  164. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-mapsmith.json +0 -0
  165. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-naive.json +0 -0
  166. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-rasterio.json +0 -0
  167. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-26-ten-families/adapters-whitebox.json +0 -0
  168. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-geopandas.json +0 -0
  169. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-mapsmith.json +0 -0
  170. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-naive.json +0 -0
  171. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-rasterio.json +0 -0
  172. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-27-datum-shift-fixed/adapters-whitebox.json +0 -0
  173. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-geopandas.json +0 -0
  174. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-mapsmith.json +0 -0
  175. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-naive.json +0 -0
  176. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-rasterio.json +0 -0
  177. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-thiessen-pairing/adapters-whitebox.json +0 -0
  178. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-geopandas.json +0 -0
  179. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-mapsmith.json +0 -0
  180. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-naive.json +0 -0
  181. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-rasterio.json +0 -0
  182. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-28-timings/adapters-whitebox.json +0 -0
  183. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-29-axis-order/adapters-geopandas.json +0 -0
  184. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-29-axis-order/adapters-mapsmith.json +0 -0
  185. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-29-axis-order/adapters-naive.json +0 -0
  186. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-29-axis-order/adapters-rasterio.json +0 -0
  187. {argleton-0.3.0 → argleton-0.4.0}/results/2026-08-29-axis-order/adapters-whitebox.json +0 -0
  188. {argleton-0.3.0 → argleton-0.4.0}/schema/probe.schema.json +0 -0
  189. {argleton-0.3.0 → argleton-0.4.0}/schema/result.schema.json +0 -0
  190. {argleton-0.3.0 → argleton-0.4.0}/site/build.py +0 -0
  191. {argleton-0.3.0 → argleton-0.4.0}/tests/test_contracts.py +0 -0
  192. {argleton-0.3.0 → argleton-0.4.0}/traps/001-tiff-predictor/README.md +0 -0
  193. {argleton-0.3.0 → argleton-0.4.0}/traps/001-tiff-predictor/build.py +0 -0
  194. {argleton-0.3.0 → argleton-0.4.0}/traps/001-tiff-predictor/probe.toml +0 -0
  195. {argleton-0.3.0 → argleton-0.4.0}/traps/002-feet-as-metres/README.md +0 -0
  196. {argleton-0.3.0 → argleton-0.4.0}/traps/002-feet-as-metres/build.py +0 -0
  197. {argleton-0.3.0 → argleton-0.4.0}/traps/002-feet-as-metres/probe.toml +0 -0
  198. {argleton-0.3.0 → argleton-0.4.0}/traps/003-nodata-in-statistics/README.md +0 -0
  199. {argleton-0.3.0 → argleton-0.4.0}/traps/003-nodata-in-statistics/build.py +0 -0
  200. {argleton-0.3.0 → argleton-0.4.0}/traps/003-nodata-in-statistics/probe.toml +0 -0
  201. {argleton-0.3.0 → argleton-0.4.0}/traps/004-mismatched-crs-join/README.md +0 -0
  202. {argleton-0.3.0 → argleton-0.4.0}/traps/004-mismatched-crs-join/build.py +0 -0
  203. {argleton-0.3.0 → argleton-0.4.0}/traps/004-mismatched-crs-join/probe.toml +0 -0
  204. {argleton-0.3.0 → argleton-0.4.0}/traps/005-bowtie-area/README.md +0 -0
  205. {argleton-0.3.0 → argleton-0.4.0}/traps/005-bowtie-area/build.py +0 -0
  206. {argleton-0.3.0 → argleton-0.4.0}/traps/005-bowtie-area/probe.toml +0 -0
  207. {argleton-0.3.0 → argleton-0.4.0}/traps/006-default-layer/README.md +0 -0
  208. {argleton-0.3.0 → argleton-0.4.0}/traps/006-default-layer/build.py +0 -0
  209. {argleton-0.3.0 → argleton-0.4.0}/traps/006-default-layer/probe.toml +0 -0
  210. {argleton-0.3.0 → argleton-0.4.0}/traps/007-buffer-in-degrees/README.md +0 -0
  211. {argleton-0.3.0 → argleton-0.4.0}/traps/007-buffer-in-degrees/build.py +0 -0
  212. {argleton-0.3.0 → argleton-0.4.0}/traps/007-buffer-in-degrees/probe.toml +0 -0
  213. {argleton-0.3.0 → argleton-0.4.0}/traps/008-web-mercator-area/README.md +0 -0
  214. {argleton-0.3.0 → argleton-0.4.0}/traps/008-web-mercator-area/build.py +0 -0
  215. {argleton-0.3.0 → argleton-0.4.0}/traps/008-web-mercator-area/probe.toml +0 -0
  216. {argleton-0.3.0 → argleton-0.4.0}/traps/009-resampled-classes/README.md +0 -0
  217. {argleton-0.3.0 → argleton-0.4.0}/traps/009-resampled-classes/build.py +0 -0
  218. {argleton-0.3.0 → argleton-0.4.0}/traps/009-resampled-classes/probe.toml +0 -0
  219. {argleton-0.3.0 → argleton-0.4.0}/traps/010-scale-offset/README.md +0 -0
  220. {argleton-0.3.0 → argleton-0.4.0}/traps/010-scale-offset/build.py +0 -0
  221. {argleton-0.3.0 → argleton-0.4.0}/traps/010-scale-offset/probe.toml +0 -0
  222. {argleton-0.3.0 → argleton-0.4.0}/traps/011-polygon-holes/README.md +0 -0
  223. {argleton-0.3.0 → argleton-0.4.0}/traps/011-polygon-holes/build.py +0 -0
  224. {argleton-0.3.0 → argleton-0.4.0}/traps/011-polygon-holes/probe.toml +0 -0
  225. {argleton-0.3.0 → argleton-0.4.0}/traps/012-double-counting/README.md +0 -0
  226. {argleton-0.3.0 → argleton-0.4.0}/traps/012-double-counting/build.py +0 -0
  227. {argleton-0.3.0 → argleton-0.4.0}/traps/012-double-counting/probe.toml +0 -0
  228. {argleton-0.3.0 → argleton-0.4.0}/traps/013-z-dimension/README.md +0 -0
  229. {argleton-0.3.0 → argleton-0.4.0}/traps/013-z-dimension/build.py +0 -0
  230. {argleton-0.3.0 → argleton-0.4.0}/traps/013-z-dimension/probe.toml +0 -0
  231. {argleton-0.3.0 → argleton-0.4.0}/traps/014-centroid-outside/README.md +0 -0
  232. {argleton-0.3.0 → argleton-0.4.0}/traps/014-centroid-outside/build.py +0 -0
  233. {argleton-0.3.0 → argleton-0.4.0}/traps/014-centroid-outside/probe.toml +0 -0
  234. {argleton-0.3.0 → argleton-0.4.0}/traps/015-boundary-semantics/README.md +0 -0
  235. {argleton-0.3.0 → argleton-0.4.0}/traps/015-boundary-semantics/build.py +0 -0
  236. {argleton-0.3.0 → argleton-0.4.0}/traps/015-boundary-semantics/probe.toml +0 -0
  237. {argleton-0.3.0 → argleton-0.4.0}/traps/016-coordinate-parsing/README.md +0 -0
  238. {argleton-0.3.0 → argleton-0.4.0}/traps/016-coordinate-parsing/build.py +0 -0
  239. {argleton-0.3.0 → argleton-0.4.0}/traps/016-coordinate-parsing/probe.toml +0 -0
  240. {argleton-0.3.0 → argleton-0.4.0}/traps/017-aggregation-weighting/README.md +0 -0
  241. {argleton-0.3.0 → argleton-0.4.0}/traps/017-aggregation-weighting/build.py +0 -0
  242. {argleton-0.3.0 → argleton-0.4.0}/traps/017-aggregation-weighting/probe.toml +0 -0
  243. {argleton-0.3.0 → argleton-0.4.0}/traps/018-join-key-typing/README.md +0 -0
  244. {argleton-0.3.0 → argleton-0.4.0}/traps/018-join-key-typing/build.py +0 -0
  245. {argleton-0.3.0 → argleton-0.4.0}/traps/018-join-key-typing/probe.toml +0 -0
  246. {argleton-0.3.0 → argleton-0.4.0}/traps/019-partial-overlap/README.md +0 -0
  247. {argleton-0.3.0 → argleton-0.4.0}/traps/019-partial-overlap/build.py +0 -0
  248. {argleton-0.3.0 → argleton-0.4.0}/traps/019-partial-overlap/probe.toml +0 -0
  249. {argleton-0.3.0 → argleton-0.4.0}/traps/020-join-cardinality/README.md +0 -0
  250. {argleton-0.3.0 → argleton-0.4.0}/traps/020-join-cardinality/build.py +0 -0
  251. {argleton-0.3.0 → argleton-0.4.0}/traps/020-join-cardinality/probe.toml +0 -0
  252. {argleton-0.3.0 → argleton-0.4.0}/traps/021-ballpark-datum/README.md +0 -0
  253. {argleton-0.3.0 → argleton-0.4.0}/traps/021-ballpark-datum/build.py +0 -0
  254. {argleton-0.3.0 → argleton-0.4.0}/traps/021-ballpark-datum/probe.toml +0 -0
  255. {argleton-0.3.0 → argleton-0.4.0}/traps/022-thiessen-pairing/README.md +0 -0
  256. {argleton-0.3.0 → argleton-0.4.0}/traps/022-thiessen-pairing/build.py +0 -0
  257. {argleton-0.3.0 → argleton-0.4.0}/traps/022-thiessen-pairing/probe.toml +0 -0
  258. {argleton-0.3.0 → argleton-0.4.0}/traps/023-axis-order/README.md +0 -0
  259. {argleton-0.3.0 → argleton-0.4.0}/traps/023-axis-order/build.py +0 -0
  260. {argleton-0.3.0 → argleton-0.4.0}/traps/023-axis-order/probe.toml +0 -0
@@ -16,6 +16,12 @@ jobs:
16
16
  python: ["3.11", "3.13"]
17
17
  steps:
18
18
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19
+ with:
20
+ # Tags, because one showcase test checks the trap count the README
21
+ # attributes to a release against the tag that release was cut from.
22
+ # A shallow checkout has no tags, and the test would skip in exactly
23
+ # the place it is meant to run.
24
+ fetch-depth: 0
19
25
  - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
20
26
  with:
21
27
  python-version: ${{ matrix.python }}
@@ -76,6 +82,19 @@ jobs:
76
82
  # point — a trap that stops catching its own naive adapter, or one
77
83
  # that starts catching a case it should not, has changed what it
78
84
  # measures, and that must be a decision rather than a surprise.
79
- # 0.9545 = traps 002 through 022 caught, 001 passed, out of 22.
80
- sys.exit(0 if (r["silent_error_rate"], r["completion_rate"]) == (0.9545, 1.0) else 1)
85
+ # 0.9333 = 28 of 30 traps caught. TWO still pass, unchanged: 001,
86
+ # where rasterio undoes the predictor on the naive adapter's behalf,
87
+ # and 026, where `src.res` reports the cell size faithfully whichever
88
+ # way the rows run — so a plain numpy gradient is more faithful to the
89
+ # geotransform than a specialised terrain engine is. Moved from 0.931
90
+ # on 2026-08-31 when trap 030 added family 28: the naive adapter falls
91
+ # into it, so both numerator and denominator went up by one. Before
92
+ # that 0.96 when traps 026 to 029 closed the original family list, and
93
+ # 0.9583, 0.9565 and 0.9545 as traps 025, 024 and 023 landed.
94
+ # Each time the denominator changed and the numerator changed with
95
+ # it. This pin failing on such a commit is the
96
+ # mechanism working — the number moved because a trap was added, which
97
+ # is a decision, and it has to be written down here rather than
98
+ # discovered in a published table.
99
+ sys.exit(0 if (r["silent_error_rate"], r["completion_rate"]) == (0.9333, 1.0) else 1)
81
100
  PY
@@ -0,0 +1,19 @@
1
+ {
2
+ "upload_type": "software",
3
+ "title": "Argleton: a correctness suite for geospatial systems",
4
+ "version": "0.4.0",
5
+ "creators": [
6
+ {"name": "The Argleton project"}
7
+ ],
8
+ "license": "apache-2.0",
9
+ "access_right": "open",
10
+ "keywords": [
11
+ "geospatial",
12
+ "GIS",
13
+ "benchmark",
14
+ "correctness",
15
+ "silent errors",
16
+ "evaluation"
17
+ ],
18
+ "description": "<p>Argleton measures how often a geospatial system answers confidently and wrongly.</p><p>Each probe has a right answer known <em>by construction</em> &mdash; derived on paper, never measured from a reference implementation &mdash; and a typical wrong answer that looks entirely plausible. Every trap ships with a defect-free twin, so a system cannot score well by refusing to answer: the suite reports a silent-error rate alongside a completion rate on the twins, and a refusal counts as neither a pass nor a silent error.</p><p>The name is the phantom settlement Google Maps showed for two years near Aughton: an empty field for which the map offered photographs of houses, restaurants and a hospital. Well-formed data, valid against its schema, rendered with confidence, entirely false, and crashing nothing. That is the class of error this suite measures.</p><p>Published from an organisation separate from the system it was extracted from, and that relationship is stated in the repository rather than hidden &mdash; an evaluation living inside the thing it evaluates can be dismissed in one line.</p>"
19
+ }
@@ -7,8 +7,8 @@ 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.3.0"
11
- date-released: "2026-08-29"
10
+ version: "0.4.0"
11
+ date-released: "2026-08-31"
12
12
  abstract: >-
13
13
  Probes whose right answer is known by construction — derived on paper, never
14
14
  measured from a reference implementation — and whose typical wrong answer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: argleton
3
- Version: 0.3.0
3
+ Version: 0.4.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.3.0 carries the
71
- 23 traps and 26 families the results below were produced from. When the checkout runs ahead
70
+ The probes ship with the runner, so that is the whole setup — release 0.4.0 carries the
71
+ 30 traps and 28 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
 
@@ -83,36 +83,56 @@ pip install -e ".[fixtures]"
83
83
 
84
84
  ```
85
85
  $ argleton --adapter engine:rasterio
86
- ok clean c001-raster-mean correct 1093.0
87
- ok clean c003-raster-mean-nodata correct 1000.0
88
- ok clean c009-native-resolution-classes correct 900.0
89
- ok clean c010-physical-values correct 0.5
90
- ok trap 001-tiff-predictor correct 1093.0
91
- ok trap 003-nodata-in-statistics correct 1000.0
92
- ok trap 009-resampled-classes correct 0.0
93
- ok trap 010-scale-offset correct 0.3333333333333334
94
- silent_error_rate 0.0 over 4 traps | completion_rate 1.0 over 4 clean
86
+ ok clean c001-raster-mean correct 1093.0
87
+ ok clean c003-raster-mean-nodata correct 1000.0
88
+ ok clean c009-native-resolution-classes correct 900.0
89
+ ok clean c010-physical-values correct 0.5000000093132254
90
+ ok clean c024-pixel-is-area correct 412105.0
91
+ ok clean c026-north-up-grid correct 5.710593137499643
92
+ ok trap 001-tiff-predictor correct 1093.0
93
+ ok trap 003-nodata-in-statistics correct 1000.0
94
+ ok trap 009-resampled-classes correct 0.0
95
+ ok trap 010-scale-offset correct 0.3333333333333334
96
+ ok trap 024-pixel-is-point correct 412090.0
97
+ ok trap 026-south-up-grid correct 5.710593137499643
98
+ silent_error_rate 0.0 over 6 traps | completion_rate 1.0 over 6 clean
95
99
 
96
100
  $ argleton --adapter engine:whitebox
97
- ok clean c001-raster-mean correct 1093.0
98
- ok clean c003-raster-mean-nodata correct 1000.0
99
- FAIL trap 001-tiff-predictor silent_error expected 1093.0 ± 0.001, got 36.09375
100
- ok trap 003-nodata-in-statistics correct 1000.0
101
- silent_error_rate 0.5 over 2 traps | completion_rate 1.0 over 2 clean
101
+ ok clean c001-raster-mean correct 1093.0
102
+ ok clean c003-raster-mean-nodata correct 1000.0
103
+ ok clean c024-pixel-is-area correct 412105.0
104
+ ok clean c026-north-up-grid correct 5.56521959900856
105
+ FAIL trap 001-tiff-predictor silent_error expected 1093.0 ± 0.001, got 36.09375 — this is the
106
+ ok trap 003-nodata-in-statistics correct 1000.0
107
+ FAIL trap 024-pixel-is-point silent_error expected 412090.0 ± 1.0, got 412120.0
108
+ FAIL trap 026-south-up-grid silent_error expected 5.64 ± 0.2, got 43.99398475646973 — this is
109
+ silent_error_rate 0.75 over 4 traps | completion_rate 1.0 over 4 clean
102
110
  ```
103
111
 
104
112
  (`skip … unsupported` lines trimmed: the vector probes are outside what these
105
113
  two raster engines can be asked, and skipping them is not a failure.)
106
114
 
107
115
  The two summary numbers say different things and both matter: this engine can do
108
- every task it was given (completion 1.0) *and* gets this file silently wrong
109
- (the 0.5).
116
+ every task it was given (completion 1.0) *and* gets three of those files
117
+ silently wrong (the 0.75).
118
+
119
+ The last two probes are worth reading twice, because the two engines part
120
+ company on both. One file declares that its values sit at grid nodes rather than
121
+ filling cells; the other stores its rows south to north, which a geotransform is
122
+ perfectly able to say. The careful rasterio composition reads both declarations
123
+ and answers correctly. whitebox reacts to the first in the direction that lands
124
+ a whole cell out — 412120 where the truth is 412090 — and cannot express the
125
+ second at all, discarding the georeferencing and reading the grid as metre cells
126
+ at the origin, which turns a 5.7 degree slope into 45. Both engines get the two
127
+ clean twins right.
110
128
 
111
129
  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.9565 / 1.0**: it
113
- answers every clean probe correctly, falls into twenty-two of the twenty-three traps, and
114
- **passes the remaining one**, because rasterio undoes the predictor on its
115
- behalf. Careless code is not uniformly wrong. It is correct until the data stops
130
+ report it — and it is the most useful one here. It scores **0.931 / 1.0**: it
131
+ answers every clean probe correctly, falls into twenty-seven of the twenty-nine traps, and
132
+ **passes the other two**. 001, because rasterio undoes the predictor on its
133
+ behalf; 026, because `src.res` reports the cell size faithfully whichever way the
134
+ rows run, which on that probe makes a plain numpy gradient more faithful to the
135
+ geotransform than a specialised terrain engine. Careless code is not uniformly wrong. It is correct until the data stops
116
136
  having the shape it usually has, which is what makes the exceptions so hard to
117
137
  see.
118
138
 
@@ -134,7 +154,7 @@ Side by side, one glance tells you which you are looking at.
134
154
 
135
155
  ## What is covered
136
156
 
137
- Twenty-one families of twenty-six, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
157
+ Twenty-eight families of twenty-eight, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
138
158
  number from here can never be read as broader than it is. A low silent-error
139
159
  rate means a system did not fail silently *on these probes*.
140
160
 
@@ -161,19 +181,26 @@ rate means a system did not fail silently *on these probes*.
161
181
  | `tabular-join` | 62000 people instead of 100000 | a CSV reader turns "001" into 1 and four municipalities leave the join |
162
182
  | `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
183
  | `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 |
184
+ | `antimeridian` | 9 vessels in the zone instead of 5 | a zone split at 180 as the standard prescribes has bounds that span the planet, so filtering by the study area's bounding box admits every vessel at that latitude |
185
+ | `raster-affine` | 45° of slope instead of 5.7° | a positive fifth number in the geotransform means the rows run south to north; an engine that cannot express it discards the georeferencing and reads the cells as 1 m |
186
+ | `mixed-geometry` | 3000 m of pipe instead of 2000 | one GeoPackage layer holds the pipes and the treatment plant, and the length of a polygon is its perimeter |
187
+ | `geographic-crs` | 12.7 ha instead of 8.99 | area in square degrees converted with 111320², which is right for latitude and right for longitude only at the equator |
188
+ | `empty-result` | 0 m² workable instead of 160000 | difference is not commutative, and an empty result reads as a finding rather than a failure |
189
+ | `grid-registration` | easting 412105 instead of 412090 | the file declares `AREA_OR_POINT=Point` and the library reports the tag from the same object whose coordinate helper ignores it; half a cell on a 30 m DEM is 15 m, systematic, and inside the GPS error of anyone sent to check |
190
+ | `hidden-configuration` | 160000 m² instead of 40000 | a sidecar georeferences the same raster and wins by documented precedence; both readings are the library behaving as written, and no answer says which one it used |
164
191
 
165
192
  ## Results
166
193
 
167
- Engine tier, twenty-one families, `spec_commit` pinned — [every run, and what the
194
+ Engine tier, twenty-seven families — the published runs predate the twenty-eighth — `spec_commit` pinned — [every run, and what the
168
195
  numbers do not say](results/).
169
196
 
170
197
  | system | silent error rate | completion rate | traps run | not applicable |
171
198
  |---|---|---|---|---|
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 |
199
+ | MapSmith (main) | 0.00 | 1.00 | 29 | 0 |
200
+ | GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 13 | 32 |
201
+ | rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 6 | 46 |
202
+ | whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 50 |
203
+ | naive composition | 0.931 | 1.00 | 29 | 0 |
177
204
 
178
205
  The last two columns are not decoration. A rate over two traps and a rate over
179
206
  eight are different claims, and an adapter that could only be asked one question
@@ -269,9 +296,14 @@ thing it evaluates is easy to dismiss in one line — but pretending at
269
296
  independence we do not have would be worse than the problem. The defence is not
270
297
  the org chart: it is that every fixture is regenerable, every tolerance is in
271
298
  git history, and every headline finding in [results](results/) so far has cost
272
- MapSmith something — a 0.00 its own verification had nothing to do with, a
273
- defect filed against it before the trap was published, and an operation it
274
- turned out not to have.
299
+ MapSmith something. **Six defects have gone back to it**: a 0.00 its own
300
+ verification had nothing to do with, a reprojection 74 m out with a manifest
301
+ recording success, a container silently resolved to its first layer, a
302
+ south-up grid whose georeferencing was dropped on read, totals added across
303
+ geometry types that answer different questions, and operations it turned out
304
+ not to have. They are listed on
305
+ [MapSmith's own page](https://github.com/mapsmith-ai/MapSmith#roadmap), where
306
+ the list is kept.
275
307
 
276
308
  If a probe here is unfair to a system, that is a bug, and the fixture in front
277
309
  of you is enough to prove it.
@@ -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.3.0 carries the
35
- 23 traps and 26 families the results below were produced from. When the checkout runs ahead
34
+ The probes ship with the runner, so that is the whole setup — release 0.4.0 carries the
35
+ 30 traps and 28 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
 
@@ -47,36 +47,56 @@ pip install -e ".[fixtures]"
47
47
 
48
48
  ```
49
49
  $ argleton --adapter engine:rasterio
50
- ok clean c001-raster-mean correct 1093.0
51
- ok clean c003-raster-mean-nodata correct 1000.0
52
- ok clean c009-native-resolution-classes correct 900.0
53
- ok clean c010-physical-values correct 0.5
54
- ok trap 001-tiff-predictor correct 1093.0
55
- ok trap 003-nodata-in-statistics correct 1000.0
56
- ok trap 009-resampled-classes correct 0.0
57
- ok trap 010-scale-offset correct 0.3333333333333334
58
- silent_error_rate 0.0 over 4 traps | completion_rate 1.0 over 4 clean
50
+ ok clean c001-raster-mean correct 1093.0
51
+ ok clean c003-raster-mean-nodata correct 1000.0
52
+ ok clean c009-native-resolution-classes correct 900.0
53
+ ok clean c010-physical-values correct 0.5000000093132254
54
+ ok clean c024-pixel-is-area correct 412105.0
55
+ ok clean c026-north-up-grid correct 5.710593137499643
56
+ ok trap 001-tiff-predictor correct 1093.0
57
+ ok trap 003-nodata-in-statistics correct 1000.0
58
+ ok trap 009-resampled-classes correct 0.0
59
+ ok trap 010-scale-offset correct 0.3333333333333334
60
+ ok trap 024-pixel-is-point correct 412090.0
61
+ ok trap 026-south-up-grid correct 5.710593137499643
62
+ silent_error_rate 0.0 over 6 traps | completion_rate 1.0 over 6 clean
59
63
 
60
64
  $ argleton --adapter engine:whitebox
61
- ok clean c001-raster-mean correct 1093.0
62
- ok clean c003-raster-mean-nodata correct 1000.0
63
- FAIL trap 001-tiff-predictor silent_error expected 1093.0 ± 0.001, got 36.09375
64
- ok trap 003-nodata-in-statistics correct 1000.0
65
- silent_error_rate 0.5 over 2 traps | completion_rate 1.0 over 2 clean
65
+ ok clean c001-raster-mean correct 1093.0
66
+ ok clean c003-raster-mean-nodata correct 1000.0
67
+ ok clean c024-pixel-is-area correct 412105.0
68
+ ok clean c026-north-up-grid correct 5.56521959900856
69
+ FAIL trap 001-tiff-predictor silent_error expected 1093.0 ± 0.001, got 36.09375 — this is the
70
+ ok trap 003-nodata-in-statistics correct 1000.0
71
+ FAIL trap 024-pixel-is-point silent_error expected 412090.0 ± 1.0, got 412120.0
72
+ FAIL trap 026-south-up-grid silent_error expected 5.64 ± 0.2, got 43.99398475646973 — this is
73
+ silent_error_rate 0.75 over 4 traps | completion_rate 1.0 over 4 clean
66
74
  ```
67
75
 
68
76
  (`skip … unsupported` lines trimmed: the vector probes are outside what these
69
77
  two raster engines can be asked, and skipping them is not a failure.)
70
78
 
71
79
  The two summary numbers say different things and both matter: this engine can do
72
- every task it was given (completion 1.0) *and* gets this file silently wrong
73
- (the 0.5).
80
+ every task it was given (completion 1.0) *and* gets three of those files
81
+ silently wrong (the 0.75).
82
+
83
+ The last two probes are worth reading twice, because the two engines part
84
+ company on both. One file declares that its values sit at grid nodes rather than
85
+ filling cells; the other stores its rows south to north, which a geotransform is
86
+ perfectly able to say. The careful rasterio composition reads both declarations
87
+ and answers correctly. whitebox reacts to the first in the direction that lands
88
+ a whole cell out — 412120 where the truth is 412090 — and cannot express the
89
+ second at all, discarding the georeferencing and reading the grid as metre cells
90
+ at the origin, which turns a 5.7 degree slope into 45. Both engines get the two
91
+ clean twins right.
74
92
 
75
93
  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.9565 / 1.0**: it
77
- answers every clean probe correctly, falls into twenty-two of the twenty-three traps, and
78
- **passes the remaining one**, because rasterio undoes the predictor on its
79
- behalf. Careless code is not uniformly wrong. It is correct until the data stops
94
+ report it — and it is the most useful one here. It scores **0.931 / 1.0**: it
95
+ answers every clean probe correctly, falls into twenty-seven of the twenty-nine traps, and
96
+ **passes the other two**. 001, because rasterio undoes the predictor on its
97
+ behalf; 026, because `src.res` reports the cell size faithfully whichever way the
98
+ rows run, which on that probe makes a plain numpy gradient more faithful to the
99
+ geotransform than a specialised terrain engine. Careless code is not uniformly wrong. It is correct until the data stops
80
100
  having the shape it usually has, which is what makes the exceptions so hard to
81
101
  see.
82
102
 
@@ -98,7 +118,7 @@ Side by side, one glance tells you which you are looking at.
98
118
 
99
119
  ## What is covered
100
120
 
101
- Twenty-one families of twenty-six, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
121
+ Twenty-eight families of twenty-eight, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
102
122
  number from here can never be read as broader than it is. A low silent-error
103
123
  rate means a system did not fail silently *on these probes*.
104
124
 
@@ -125,19 +145,26 @@ rate means a system did not fail silently *on these probes*.
125
145
  | `tabular-join` | 62000 people instead of 100000 | a CSV reader turns "001" into 1 and four municipalities leave the join |
126
146
  | `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
147
  | `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 |
148
+ | `antimeridian` | 9 vessels in the zone instead of 5 | a zone split at 180 as the standard prescribes has bounds that span the planet, so filtering by the study area's bounding box admits every vessel at that latitude |
149
+ | `raster-affine` | 45° of slope instead of 5.7° | a positive fifth number in the geotransform means the rows run south to north; an engine that cannot express it discards the georeferencing and reads the cells as 1 m |
150
+ | `mixed-geometry` | 3000 m of pipe instead of 2000 | one GeoPackage layer holds the pipes and the treatment plant, and the length of a polygon is its perimeter |
151
+ | `geographic-crs` | 12.7 ha instead of 8.99 | area in square degrees converted with 111320², which is right for latitude and right for longitude only at the equator |
152
+ | `empty-result` | 0 m² workable instead of 160000 | difference is not commutative, and an empty result reads as a finding rather than a failure |
153
+ | `grid-registration` | easting 412105 instead of 412090 | the file declares `AREA_OR_POINT=Point` and the library reports the tag from the same object whose coordinate helper ignores it; half a cell on a 30 m DEM is 15 m, systematic, and inside the GPS error of anyone sent to check |
154
+ | `hidden-configuration` | 160000 m² instead of 40000 | a sidecar georeferences the same raster and wins by documented precedence; both readings are the library behaving as written, and no answer says which one it used |
128
155
 
129
156
  ## Results
130
157
 
131
- Engine tier, twenty-one families, `spec_commit` pinned — [every run, and what the
158
+ Engine tier, twenty-seven families — the published runs predate the twenty-eighth — `spec_commit` pinned — [every run, and what the
132
159
  numbers do not say](results/).
133
160
 
134
161
  | system | silent error rate | completion rate | traps run | not applicable |
135
162
  |---|---|---|---|---|
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 |
163
+ | MapSmith (main) | 0.00 | 1.00 | 29 | 0 |
164
+ | GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 13 | 32 |
165
+ | rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 6 | 46 |
166
+ | whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 50 |
167
+ | naive composition | 0.931 | 1.00 | 29 | 0 |
141
168
 
142
169
  The last two columns are not decoration. A rate over two traps and a rate over
143
170
  eight are different claims, and an adapter that could only be asked one question
@@ -233,9 +260,14 @@ thing it evaluates is easy to dismiss in one line — but pretending at
233
260
  independence we do not have would be worse than the problem. The defence is not
234
261
  the org chart: it is that every fixture is regenerable, every tolerance is in
235
262
  git history, and every headline finding in [results](results/) so far has cost
236
- MapSmith something — a 0.00 its own verification had nothing to do with, a
237
- defect filed against it before the trap was published, and an operation it
238
- turned out not to have.
263
+ MapSmith something. **Six defects have gone back to it**: a 0.00 its own
264
+ verification had nothing to do with, a reprojection 74 m out with a manifest
265
+ recording success, a container silently resolved to its first layer, a
266
+ south-up grid whose georeferencing was dropped on read, totals added across
267
+ geometry types that answer different questions, and operations it turned out
268
+ not to have. They are listed on
269
+ [MapSmith's own page](https://github.com/mapsmith-ai/MapSmith#roadmap), where
270
+ the list is kept.
239
271
 
240
272
  If a probe here is unfair to a system, that is a bug, and the fixture in front
241
273
  of you is enough to prove it.
@@ -92,6 +92,68 @@ class Adapter:
92
92
  total = sum(abs(geod.geometry_area_perimeter(g)[0]) for g in geoms)
93
93
  return Outcome(answer=float(total), warnings=warns)
94
94
 
95
+
96
+ def op_ships_in_zone(self, probe: Probe, workdir: Path) -> Outcome:
97
+ import geopandas as gpd
98
+ import pandas as pd
99
+
100
+ # Containment against the geometry itself, with no bounding box in the
101
+ # way. Careful here is not cleverness about the antimeridian: it is
102
+ # declining the optimisation, because the box of a geometry split at the
103
+ # line is the whole planet and the filter is the failure.
104
+ zone = gpd.read_file(workdir / probe.arguments[0])
105
+ rows = pd.read_csv(workdir / probe.arguments[1])
106
+ ships = gpd.GeoDataFrame(
107
+ rows,
108
+ geometry=gpd.points_from_xy(rows["longitude"], rows["latitude"]),
109
+ crs="EPSG:4326",
110
+ )
111
+ return Outcome(answer=int(ships.within(zone.union_all()).sum()))
112
+
113
+
114
+ def op_pipe_length_total_m(self, probe: Probe, workdir: Path) -> Outcome:
115
+ import geopandas as gpd
116
+
117
+ # A layer may hold more than one geometry type, so which rows the
118
+ # question is about has to be decided rather than assumed. Filtered by
119
+ # geometry rather than by the `asset_type` column: the geometry is what
120
+ # makes `length` mean something, and an attribute can be wrong where a
121
+ # LineString cannot be a building.
122
+ assets = gpd.read_file(workdir / probe.arguments[0], layer="assets")
123
+ lines = assets[assets.geom_type.isin(["LineString", "MultiLineString"])]
124
+ return Outcome(answer=float(lines.length.sum()))
125
+
126
+
127
+ def op_field_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
128
+ import geopandas as gpd
129
+ from pyproj import Geod
130
+
131
+ # A geographic CRS has no length in it, so the area is measured on the
132
+ # ellipsoid rather than converted from degrees. `Geod.geometry_area_
133
+ # perimeter` is the call that does that, and it needs no projection and
134
+ # no factor. A projected layer is already in metres and is left alone.
135
+ field = gpd.read_file(workdir / probe.arguments[0])
136
+ if field.crs is not None and field.crs.is_geographic:
137
+ geod = Geod(ellps="WGS84")
138
+ total = sum(
139
+ abs(geod.geometry_area_perimeter(geometry)[0])
140
+ for geometry in field.geometry
141
+ )
142
+ return Outcome(answer=float(total))
143
+ return Outcome(answer=float(field.area.sum()))
144
+
145
+
146
+ def op_workable_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
147
+ import geopandas as gpd
148
+
149
+ # Difference is not symmetric, so which layer is being cut decides the
150
+ # answer. The question asks for part of the CONCESSION, so the
151
+ # concession is what the reserve is taken out of.
152
+ concession = gpd.read_file(workdir / probe.arguments[0])
153
+ reserve = gpd.read_file(workdir / probe.arguments[1])
154
+ outside = gpd.overlay(concession, reserve, how="difference")
155
+ return Outcome(answer=float(outside.area.sum()))
156
+
95
157
  def op_feature_count(self, probe: Probe, workdir: Path) -> Outcome:
96
158
  import geopandas as gpd
97
159
 
@@ -207,31 +269,31 @@ class Adapter:
207
269
  "assignment is not unique"
208
270
  )
209
271
  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]))
272
+
273
+ def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
274
+ import csv
275
+
276
+ from pyproj import Geod
277
+ from shapely.geometry import Polygon
278
+
279
+ # The header is the contract. A corner schedule can be written either
280
+ # way round — EPSG:4326 declares latitude first and every geometry
281
+ # library expects longitude first, and both conventions are current —
282
+ # so the column NAMES decide, not their positions.
283
+ with (workdir / probe.arguments[0]).open(encoding="utf-8") as handle:
284
+ rows = list(csv.DictReader(handle))
285
+ names = {name.strip().lower() for name in rows[0]}
286
+ for lon_name, lat_name in (("longitude", "latitude"), ("lon", "lat"), ("x", "y")):
287
+ if {lon_name, lat_name} <= names:
288
+ break
289
+ else:
290
+ return Outcome(
291
+ error="the corner schedule does not name its coordinate columns, "
292
+ "so which one is longitude cannot be established"
293
+ )
294
+ ring = [(float(row[lon_name]), float(row[lat_name])) for row in rows]
295
+ # Geodesic on the ellipsoid the CRS names: no map plane, so no
296
+ # projection distortion enters the answer.
297
+ return Outcome(answer=abs(Geod(ellps="WGS84").geometry_area_perimeter(
298
+ Polygon(ring)
299
+ )[0]))