argleton 0.1.0__py3-none-any.whl

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 (124) hide show
  1. adapters/__init__.py +0 -0
  2. adapters/engine_geopandas.py +176 -0
  3. adapters/engine_naive.py +220 -0
  4. adapters/engine_rasterio.py +78 -0
  5. adapters/engine_whitebox.py +34 -0
  6. adapters/gis_mcp.py +192 -0
  7. adapters/mapsmith.py +496 -0
  8. argleton/__init__.py +3 -0
  9. argleton/model.py +202 -0
  10. argleton/probes/clean/c001-raster-mean/build.py +36 -0
  11. argleton/probes/clean/c001-raster-mean/probe.toml +25 -0
  12. argleton/probes/clean/c002-projected-area/build.py +44 -0
  13. argleton/probes/clean/c002-projected-area/probe.toml +25 -0
  14. argleton/probes/clean/c003-raster-mean-nodata/build.py +35 -0
  15. argleton/probes/clean/c003-raster-mean-nodata/probe.toml +26 -0
  16. argleton/probes/clean/c004-points-in-polygon/build.py +60 -0
  17. argleton/probes/clean/c004-points-in-polygon/probe.toml +29 -0
  18. argleton/probes/clean/c005-polygon-area/build.py +38 -0
  19. argleton/probes/clean/c005-polygon-area/probe.toml +26 -0
  20. argleton/probes/clean/c006-named-layer/build.py +35 -0
  21. argleton/probes/clean/c006-named-layer/probe.toml +26 -0
  22. argleton/probes/clean/c007-distance-in-metres/build.py +42 -0
  23. argleton/probes/clean/c007-distance-in-metres/probe.toml +27 -0
  24. argleton/probes/clean/c008-equal-area-crs/build.py +39 -0
  25. argleton/probes/clean/c008-equal-area-crs/probe.toml +37 -0
  26. argleton/probes/clean/c009-native-resolution-classes/build.py +51 -0
  27. argleton/probes/clean/c009-native-resolution-classes/probe.toml +31 -0
  28. argleton/probes/clean/c010-physical-values/build.py +49 -0
  29. argleton/probes/clean/c010-physical-values/probe.toml +30 -0
  30. argleton/probes/clean/c011-solid-parcel/build.py +52 -0
  31. argleton/probes/clean/c011-solid-parcel/probe.toml +28 -0
  32. argleton/probes/clean/c012-disjoint-concessions/build.py +56 -0
  33. argleton/probes/clean/c012-disjoint-concessions/probe.toml +28 -0
  34. argleton/probes/clean/c013-flat-pipeline/build.py +42 -0
  35. argleton/probes/clean/c013-flat-pipeline/probe.toml +28 -0
  36. argleton/probes/clean/c014-convex-parcel/build.py +70 -0
  37. argleton/probes/clean/c014-convex-parcel/probe.toml +28 -0
  38. argleton/probes/clean/c015-wells-off-the-seam/build.py +73 -0
  39. argleton/probes/clean/c015-wells-off-the-seam/probe.toml +28 -0
  40. argleton/probes/clean/c016-decimal-degrees/build.py +29 -0
  41. argleton/probes/clean/c016-decimal-degrees/probe.toml +27 -0
  42. argleton/probes/clean/c017-equal-populations/build.py +60 -0
  43. argleton/probes/clean/c017-equal-populations/probe.toml +28 -0
  44. argleton/probes/clean/c018-plain-keys/build.py +59 -0
  45. argleton/probes/clean/c018-plain-keys/probe.toml +28 -0
  46. argleton/probes/clean/c019-fully-contained/build.py +64 -0
  47. argleton/probes/clean/c019-fully-contained/probe.toml +28 -0
  48. argleton/probes/clean/c020-one-owner-each/build.py +55 -0
  49. argleton/probes/clean/c020-one-owner-each/probe.toml +27 -0
  50. argleton/probes/clean/c021-greenwich-variant/build.py +41 -0
  51. argleton/probes/clean/c021-greenwich-variant/probe.toml +41 -0
  52. argleton/probes/schema/probe.schema.json +138 -0
  53. argleton/probes/schema/result.schema.json +103 -0
  54. argleton/probes/traps/001-tiff-predictor/README.md +75 -0
  55. argleton/probes/traps/001-tiff-predictor/build.py +58 -0
  56. argleton/probes/traps/001-tiff-predictor/probe.toml +57 -0
  57. argleton/probes/traps/002-feet-as-metres/README.md +79 -0
  58. argleton/probes/traps/002-feet-as-metres/build.py +44 -0
  59. argleton/probes/traps/002-feet-as-metres/probe.toml +70 -0
  60. argleton/probes/traps/003-nodata-in-statistics/README.md +61 -0
  61. argleton/probes/traps/003-nodata-in-statistics/build.py +51 -0
  62. argleton/probes/traps/003-nodata-in-statistics/probe.toml +62 -0
  63. argleton/probes/traps/004-mismatched-crs-join/README.md +65 -0
  64. argleton/probes/traps/004-mismatched-crs-join/build.py +65 -0
  65. argleton/probes/traps/004-mismatched-crs-join/probe.toml +75 -0
  66. argleton/probes/traps/005-bowtie-area/README.md +63 -0
  67. argleton/probes/traps/005-bowtie-area/build.py +41 -0
  68. argleton/probes/traps/005-bowtie-area/probe.toml +73 -0
  69. argleton/probes/traps/006-default-layer/README.md +65 -0
  70. argleton/probes/traps/006-default-layer/build.py +55 -0
  71. argleton/probes/traps/006-default-layer/probe.toml +63 -0
  72. argleton/probes/traps/007-buffer-in-degrees/README.md +57 -0
  73. argleton/probes/traps/007-buffer-in-degrees/build.py +42 -0
  74. argleton/probes/traps/007-buffer-in-degrees/probe.toml +64 -0
  75. argleton/probes/traps/008-web-mercator-area/README.md +55 -0
  76. argleton/probes/traps/008-web-mercator-area/build.py +39 -0
  77. argleton/probes/traps/008-web-mercator-area/probe.toml +68 -0
  78. argleton/probes/traps/009-resampled-classes/README.md +88 -0
  79. argleton/probes/traps/009-resampled-classes/build.py +46 -0
  80. argleton/probes/traps/009-resampled-classes/probe.toml +76 -0
  81. argleton/probes/traps/010-scale-offset/README.md +76 -0
  82. argleton/probes/traps/010-scale-offset/build.py +55 -0
  83. argleton/probes/traps/010-scale-offset/probe.toml +69 -0
  84. argleton/probes/traps/011-polygon-holes/README.md +47 -0
  85. argleton/probes/traps/011-polygon-holes/build.py +55 -0
  86. argleton/probes/traps/011-polygon-holes/probe.toml +59 -0
  87. argleton/probes/traps/012-double-counting/README.md +40 -0
  88. argleton/probes/traps/012-double-counting/build.py +58 -0
  89. argleton/probes/traps/012-double-counting/probe.toml +52 -0
  90. argleton/probes/traps/013-z-dimension/README.md +42 -0
  91. argleton/probes/traps/013-z-dimension/build.py +43 -0
  92. argleton/probes/traps/013-z-dimension/probe.toml +53 -0
  93. argleton/probes/traps/014-centroid-outside/README.md +42 -0
  94. argleton/probes/traps/014-centroid-outside/build.py +72 -0
  95. argleton/probes/traps/014-centroid-outside/probe.toml +59 -0
  96. argleton/probes/traps/015-boundary-semantics/README.md +41 -0
  97. argleton/probes/traps/015-boundary-semantics/build.py +74 -0
  98. argleton/probes/traps/015-boundary-semantics/probe.toml +57 -0
  99. argleton/probes/traps/016-coordinate-parsing/README.md +40 -0
  100. argleton/probes/traps/016-coordinate-parsing/build.py +36 -0
  101. argleton/probes/traps/016-coordinate-parsing/probe.toml +52 -0
  102. argleton/probes/traps/017-aggregation-weighting/README.md +42 -0
  103. argleton/probes/traps/017-aggregation-weighting/build.py +62 -0
  104. argleton/probes/traps/017-aggregation-weighting/probe.toml +53 -0
  105. argleton/probes/traps/018-join-key-typing/README.md +41 -0
  106. argleton/probes/traps/018-join-key-typing/build.py +61 -0
  107. argleton/probes/traps/018-join-key-typing/probe.toml +53 -0
  108. argleton/probes/traps/019-partial-overlap/README.md +43 -0
  109. argleton/probes/traps/019-partial-overlap/build.py +66 -0
  110. argleton/probes/traps/019-partial-overlap/probe.toml +53 -0
  111. argleton/probes/traps/020-join-cardinality/README.md +42 -0
  112. argleton/probes/traps/020-join-cardinality/build.py +60 -0
  113. argleton/probes/traps/020-join-cardinality/probe.toml +51 -0
  114. argleton/probes/traps/021-ballpark-datum/README.md +100 -0
  115. argleton/probes/traps/021-ballpark-datum/build.py +44 -0
  116. argleton/probes/traps/021-ballpark-datum/probe.toml +92 -0
  117. argleton/published.py +49 -0
  118. argleton/run.py +170 -0
  119. argleton/score.py +150 -0
  120. argleton-0.1.0.dist-info/METADATA +275 -0
  121. argleton-0.1.0.dist-info/RECORD +124 -0
  122. argleton-0.1.0.dist-info/WHEEL +4 -0
  123. argleton-0.1.0.dist-info/entry_points.txt +2 -0
  124. argleton-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,27 @@
