Repository navigation
Increment 36: GeoPackage output (--out X.gpkg) over sqlite3, QGIS-ready - #231
Merged
Merged
Conversation
…pkg (@architect) docs/increments/36-geopackage-output.md: the design. A writer under io/ (sqlite3 from the standard library, no GDAL/OGR/Fiona) chosen by the .gpkg suffix as .vtk and .ply are; layers triangles (PolygonZ with code, name, colour, z_min, z_max, z_mean, area_m2, slope_deg, R-tree indexed), lines (constraint edges with their property bits), land_cover (the clipped, cleaned coded polygons), land_cover_classes (the legend and shares), a run table holding the run record, and the stored QGIS style. Mirror of the reader io/geopackage.py, which reads the files back in the tests. Measured with docs/increments/36-probes/gpkg_write_probe.py on this worktree's .venv (AC power, SQLite 3.53.1): the quick check's Numedalslagen mesh (653,039 triangles) 4.0 s and 203 MB against 0.28 s and 29.2 MB for the binary .vtk, Ola's stopgap 5.1 s; the Romsdalen slope mesh (3,096,202 triangles) 21.5 s and 922 MB at 2.1 GB peak RSS, the stopgap 28.7 s and 4.1 GB. The R-tree is 60 % of the time (about 4.6 us per triangle); feeding it in Morton order was slower, a 256 MB page cache 25 % faster; building blobs and rows per batch of 100,000 cut peak RSS from 5.8 GB to 2.1 GB. Two writes of each mesh gave identical SHA-256; timestamps are fixed text. Left out: the per-triangle source-polygon ID (its own step, as the ROADMAP row says) and bulk R-tree loading (36b, for basin scale: about 8 minutes of index and a 30 GB file at 1e8 triangles). Estimate about 305 net production lines. Byte-identical .vtk/.ply pinned by test_refine_golden's GOLDEN_STRIDE_VTK and the quick check's mesh_sha256. ROADMAP.md: the row numbered 36, status designed, with the figures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/increments/36-probes/gpkg_write_probe.py formatted with ruff and its findings fixed (one statement per line, zip strict, context managers, a lowercase name); the long SQL and report lines carry a file-level E501 exemption. Re-run on the test tile after the change: the same figures (69,571 triangles, 0.40 s, 20.0 MB, second write identical). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…architect) docs/increments/36-geopackage-output.md, the GeoPackage output design: (1) T1 (section 8): the reader's decode_geometry skips the 32-byte envelope, so T1 now unpacks each blob's header itself (bytes 0-4 magic and flags, 4-8 srs_id, 8-40 the triangle's minx, maxx, miny, maxy) and names the oracle per mutant. F1 is written with srs_id 25833: 0 and -1 read the same in either byte order, 25833 read big-endian is -379322368 (checked with struct). The unclosed-ring and 3-point mutants are caught by GEOS ("Points of LinearRing do not form a closed linestring"), checked with shapely 2.2 on the F1 corners. (2) T2: written with batch=30_000, so F2's 69,571 triangles cross two boundaries and leave a partial batch of 9,571; the dropped-batch mutant shows as 60,000 rows against 69,571. Oracles named per mutant. (3) Section 4: land_cover_classes and run get fid INTEGER PRIMARY KEY (OGC 12-128 Requirement 119); run.name becomes TEXT UNIQUE. (4) Section 3: the one probe command behind every candidate figure (--rtree executemany --cache-mb 256 --stream --per-batch --vtk-time under /usr/bin/time -l), the stopgap and --twice flags, the mesh behind each row (test tile command, the quick check's numedalslagen case, the Romsdalen .vtk in rasputin_scratch), and each rejected variant's flag beside its figure. The probe's defaults are the rejected variants, said outright. Row 1 re-run with that command on this worktree: 0.40 s, R-tree 0.24 s, 20.0 MB, 160 MB peak RSS (the table says 0.23 s for the R-tree; single runs). No other figure re-measured. Reviewer's idea taken (section 5): write_land_cover states its envelope order, minx, maxx, miny, maxy, as flag 0b0011 declares; the stopgap's poly_layer reorders the bounds twice (its lines 229 and 118) and stores minx, miny, maxx, maxy, confirmed by reading those lines. Status line updated. Lesson recorded in the handback: a mutant's kill names the oracle that sees the mutated bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…h the CLI (@tester) docs/increments/36-geopackage-output.md section 8, red before any production code. 42 tests in two files, all red for the design's reasons and no other: - tests/python/test_io_geopackage_write.py (unit half, 24 red): 17 error on `ModuleNotFoundError: tin_engine.io.geopackage_write`, 5 on `AttributeError: tin_engine.crs has no attribute srs_entry`, 1 on the writer's source file not existing. F1 (4 vertices, 2 triangles, the 0-2 diagonal with the `river` bit, codes 312 and 0, srs_id 25833) in a `:memory:` connection: T1 round trip through `io/geopackage.py` plus the header bytes 0-40 unpacked here (GP, 0, 0b0011, `<i` 25833, `<4d` minx,maxx,miny,maxy) and the WKB bytes 40-53 (1, 1003, 1, 4); T2 on F2's arrays with batch=30_000 (69,571 rows, counts equal, each R-tree box within one float32 step of its triangle); T3 the eight attributes by hand, the unnamed code (NULL, #ff00ff), codes=None; T5 `write_run` rows in order, fid INTEGER PRIMARY KEY, name UNIQUE; T6 `write_lines` length, bits, no column for an unset property; T7 `srs_entry` (EPSG -> WKT1, no code -> 100000 WKT2, None/free text -> -1) and the SRS rows. - tests/python/test_cli_mesh_gpkg.py (CLI half, 18 red): every run with `--out X.gpkg` is refused today as "has no known suffix; use .vtk or .ply"; the two refusal tests fail on the missing sentence, not on the exit code. F2 = the Kartverket tile at --tolerance 10 (needs the codecs extra), meshed once to .gpkg with --record and --stats, once to .vtk, twice to one path; F3 = the bumpy DEM with CORINE rectangles 311/512. T3 figures against the .vtk arrays, land_cover layer and its envelope order; T4 SHA-256 and fixed timestamps; T5 run rows against --record and --stats, land_cover_classes sums, plan areas sum to the domain; T6 lines through feature_mask and the vocabulary; T7 EPSG:25833 and `--flat --crs "free text"` -> -1; T8 echo, refusals, replace, record/stats, fid-1 = .vtk index; T9 the QML (attr code, one category per palette code, palette colours). Oracles checked against the existing .vtk path before committing: F2 has 69,571 triangles and 383 edges, F3 586 triangles with codes {0, 311, 512} and plan areas summing to the domain within 1e-7 m^2. Red run: .venv/bin/python -m pytest tests/python/test_io_geopackage_write.py tests/python/test_cli_mesh_gpkg.py --no-cov -q -> 4 failed, 38 errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oper) The green step of increment 36 (docs/increments/36-geopackage-output.md, sections 4 and 5). The 42 tests of the red commit 87f41c3 pass (tests/python/test_io_geopackage_write.py, tests/python/test_cli_mesh_gpkg.py); no test file is touched. New module src_python/tin_engine/io/geopackage_write.py, the mirror of the reader io/geopackage.py, over an open sqlite3.Connection (opens no file, knows no path, no _core, no pyproj; the standard library's sqlite3 only): - polygon_blobs / line_blobs: one numpy structured array per batch; GP header, version 0, flags 0b0011 (little-endian, 32-byte XY envelope as minx, maxx, miny, maxy), srs_id, then ISO WKB (byte order 1, type 1003 PolygonZ with one ring of four points / 1002 LineStringZ). - create_geopackage: application_id GPKG, user_version 10300, the four gpkg_* tables, the reserved rows -1 and 0, EPSG:4326, and the run's SRS once (not again when it is one of those). - write_triangles(conn, vertices, triangles, codes, palette, srs_id, batch=100_000): rows and blobs built per batch from array slices, one transaction via executemany; code / land_cover / colour (NULL on every row with codes=None; a code the palette lacks: NULL name, #ff00ff), z_min, z_max, z_mean, area_m2 (half the cross product), slope_deg (atan2(|n_xy|, |n_z|)); registers the layer (POLYGON, z=1) and builds the R-tree after the rows. - build_rtree: the virtual table fed directly in batches under a 256 MB page cache (reset afterwards), registered as the write-only gpkg_rtree_index. - write_lines: LineStringZ per constraint edge, length_m, one 0/1 column per vocabulary property set on at least one edge, in bit order; registered, no R-tree. - write_land_cover: 2-D MultiPolygon per (code, polygon) through shapely.to_wkb(flavor="iso"), header envelope minx, maxx, miny, maxy. - write_classes: land_cover_classes (fid INTEGER PRIMARY KEY) from one GROUP BY over triangles, so its sums are the table's own. - write_run(conn, rows): run (fid INTEGER PRIMARY KEY, name TEXT UNIQUE, wording, value, number REAL), registered as attributes. - write_styles: QGIS layer_styles, a categorised fill on `code` with the palette's colours and labels, useAsDefault 1, for triangles and land_cover. - Every last_change and update_time is the fixed text 1970-01-01T00:00:00.000Z; nothing reads the clock, so two writes of one run are byte-identical. Two departures from section 5 of the design, each forced by an existing test: - SrsEntry (with UNDEFINED_SRS and CUSTOM_SRS_ID) lives in io/models.py, not in geopackage_write.py: crs.py (layer L1) may not import an io/ codec (L2), per tests/python/test_layering.py, and the writer may not import crs, per the red suite. io.models is L0 and both import it. - The CLI does not call sqlite3.connect: tests/python/test_io_geopackage.py (TestBoundaries.test_only_the_repository_connects) allows only io/repository.py to connect, so repository.py gains create_sqlite(path), the one writable connection (it removes an existing file first). Still red, by design of the test: three tests of tests/python/test_layering.py want table rows for the new module and the two new edges, which the table's own docstring says are a @tester commit: io.geopackage_write (L2) imports "features io.geopackage io.models"; crs adds io.models; cli adds io.geopackage_write. crs.py: srs_entry(crs) -> SrsEntry, the only place pyproj is asked for WKT: EPSG code -> srs_id = code with WKT1_GDAL; a CRS pyproj parses but cannot code -> 100000, organization rasputin, WKT2; None or text pyproj refuses -> the reserved -1 row. Never raises. feature_input.py: ClassMap.palette (None), "corine" on the three CORINE maps, so the writer looks the palette up by name in palettes.PALETTES. cli.py: MESH_SUFFIXES gains .gpkg; --out-edges with .gpkg is refused ("the edges are the lines layer of the GeoPackage") and --ascii with .gpkg is refused ("applies to .vtk and .ply; a GeoPackage is an SQLite file"), both before anything is meshed; _write_geopackage opens <out>.part, writes the layers in one `write: geopackage` phase, commits, closes and os.replace()s it onto --out, so an existing file is replaced, never appended to, and a failed run removes the .part. The .vtk and .ply branches are untouched (tests/python/test_refine_golden.py passes). Verified: the whole Python suite (7157 passed, 17 skipped; the three layering-table tests above are the only red), mypy strict, ruff check, ruff format --check, tools/check_prohibited_deps.py, tools/check_detria_boundary.py, tools/check_citations.py. No C++ file changed. The quick check's numedalslagen case written once on AC power: `write: geopackage` 3.98 s, 203.3 MB, 653,039 triangles, 67,240 lines, 22 land_cover rows (design: 4.0 s, 203 MB). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…unts them (@developer) No behaviour change: the templates hold no backslash. docs/increments/36-geopackage-output.md section 9 says the QML template is a raw literal and does not count; CLAUDE.md section 2 excludes the body of a raw literal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ester) tests/python/test_layering.py: the dependency map gains the edges increment 36 adds, each checked against the module's import lines before it was added (`grep -n "^from\|^import"` over the three files): - L2 `io.geopackage_write`: `features io.geopackage io.models` (new row). - L1 `crs`: adds `io.models` (SrsEntry, CUSTOM_SRS_ID, UNDEFINED_SRS). - L5 `cli`: adds `io.geopackage_write`. All three go down or sideways (L0 into L1, L2 into L2, L2 into L5); no new entry in UPWARD. The three rows @developer named were all correct; none was missing or wrong. `tests/python/test_layering.py`: 64 passed. Whole Python suite (`.venv/bin/python -m pytest -q --no-cov`): 7161 passed, 17 skipped, 1 failed, and the failure is not in a test file this PR touches: tests/python/test_io_repository.py::TestQ3OneModuleOpensFiles:: test_the_repository_opens_files_read_only finds `unlink:248` in io/repository.py, the `Path(path).unlink(missing_ok=True)` of the green commit c59246e's `create_sqlite`. Ola's Q3 ruling (increment 15a) lets repository.py open files read-only and write nothing outside CacheWriter; the design's section 5 had cli.py call sqlite3.connect itself, which tests/python/test_io_geopackage.py's TestBoundaries forbids. A production-code question, left to the handback; the Q3 guard is not loosened here. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing out (@tester) Kill record for the invariant-critical suite T1 to T6 (docs/increments/36-geopackage-output.md, section 8). Each mutant was planted in a scratch copy of 0943a7a made by `python3 tools/scratch_copy.py HEAD <scratchpad>/m36`, in src_python/tin_engine/io/geopackage_write.py, and the two 36 test files (tests/python/test_io_geopackage_write.py, tests/python/test_cli_mesh_gpkg.py; 42 tests) were run with `--no-cov`; the copy was restored and `touch`ed after each mutant and confirmed green before, between and after (42 passed), then removed. One row per mutant: where planted (function, line of 0943a7a) | the test and assertion that kills it (the design's oracle first) | result. M1 T1 header byte-order flag 0: `_records` l.143, flags 0b0011 -> 0b0010 | TestRoundTrip::test_each_triangle_reads_back_as_its_polygonz_in_triangle_order, the reader raises GeoPackageError "row 1: srs_id -379322368, the layer's is 25833" (the design's figure); also test_the_header_is_gp_version_0_..., `assert blob[3] == 0b0011`; 6 tests fail in all | KILLED M2 T1 envelope order swapped (minx, miny, maxx, maxy): `_envelopes` l.137 | TestRoundTrip::test_the_header_is_gp_version_0_little_endian_with_an_xy_envelope, `struct.unpack_from("<4d", blob, 8) == bounds(corners)` (the header unpack; the reader never sees it); also the gpkg_contents bounds, the R-tree boxes (T2: 69,571 boxes differ) and the lines header; 6 fail | KILLED M3 T1 WKB type 3 for 1003: `polygon_blobs` l.157 | TestRoundTrip::test_the_wkb_is_iso_polygonz_with_one_ring_of_four_points, `(geometry_type, rings, points) == (1003, 1, 4)` got (3, 1, 4); and the reader: GeoPackageError "unreadable WKB: Points of LinearRing do not form a closed linestring" (GEOS reads 4 points of 2 doubles out of the 12 written, so it never reaches the design's predicted flat Polygon with has_z false); 3 fail | KILLED M4 T1 ring not closed (fourth point = second corner): `polygon_blobs` l.159 | TestRoundTrip::test_each_triangle_reads_back_as_its_polygonz_in_triangle_order, GeoPackageError "Points of LinearRing do not form a closed linestring", and the CLI's TestCli::test_fid_minus_one_is_the_triangle_index_in_the_vtk_...; 2 fail | KILLED M5 T1 point count 3: `polygon_blobs` l.158, points 4 -> 3 | the reader as M4, and test_the_wkb_is_iso_polygonz_..., got (1003, 1, 3); 3 fail | KILLED M6 T2 R-tree id off by one: `build_rtree` l.229, range(start, ...) | TestIndexMatchesRows::test_every_fid_has_its_own_box_and_the_counts_agree, `assert_array_equal(stored[:, 0], np.arange(1, len(triangles) + 1))`; and TestRoundTrip::test_the_rtree_holds_each_triangle_box_under_its_fid; 2 fail | KILLED M7 T2 last partial batch dropped: `build_rtree` l.227, range(0, len(boxes) - len(boxes) % batch, batch) | TestIndexMatchesRows::test_every_fid_has_its_own_box_and_the_counts_agree, `rows == indexed == len(triangles)`: 69571 == 60000 (the design's figure, reached only because T2 writes with batch=30_000); at F1 the R-tree is empty, so test_the_rtree_holds_each_triangle_box_under_its_fid fails too; 2 fail | KILLED M8 T2 R-tree miny/maxx swapped: `build_rtree` l.230, columns [0, 2, 1, 3] | TestIndexMatchesRows::..., "69571 boxes differ from their triangle; fids [1 2 3 4 5]"; and the F1 R-tree test; 2 fail | KILLED M9 T3 plan area without the half: `write_triangles` l.275 | TestAttributes::test_code_name_colour_heights_area_and_slope, "plan area, with the half"; and in the CLI file TestRunTable (sum(area_m2) against --stats), TestLandCoverClasses (areas sum to the domain) and TestLandCover; 4 fail | KILLED M10 T6 a lines column for a property set on no edge: `write_lines` l.317, the `if name in used` filter dropped | TestLines::test_a_property_set_on_no_edge_gets_no_column, `attributes == {"length_m", "river", "road"}`; also test_the_diagonal_reads_back_... and the CLI's TestLines; 3 fail | KILLED 10 of 10 killed; no test added. T4 and T5 have no mutant named in the design. Red-step scaffolding removed from the red commit 87f41c3's files: - test_io_geopackage_write.py: the `gw` and `srs_entry` fixtures that imported the modules under test with `importlib.import_module` (so that collection succeeded before the module existed) are gone; the file imports `geopackage_write`, `srs_entry` and `SrsEntry` at the top like any other suite. The docstring paragraph explaining that mechanism, and the two "red" notes, are gone. The pins list stays as documentation. - test_cli_mesh_gpkg.py: the two "red" notes in the docstring. No throwaway tests, xfails or skips were planted for the red step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
….py (@developer) tests/python/test_io_repository.py::TestQ3OneModuleOpensFiles:: test_the_repository_opens_files_read_only found `unlink` in io/repository.py's create_sqlite, added at the green step because tests/python/test_io_geopackage.py::TestBoundaries lets only the repository module call sqlite3.connect. Both boundaries now hold: the repository is the one module that opens files, and outside CacheWriter it deletes and writes nothing beyond the connection it hands back. - io/repository.py: create_sqlite is `sqlite3.connect(path)` alone; the docstring says removing a stale file and renaming the finished one are the caller's. - cli.py _write_geopackage: `part.unlink(missing_ok=True)` before create_sqlite, so a stale `<target>.part` is never appended to; the rename onto the target after the write was already there. Verified in this worktree: full Python suite 7162 passed, 17 skipped (7161 passed, 1 failed at dddce8e); test_cli_mesh_gpkg.py, test_io_geopackage_write.py, test_io_geopackage.py and test_io_repository.py pass explicitly; mypy strict clean; ruff check and format clean; check_prohibited_deps, check_detria_boundary and check_citations OK. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lts recorded, with the Status line and ROADMAP row, by the main session Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ged, GeoPackage writer 3.93 s / +32 MB on Numedalslagen and 19.37 s / +188 MB on Romsdalen, ACCEPTED (@Perf) Evidence in docs/benchmarks/2026-10-11/gpkg-out/ (README.md with the gate table, the method and the comparison tables; the raw records and logs under quick/ and writer/; the two scripts that produced them under scripts/). Head e2470ae against its base 81a551b, AC power, caffeinated, nothing else running. The branch touches no C++, so the quick check only. (a) The worktree's stored baseline (39e3ae3, numpy 2.5.3) gave NO BASELINE against this venv's numpy 2.5.4, so the base 81a551b was measured with --tree in a detached scratch worktree first (--save-baseline), then head against it: NO CHANGE, exit 0; totals -4.3 % to +1.6 %; every mesh_sha256 equal, and equal to master's refreshed baseline (357808b). Against master's baseline Lagan is +37.7 % (features clip 6.34 -> 11.52 s): increment 35's saving, which this branch does not contain (the base has the same 11.58 s); the other cases are +2 to +10 %, which the base run of unchanged code also shows against the afternoon's record. The stored baseline file was restored to the branch's committed one afterwards. (b) /usr/bin/time -l on .venv/bin/rasputin mesh ... --out mesh.gpkg --stats - (the head run's Release _core installed in the venv first), one warm-up and 3 runs, medians: Numedalslagen write: geopackage 3.932 s (limit 5 s), file 203.3 MB; Romsdalen (tile 6901_3, --tolerance 10 --tolerance-slope 2 25 35) 19.368 s (limit 25 s), file 810.0 MB. Peak RSS of the whole command 2.77 GB and 2.91 GB against 2.73 GB and 2.78 GB for the same commands writing .vtk; the writer's own share, measured as the RSS growth inside its phase from a 0.1 s ps trace, +32 MB and +188 MB. Under 1 GB and 3 GB by either bound. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…AP row, by the main session Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lied, by the main session Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ite the GeoPackage (@tester) Code review round 1's blocker on increment 36 (GeoPackage output): a coded `--features` source whose only feature is a line leaves `cover` empty at cli.py:1146, and `write_land_cover(conn, [], ...)` reaches `_bounds` on a zero-row array (io/geopackage_write.py:360 -> :172), raising `ValueError: zero-size array to reduction operation minimum which has no identity`. The same arguments write `.vtk` with exit 0; probed directly through the CliRunner, since `invoke` swallows the exception and the test's failure message shows only exit 1 after the mesh summary. The test (T3, TestLandCover): a corine-mapped GeoJSON carrying one LineString (code 122) across the SQUARE domain on the bumpy DEM. Pins: - the run exits 0 and the `.vtk` twin matches (mesh_pair); - `land_cover` is absent, both as a table and in `gpkg_contents` -- the reading of section 4's "written only when a coded source ran" that the reviewer left open (absent or empty); the alternative is one empty MultiPolygon layer, which has no envelope to register; - `triangles.code` is 0 on every row (in no polygon) and equals the `.vtk`'s `land_cover_code` array; - `land_cover_classes` has exactly one row: code 0, its palette name and colour, `triangles` = the triangle count. Red: `pytest tests/python/test_cli_mesh_gpkg.py -k no_polygon_in_the_domain` fails at mesh_pair's exit-code assertion; the other 20 tests in the file pass. ruff check and ruff format --check clean on the file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…kage without a land_cover layer (@developer)
Code review round 1's blocker on increment 36 (GeoPackage output): a coded
--features source that leaves no polygon inside the domain gives an empty
`cover` list (cli.py, the `coded is not None` branch), and
`write_land_cover(conn, [], ...)` crashed in `_bounds` on a zero-row array
(io/geopackage_write.py), so the `.gpkg` run exited 1 where the `.vtk` run
of the same arguments exited 0.
The guard is in `_write_geopackage` (cli.py): `write_land_cover` runs only
when `cover` is non-empty, and `write_styles` gets "land_cover" in its layer
list only in that case, so no `layer_styles` row names a layer that does not
exist. `write_classes` still runs: `land_cover_classes` carries the code-0
row ("in no polygon") with the triangle count. The writer module is
untouched; `write_land_cover`'s contract stays one row per polygon, with the
caller deciding whether there is anything to draw.
Makes tests/python/test_cli_mesh_gpkg.py::TestLandCover::
test_a_coded_run_with_no_polygon_in_the_domain_writes_without_a_land_cover_layer
(red in 55cc3c3) pass; no test file touched. The two increment 36 files and
test_io_repository.py: 84 passed, 1 skipped (Ola's DTM10 archive absent).
mypy strict, ruff check, ruff format --check, check_prohibited_deps,
check_detria_boundary and check_citations green.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d_cover is written, by the main session Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ADMAP row, by the main session Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
skavhaug
enabled auto-merge
October 12, 2026 04:44
skavhaug
added a commit
that referenced
this pull request
Oct 12, 2026
…26a (@developer) PR #232 (increment 26a, land-cover class raster in the cache) went DIRTY in the merge queue after master moved. origin/master at a1aec50 brings two merged PRs: #231 (increment 36, GeoPackage output, merge commit fa6e485) and #230 (release hygiene, merge commit a1aec50). Merge commit, no rebase: the branch is pushed and the PR is open. One conflict, src_python/tin_engine/io/repository.py, in its module docstring only. Master wrote the paragraph "Increment 36 adds :func:`create_sqlite` ..." directly under the "Audit PR C" line; this branch wrote the paragraph "Increment 26a adds the class raster's reads ..." after a blank line. Resolved by keeping both, 36's first (it merged first), then 26a's, each as its side wrote it. The rest of the file auto-merged, and both sides' code is present: CachedObject.fetched, CacheRepository .load_class_window and .class_strips (26a), create_sqlite (36). ROADMAP.md, cli.py and io/models.py auto-merged. ROADMAP rows 26 and 36 are byte-identical to their own branch's text. In cli.py, 26a's --year option and check_values on --dem and 36's .gpkg output path are disjoint additions; in io/models.py, 26a's ClassWindow and 36's SrsEntry likewise. Gates, in this worktree's venv, after the merge: ruff check . all checks passed ruff format --check . 250 files already formatted mypy no issues in 68 source files python3 tools/check_prohibited_deps.py OK, 399 files clean python3 tools/check_citations.py exit 0; 60 at-risk lines listed, all of the form "this branch edits <file>" from the merged edits pytest tests/python/ 7236 passed, 17 skipped (environment skips only), coverage 99% (floor 85) No test broke. No C++ change on either side; _core not rebuilt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Increment 36,
docs/increments/36-geopackage-output.md. 378 net production lines (python3 tools/count_loc.py de9b6c03 HEAD).rasputin mesh --out X.gpkgwrites a GeoPackage through Python'ssqlite3only: no GDAL, OGR or Fiona (tools/check_prohibited_deps.pyclean).triangles(PolygonZ, eight attributes, R-tree index),lines,land_coverandland_cover_classes(only when a coded source leaves at least one polygon),run(the run record), and a stored QGISlayer_stylesrow so the file opens coloured.io/geopackage_write.py;.parthousekeeping stays incli.pysoio/repository.pykeeps its read-only boundary.Evidence: @Perf acceptance ACCEPTED (
docs/benchmarks/2026-10-11/gpkg-out/): Numedalslågen 3.93 s / 203 MB file, Romsdalen 19.4 s / 810 MB; writer RSS share +32 MB / +188 MB. 10 of 10 mutants killed. Code review rounds 1 and 2 recorded in the increment file, round 2 APPROVED; round 1 found the empty land-cover crash, fixed with a red test and a guard.🤖 Generated with Claude Code