1
+ id = "c007-distance-in-metres"
2
+ population = "clean"
3
+ family = "implicit-parameter-units"
4
+ title = "Count of wells within a distance, layer already in metres"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "count_within_distance(wells.gpkg, target=W-1, distance_meters=500)"
9
+ prompt = """
10
+ How many other wells in wells.gpkg lie within 500 meters of the well W-1?
11
+ Answer with the count."""
12
+
13
+ [truth]
14
+ kind = "count"
15
+ value = 5
16
+ tolerance = 0
17
+ derivation = """
18
+ By construction in EPSG:32633 (metres), by Pythagoras on the offsets from W-1:
19
+ 300, 400, ~354, ~446 and 460 metres for the five inside wells; every other
20
+ well lies 2,500 m away or more. The farthest included (460) and the nearest
21
+ excluded (2,500) leave no boundary case: the count is 5. The trap and this
22
+ control differ only in whether the layer's unit is the one the question uses."""
23
+
24
+ [provenance]
25
+ source = "control for 007-buffer-in-degrees"
26
+ found_by = "mapsmith"
27
+ date = "2026-08-25"
@@ -0,0 +1,39 @@
1
+ """Build the clean twin of 008: the same question where the plane tells the truth.
2
+
3
+ The parcel is an exact 100 x 100 rectangle in EPSG:3035 (Lambert Azimuthal
4
+ Equal-Area over Europe), at the same latitude as the trap. LAEA preserves area
5
+ by construction, so here — and only here — the planar shoelace IS the ground
6
+ area, and the composition that falls into 008 answers this one correctly.
7
+ Without this twin, a failure on 008 could not be told apart from "the system
8
+ cannot measure an area at all".
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ # GeoPackage stamps the write time into `gpkg_contents.last_change`, so two
18
+ # builds of the same data differ byte for byte unless the clock is pinned.
19
+ os.environ.setdefault("OGR_CURRENT_DATE", "2026-08-25T00:00:00.000Z")
20
+
21
+ import geopandas as gpd
22
+ from shapely.geometry import box
23
+
24
+ CRS = "EPSG:3035"
25
+ X0, Y0, SIDE = 4_532_000.0, 2_050_000.0, 100.0 # ~12.52E 41.52N
26
+
27
+
28
+ def main(destination: Path) -> int:
29
+ destination.mkdir(parents=True, exist_ok=True)
30
+ gpd.GeoDataFrame(
31
+ {"parcel_id": ["P-1"]},
32
+ geometry=[box(X0, Y0, X0 + SIDE, Y0 + SIDE)],
33
+ crs=CRS,
34
+ ).to_file(destination / "parcel.gpkg", layer="parcel", driver="GPKG")
35
+ return 0
36
+
37
+
38
+ if __name__ == "__main__":
39
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,37 @@
1
+ id = "c008-equal-area-crs"
2
+ population = "clean"
3
+ family = "projection-distortion"
4
+ title = "The same area question where the plane tells the truth"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "ground_area_m2(parcel.gpkg)"
9
+ prompt = """
10
+ parcel.gpkg holds one land parcel in central Italy. How large is the parcel
11
+ on the ground, in square metres? Answer with the number."""
12
+
13
+ [truth]
14
+ kind = "scalar"
15
+ value = 10000.0
16
+ tolerance = 8.0
17
+ derivation = """
18
+ The parcel is an exact 100 x 100 rectangle in EPSG:3035, a Lambert Azimuthal
19
+ Equal-Area projection: equal-area by construction, so its planar area IS the
20
+ ground (ellipsoidal) area — 10000 m2 exactly. Checked against the WGS84
21
+ geodesic area of the same footprint: 10000.000004 m2. A UTM 33N planar
22
+ measurement, 210 km from the central meridian, reads 10002.55 m2 (scale
23
+ factor squared); the tolerance of 8 admits it. Every correct method lands
24
+ within 3 m2 of the truth."""
25
+
26
+ [provenance]
27
+ source = """
28
+ Clean twin of 008-web-mercator-area: same question, same file layout, same
29
+ feature count, an equally ordinary projected CRS in metres. The one thing
30
+ that changed is that this projection preserves area. A system that answers
31
+ this and fails 008 mishandles distortion, not areas; a system that fails
32
+ both cannot answer the question, which is a different (and lesser) finding."""
33
+ found_by = "mapsmith"
34
+ date = "2026-08-25"
35
+
36
+ [provenance.engine_versions]
37
+ geopandas = "1.1.4/shapely 2.1.2"
@@ -0,0 +1,51 @@
1
+ """Clean twin of 009: the same question where the urban class is really there.
2
+
3
+ Same legend, same CRS, already on the 15 m grid the question asks for, and
4
+ four cells genuinely coded urban. A system that can read a class map and add
5
+ up the area of one class answers 900; the trap's answer is zero. Without this
6
+ twin, a zero on the trap could not be told apart from "cannot do the task".
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import rasterio
16
+ from rasterio.transform import from_origin
17
+
18
+ CRS = "EPSG:32633"
19
+ CELL = 15.0
20
+ # Four genuinely urban cells (2), the rest forest and water.
21
+ CLASSES = np.array(
22
+ [
23
+ [1, 1, 2, 3],
24
+ [1, 1, 2, 3],
25
+ [1, 1, 2, 3],
26
+ [1, 1, 2, 3],
27
+ ],
28
+ dtype="uint8",
29
+ )
30
+
31
+
32
+ def main(destination: Path) -> int:
33
+ destination.mkdir(parents=True, exist_ok=True)
34
+ with rasterio.open(
35
+ destination / "landcover.tif",
36
+ "w",
37
+ driver="GTiff",
38
+ height=CLASSES.shape[0],
39
+ width=CLASSES.shape[1],
40
+ count=1,
41
+ dtype="uint8",
42
+ crs=CRS,
43
+ transform=from_origin(500000, 4600000, CELL, CELL),
44
+ ) as ds:
45
+ ds.write(CLASSES, 1)
46
+ ds.update_tags(legend="1=forest, 2=urban, 3=water")
47
+ return 0
48
+
49
+
50
+ if __name__ == "__main__":
51
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,31 @@
1
+ id = "c009-native-resolution-classes"
2
+ population = "clean"
3
+ family = "categorical-resampling"
4
+ title = "The same urban-area question, with urban actually present"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "class_area_m2(landcover.tif, resolution=15, class=2)"
9
+ prompt = """
10
+ landcover.tif is a land-cover map with the legend 1=forest, 2=urban, 3=water.
11
+ Put it on a 15 metre grid so it lines up with another dataset, and report how
12
+ many square metres of URBAN (class 2) it contains. Answer with the number."""
13
+
14
+ [truth]
15
+ kind = "scalar"
16
+ value = 900.0
17
+ tolerance = 0.0
18
+ derivation = """
19
+ The file is already on the 15 m grid the question asks for, so no resampling
20
+ is needed and none can change the answer: the third column of a 4x4 grid is
21
+ coded 2, which is 4 cells of 15 x 15 = 225 m2 each, so 900 m2 exactly.
22
+ Deliberately the same number the trap's naive answer produces — what separates
23
+ the two probes is not the quantity but whether the class is in the data."""
24
+
25
+ [provenance]
26
+ source = "control for 009-resampled-classes"
27
+ found_by = "mapsmith"
28
+ date = "2026-08-26"
29
+
30
+ [provenance.engine_versions]
31
+ rasterio = "1.5.1"
@@ -0,0 +1,49 @@
1
+ """Clean twin of 010: the same question on a scene already in physical units.
2
+
3
+ Same two bands, same question, no scale and no offset to apply — the values in
4
+ the file are the reflectances. Red 0.2 and NIR 0.6 give an NDVI of 0.5, a
5
+ different number from the trap's so the two cannot be confused in a log.
6
+
7
+ A system that answers this and misses the trap can compute NDVI and ignores
8
+ declared scaling; a system that misses both cannot compute NDVI at all, which
9
+ is a different and lesser finding.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ import numpy as np
18
+ import rasterio
19
+ from rasterio.transform import from_origin
20
+
21
+ CRS = "EPSG:32633"
22
+ CELL = 10.0
23
+ RED, NIR = 0.2, 0.6
24
+
25
+
26
+ def main(destination: Path) -> int:
27
+ destination.mkdir(parents=True, exist_ok=True)
28
+ shape = (4, 4)
29
+ with rasterio.open(
30
+ destination / "scene.tif",
31
+ "w",
32
+ driver="GTiff",
33
+ height=shape[0],
34
+ width=shape[1],
35
+ count=2,
36
+ dtype="float32",
37
+ crs=CRS,
38
+ transform=from_origin(500000, 4600000, CELL, CELL),
39
+ ) as ds:
40
+ ds.write(np.full(shape, RED, dtype="float32"), 1)
41
+ ds.write(np.full(shape, NIR, dtype="float32"), 2)
42
+ ds.set_band_description(1, "red")
43
+ ds.set_band_description(2, "nir")
44
+ ds.update_tags(note="surface reflectance, already in physical units")
45
+ return 0
46
+
47
+
48
+ if __name__ == "__main__":
49
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,30 @@
1
+ id = "c010-physical-values"
2
+ population = "clean"
3
+ family = "radiometric-scale-offset"
4
+ title = "The same index on a scene already in physical units"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "ndvi_mean(scene.tif, red_band=1, nir_band=2)"
9
+ prompt = """
10
+ scene.tif holds two bands of an optical satellite scene: band 1 is red, band 2
11
+ is near-infrared. Compute the mean NDVI, (NIR - RED) / (NIR + RED). Answer with
12
+ the number."""
13
+
14
+ [truth]
15
+ kind = "scalar"
16
+ value = 0.5
17
+ tolerance = 0.0001
18
+ derivation = """
19
+ The bands are float reflectances with no scale and no offset declared: red 0.2,
20
+ NIR 0.6 in every pixel. NDVI = (0.6 - 0.2) / (0.6 + 0.2) = 0.4 / 0.8 = 0.5
21
+ exactly. Deliberately a different number from the trap's, so the two cannot be
22
+ confused when reading a run."""
23
+
24
+ [provenance]
25
+ source = "control for 010-scale-offset"
26
+ found_by = "mapsmith"
27
+ date = "2026-08-26"
28
+
29
+ [provenance.engine_versions]
30
+ rasterio = "1.5.1"
@@ -0,0 +1,52 @@
1
+ """Clean twin of 011: the same parcel with no courtyard.
2
+
3
+ 100 x 100 with no inner ring: 10000 m2, and no subtraction to get wrong.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # GeoPackage stamps the write time into `gpkg_contents.last_change`, so two
13
+ # builds of the same data differ byte for byte unless the clock is pinned.
14
+ os.environ.setdefault("OGR_CURRENT_DATE", "2026-08-26T00:00:00.000Z")
15
+
16
+ import geopandas as gpd
17
+ from shapely.geometry import Polygon
18
+
19
+ CRS = "EPSG:32632"
20
+ # The fixture sits where it says it is: EPSG:32632 coordinates near Milan
21
+ # rather than at (0, 0), which in that CRS is 500 km west of the central
22
+ # meridian and on the equator — a point in the Atlantic where the projection
23
+ # distorts area by 27%. A translation changes no area, no length and no count,
24
+ # so every truth in this family is unaffected; what changes is that the file
25
+ # stops implying something false.
26
+ EAST, NORTH = 500_000.0, 5_030_000.0
27
+
28
+
29
+
30
+
31
+ def rect(x0: float, y0: float, x1: float, y1: float) -> Polygon:
32
+ """A rectangle in local metres, placed at the fixture's real-world origin."""
33
+ return Polygon([
34
+ (EAST + x0, NORTH + y0),
35
+ (EAST + x1, NORTH + y0),
36
+ (EAST + x1, NORTH + y1),
37
+ (EAST + x0, NORTH + y1),
38
+ ])
39
+
40
+
41
+ def main(destination: Path) -> int:
42
+ destination.mkdir(parents=True, exist_ok=True)
43
+ gpd.GeoDataFrame(
44
+ {"parcel_id": ["P-1"]},
45
+ geometry=[rect(0, 0, 100, 100)],
46
+ crs=CRS,
47
+ ).to_file(destination / "parcel.gpkg", layer="parcel", driver="GPKG")
48
+ return 0
49
+
50
+
51
+ if __name__ == "__main__":
52
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,28 @@
1
+ id = "c011-solid-parcel"
2
+ population = "clean"
3
+ family = "polygon-holes"
4
+ title = "The same parcel with no courtyard"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "buildable_area_m2(parcel.gpkg)"
9
+ prompt = """
10
+ parcel.gpkg holds one parcel with an internal courtyard. How many square
11
+ metres can be built on? Answer with the number."""
12
+
13
+ [truth]
14
+ kind = "scalar"
15
+ value = 10000.0
16
+ tolerance = 0.01
17
+ derivation = """
18
+ A 100 x 100 square with no inner ring: 10000 m2 exactly, and no subtraction
19
+ to get wrong. A system that answers this and misses the trap handles rings,
20
+ not areas."""
21
+
22
+ [provenance]
23
+ source = "control for 011-polygon-holes"
24
+ found_by = "mapsmith"
25
+ date = "2026-08-26"
26
+
27
+ [provenance.engine_versions]
28
+ geopandas = "1.1.4/shapely 2.1.2"
@@ -0,0 +1,56 @@
1
+ """Clean twin of 012: two concessions that do not touch.
2
+
3
+ Same two 10000 m2 squares, 60 m apart. Sum and union agree at 20000, so a
4
+ system that adds areas answers this one correctly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # GeoPackage stamps the write time into `gpkg_contents.last_change`, so two
14
+ # builds of the same data differ byte for byte unless the clock is pinned.
15
+ os.environ.setdefault("OGR_CURRENT_DATE", "2026-08-26T00:00:00.000Z")
16
+
17
+ import geopandas as gpd
18
+ from shapely.geometry import Polygon
19
+
20
+ CRS = "EPSG:32632"
21
+ # The fixture sits where it says it is: EPSG:32632 coordinates near Milan
22
+ # rather than at (0, 0), which in that CRS is 500 km west of the central
23
+ # meridian and on the equator — a point in the Atlantic where the projection
24
+ # distorts area by 27%. A translation changes no area, no length and no count,
25
+ # so every truth in this family is unaffected; what changes is that the file
26
+ # stops implying something false.
27
+ EAST, NORTH = 500_000.0, 5_030_000.0
28
+
29
+
30
+
31
+
32
+ def rect(x0: float, y0: float, x1: float, y1: float) -> Polygon:
33
+ """A rectangle in local metres, placed at the fixture's real-world origin."""
34
+ return Polygon([
35
+ (EAST + x0, NORTH + y0),
36
+ (EAST + x1, NORTH + y0),
37
+ (EAST + x1, NORTH + y1),
38
+ (EAST + x0, NORTH + y1),
39
+ ])
40
+
41
+
42
+ def main(destination: Path) -> int:
43
+ destination.mkdir(parents=True, exist_ok=True)
44
+ gpd.GeoDataFrame(
45
+ {"licence": ["L-1", "L-2"], "area_m2": [10000.0, 10000.0]},
46
+ geometry=[
47
+ rect(0, 0, 100, 100),
48
+ rect(160, 0, 260, 100),
49
+ ],
50
+ crs=CRS,
51
+ ).to_file(destination / "overlap.gpkg", layer="licences", driver="GPKG")
52
+ return 0
53
+
54
+
55
+ if __name__ == "__main__":
56
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,28 @@
1
+ id = "c012-disjoint-concessions"
2
+ population = "clean"
3
+ family = "double-counting"
4
+ title = "The same question where the concessions do not overlap"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "total_ground_area_m2(overlap.gpkg)"
9
+ prompt = """
10
+ The two concessions in overlap.gpkg overlap in part. How much ground do they
11
+ cover in total, in square metres? Answer with the number."""
12
+
13
+ [truth]
14
+ kind = "scalar"
15
+ value = 20000.0
16
+ tolerance = 0.01
17
+ derivation = """
18
+ The same two 10000 m2 squares, 60 m apart and disjoint. Sum and union agree
19
+ at 20000, so a system that adds areas answers this one correctly — which is
20
+ what makes the habit survive."""
21
+
22
+ [provenance]
23
+ source = "control for 012-double-counting"
24
+ found_by = "mapsmith"
25
+ date = "2026-08-26"
26
+
27
+ [provenance.engine_versions]
28
+ geopandas = "1.1.4/shapely 2.1.2"
@@ -0,0 +1,42 @@
1
+ """Clean twin of 013: the same pipeline on flat ground.
2
+
3
+ From (0, 0, 0) to (500, 0, 0): 3D and 2D length agree at 500 m, so a system
4
+ that ignores Z answers this one correctly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # GeoPackage stamps the write time into `gpkg_contents.last_change`, so two
14
+ # builds of the same data differ byte for byte unless the clock is pinned.
15
+ os.environ.setdefault("OGR_CURRENT_DATE", "2026-08-26T00:00:00.000Z")
16
+
17
+ import geopandas as gpd
18
+ from shapely.geometry import LineString
19
+
20
+ CRS = "EPSG:32632"
21
+ # The fixture sits where it says it is: EPSG:32632 coordinates near Milan
22
+ # rather than at (0, 0), which in that CRS is 500 km west of the central
23
+ # meridian and on the equator — a point in the Atlantic where the projection
24
+ # distorts area by 27%. A translation changes no area, no length and no count,
25
+ # so every truth in this family is unaffected; what changes is that the file
26
+ # stops implying something false.
27
+ EAST, NORTH = 500_000.0, 5_030_000.0
28
+
29
+
30
+
31
+ def main(destination: Path) -> int:
32
+ destination.mkdir(parents=True, exist_ok=True)
33
+ gpd.GeoDataFrame(
34
+ {"pipe_id": ["PL-1"]},
35
+ geometry=[LineString([(EAST + 0, NORTH + 0, 0), (EAST + 500, NORTH + 0, 0)])],
36
+ crs=CRS,
37
+ ).to_file(destination / "pipeline.gpkg", layer="pipeline", driver="GPKG")
38
+ return 0
39
+
40
+
41
+ if __name__ == "__main__":
42
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,28 @@
1
+ id = "c013-flat-pipeline"
2
+ population = "clean"
3
+ family = "z-dimension"
4
+ title = "The same pipe on flat ground"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "pipe_length_m(pipeline.gpkg)"
9
+ prompt = """
10
+ pipeline.gpkg holds a pipe with elevations. How many metres of pipe are
11
+ needed? Answer with the number."""
12
+
13
+ [truth]
14
+ kind = "scalar"
15
+ value = 500.0
16
+ tolerance = 0.01
17
+ derivation = """
18
+ From (0, 0, 0) to (500, 0, 0): the pipe is flat, so 3D and 2D length agree at
19
+ 500 m. A system that ignores Z answers this one correctly, which is how the
20
+ habit stays invisible."""
21
+
22
+ [provenance]
23
+ source = "control for 013-z-dimension"
24
+ found_by = "mapsmith"
25
+ date = "2026-08-26"
26
+
27
+ [provenance.engine_versions]
28
+ geopandas = "1.1.4/shapely 2.1.2"
@@ -0,0 +1,70 @@
1
+ """Clean twin of 014: a rectangular parcel, centroid inside it.
2
+
3
+ Same question, same districts, but the parcel is convex — so the centroid
4
+ falls where the parcel is, and locating by centroid gives the right district.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # GeoPackage stamps the write time into `gpkg_contents.last_change`, so two
14
+ # builds of the same data differ byte for byte unless the clock is pinned.
15
+ os.environ.setdefault("OGR_CURRENT_DATE", "2026-08-26T00:00:00.000Z")
16
+
17
+ import geopandas as gpd
18
+ from shapely.geometry import Polygon
19
+
20
+ CRS = "EPSG:32632"
21
+ # The fixture sits where it says it is: EPSG:32632 coordinates near Milan
22
+ # rather than at (0, 0), which in that CRS is 500 km west of the central
23
+ # meridian and on the equator — a point in the Atlantic where the projection
24
+ # distorts area by 27%. A translation changes no area, no length and no count,
25
+ # so every truth in this family is unaffected; what changes is that the file
26
+ # stops implying something false.
27
+ EAST, NORTH = 500_000.0, 5_030_000.0
28
+
29
+
30
+
31
+
32
+ def rect(x0: float, y0: float, x1: float, y1: float) -> Polygon:
33
+ """A rectangle in local metres, placed at the fixture's real-world origin."""
34
+ return Polygon([
35
+ (EAST + x0, NORTH + y0),
36
+ (EAST + x1, NORTH + y0),
37
+ (EAST + x1, NORTH + y1),
38
+ (EAST + x0, NORTH + y1),
39
+ ])
40
+
41
+
42
+
43
+ def l_shape() -> Polygon:
44
+ """The L: a 100x20 bar plus a 20x80 upright, 3600 m2, centroid in the notch."""
45
+ return Polygon([
46
+ (EAST + 0, NORTH + 0), (EAST + 100, NORTH + 0), (EAST + 100, NORTH + 20),
47
+ (EAST + 20, NORTH + 20), (EAST + 20, NORTH + 100), (EAST + 0, NORTH + 100),
48
+ ])
49
+
50
+
51
+ def main(destination: Path) -> int:
52
+ destination.mkdir(parents=True, exist_ok=True)
53
+ gpd.GeoDataFrame(
54
+ {"parcel_id": ["P-1"]},
55
+ geometry=[rect(0, 0, 100, 20)],
56
+ crs=CRS,
57
+ ).to_file(destination / "parcel.gpkg", layer="parcel", driver="GPKG")
58
+ gpd.GeoDataFrame(
59
+ {"district": ["A", "B"]},
60
+ geometry=[
61
+ l_shape(),
62
+ rect(20, 20, 100, 100),
63
+ ],
64
+ crs=CRS,
65
+ ).to_file(destination / "districts.gpkg", layer="districts", driver="GPKG")
66
+ return 0
67
+
68
+
69
+ if __name__ == "__main__":
70
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))
@@ -0,0 +1,28 @@
1
+ id = "c014-convex-parcel"
2
+ population = "clean"
3
+ family = "centroid-outside"
4
+ title = "The same question for a parcel whose centroid is inside it"
5
+ surface = ["engine", "agent"]
6
+
7
+ [task]
8
+ call = "district_of_parcel(parcel.gpkg, districts.gpkg)"
9
+ prompt = """
10
+ Which district is the depot in? Its parcel is in parcel.gpkg and the
11
+ districts are in districts.gpkg. Answer with the district name."""
12
+
13
+ [truth]
14
+ kind = "scalar"
15
+ value = "A"
16
+ tolerance = 0
17
+ derivation = """
18
+ The parcel is the 100 x 20 rectangle, entirely inside district A, and convex —
19
+ so its centroid at (50, 10) is on it and in A. Centroid and containment agree,
20
+ and a system that uses the centroid answers this one correctly."""
21
+
22
+ [provenance]
23
+ source = "control for 014-centroid-outside"
24
+ found_by = "mapsmith"
25
+ date = "2026-08-26"
26
+
27
+ [provenance.engine_versions]
28
+ geopandas = "1.1.4/shapely 2.1.2"
@@ -0,0 +1,73 @@
1
+ """Clean twin of 015: the same partition, no well on the boundary.
2
+
3
+ The four seam wells move one metre west. Strict containment and boundary
4
+ inclusion agree at 12, so a system that uses `within` answers this one
5
+ correctly.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ # GeoPackage stamps the write time into `gpkg_contents.last_change`, so two
15
+ # builds of the same data differ byte for byte unless the clock is pinned.
16
+ os.environ.setdefault("OGR_CURRENT_DATE", "2026-08-26T00:00:00.000Z")
17
+
18
+ import geopandas as gpd
19
+ from shapely.geometry import Point, Polygon
20
+
21
+ CRS = "EPSG:32632"
22
+ # The fixture sits where it says it is: EPSG:32632 coordinates near Milan
23
+ # rather than at (0, 0), which in that CRS is 500 km west of the central
24
+ # meridian and on the equator — a point in the Atlantic where the projection
25
+ # distorts area by 27%. A translation changes no area, no length and no count,
26
+ # so every truth in this family is unaffected; what changes is that the file
27
+ # stops implying something false.
28
+ EAST, NORTH = 500_000.0, 5_030_000.0
29
+
30
+
31
+
32
+
33
+ def rect(x0: float, y0: float, x1: float, y1: float) -> Polygon:
34
+ """A rectangle in local metres, placed at the fixture's real-world origin."""
35
+ return Polygon([
36
+ (EAST + x0, NORTH + y0),
37
+ (EAST + x1, NORTH + y0),
38
+ (EAST + x1, NORTH + y1),
39
+ (EAST + x0, NORTH + y1),
40
+ ])
41
+
42
+
43
+
44
+ def at(x: float, y: float) -> Point:
45
+ """A point in local metres, placed at the fixture's real-world origin."""
46
+ return Point(EAST + x, NORTH + y)
47
+
48
+
49
+ def main(destination: Path) -> int:
50
+ destination.mkdir(parents=True, exist_ok=True)
51
+ wells = [
52
+ (10, 10), (20, 30), (30, 60), (40, 80),
53
+ (60, 10), (70, 30), (80, 60), (90, 80),
54
+ (49, 20), (49, 40), (49, 60), (49, 80),
55
+ ]
56
+ gpd.GeoDataFrame(
57
+ {"well_id": [f"W-{i + 1}" for i in range(len(wells))]},
58
+ geometry=[at(x, y) for x, y in wells],
59
+ crs=CRS,
60
+ ).to_file(destination / "wells.gpkg", layer="wells", driver="GPKG")
61
+ gpd.GeoDataFrame(
62
+ {"district": ["west", "east"]},
63
+ geometry=[
64
+ rect(0, 0, 50, 100),
65
+ rect(50, 0, 100, 100),
66
+ ],
67
+ crs=CRS,
68
+ ).to_file(destination / "districts.gpkg", layer="districts", driver="GPKG")
69
+ return 0
70
+
71
+
72
+ if __name__ == "__main__":
73
+ raise SystemExit(main(Path(sys.argv[1] if len(sys.argv) > 1 else ".")))