laterite
A Rust-backed AGS4 toolchain for the AGS4 geotechnical data format — validate, read as typed data, query, build, fix, diff, certify, and convert ↔ Excel — with a modern, born-typed polars API.
Coming from python-ags4?
laterite.compat is a faithful, faster stand-in for its AGS4, check, utils
and data modules — one token changes: from python_ags4 import AGS4
becomes from laterite.compat import AGS4, and your code keeps working.
Install
pip install laterite # base AGS4 (polars + duckdb, pyarrow-free)
pip install "laterite[compat]" # + pandas (python-ags4 drop-in) — still pyarrow-free
pip install "laterite[compat,pyarrow]" # + the optional pyarrow accelerator (or [all])
Requires Python ≥ 3.12. The wheel is abi3, so one binary covers 3.12 / 3.13 /
3.14. Installing it also puts the lat CLI on your PATH.
The [compat] drop-in is pyarrow-free and fast on its own; adding pyarrow
swaps the pandas step for pyarrow's to_pandas and unlocks the Arrow-backed
string dtype — an accelerator, never a requirement.
Use
import laterite
# Validate — errors + warnings by default (FYI is opt-in)
report = laterite.validate("delivery.ags")
report.is_valid
for rule, findings in report.by_rule().items():
print(rule, len(findings))
# Read born-typed columns: a 2DP heading is a float, a DT a datetime
ags = laterite.read("delivery.ags")
ags.groups # ['PROJ', 'LOCA', 'SAMP', …]
ags["LOCA"]["LOCA_GL"][0] # → 12.3 (a polars DataFrame per group)
# SQL across groups, no conversion step
ags.sql("SELECT loca_id, count(*) FROM SAMP GROUP BY 1")
# Repair a dirty file into a fresh handle, then keep working with it
fixed = ags.fix(risky=True) # pads short rows, transliterates non-ASCII, …
# Typed graph: PROJ → LOCA → SAMP → …
from laterite.ags4 import read_typed
for loca in read_typed("delivery.ags").locas:
print(loca.loca_id, loca.loca_gl)
# python-ags4 drop-in — swap the import, keep your code
from laterite import compat as AGS4
tables, headings = AGS4.AGS4_to_dataframe("delivery.ags")
AGS4.dataframe_to_AGS4(tables, headings, "round-trip.ags")
read returns born-typed polars frames by default (or pandas with
read(..., backend="pandas")) — both pyarrow-free, read back from a
Python-owned in-memory DuckDB engine.
More than a faster python-ags4
python-ags4 is the
reference Python library for AGS4 — validation plus pandas read/write — and
it inspired this project. laterite matches that surface and adds a toolchain
on top:
laterite |
python-ags4 |
|
|---|---|---|
| Validate — numbered AGS4 rules | ✅ | ✅ |
| Read → data frames | ✅ born-typed polars or pandas | pandas, all strings |
| Build / write AGS4 · Excel ↔ AGS4 | ✅ | ✅ |
Repair engine (fix) |
✅ | — |
| SQL across groups · revision diff | ✅ | — |
Validity certificates (.ags.idx) |
✅ | — |
| Transport — zstd compress + age encrypt | ✅ | — |
| Typed PROJ → LOCA → SAMP graph | ✅ | — |
| pyarrow required | no (optional accelerator) | via pandas' own deps |
Performance
Synthetic, spec-valid AGS4 from ags4-forge — the wide scaffold: 123
groups, realistic type mix, zero findings. macOS arm64, hot files, mean of 5
warm runs, python-ags4 1.2.0 vs laterite 0.12.0. Both agree on the findings.
Validation
| File | python-ags4 check_file |
laterite.validate |
speedup |
|---|---|---|---|
| 4.9 MB | 1.5 s | 41 ms | 36.8× |
| 25.0 MB | 3.7 s | 188 ms | 19.5× |
| 103.0 MB | 12.2 s | 781 ms | 15.7× |
| 276.5 MB | 31.4 s | 2.0 s | 15.9× |
| 551.6 MB | 67.1 s | 4.1 s | 16.4× |
Read → typed — the honest comparison for real work. python-ags4 needs
AGS4_to_dataframe + convert_to_numeric on every group to get numbers, and
still leaves dates as text; laterite.read is born-typed, dates included.
| File | python-ags4 + convert_to_numeric |
laterite.read |
speedup |
|---|---|---|---|
| 4.9 MB | 188 ms | 15 ms | 12.3× |
| 25.0 MB | 785 ms | 74 ms | 10.6× |
| 103.0 MB | 3.2 s | 300 ms | 10.7× |
| 276.5 MB | 8.7 s | 780 ms | 11.2× |
| 551.6 MB | 18.3 s | 1.6 s | 11.1× |
Read → strings — like for like, both returning pandas frames of text.
| File | python-ags4 AGS4_to_dataframe |
laterite.compat |
speedup |
|---|---|---|---|
| 4.9 MB | 141 ms | 38 ms | 3.7× |
| 25.0 MB | 672 ms | 148 ms | 4.6× |
| 103.0 MB | 2.7 s | 589 ms | 4.6× |
| 276.5 MB | 7.2 s | 1.5 s | 4.9× |
| 551.6 MB | 15.3 s | 3.0 s | 5.1× |
Validation — peak RSS
| File | python-ags4 check_file peak RSS |
laterite.validate peak RSS |
ratio |
|---|---|---|---|
| 4.9 MB | 171 MB | 95 MB | 1.80× |
| 25.0 MB | 343 MB | 175 MB | 1.96× |
| 103.0 MB | 1019 MB | 470 MB | 2.17× |
| 276.5 MB | 2664 MB | 1121 MB | 2.38× |
Read → typed — peak RSS
| File | python-ags4 + convert_to_numeric peak RSS |
laterite.read peak RSS |
ratio |
|---|---|---|---|
| 4.9 MB | 156 MB | 95 MB | 1.64× |
| 25.0 MB | 315 MB | 182 MB | 1.73× |
| 103.0 MB | 938 MB | 505 MB | 1.86× |
| 276.5 MB | 2446 MB | 1266 MB | 1.93× |
Read → strings — peak RSS
| File | python-ags4 AGS4_to_dataframe peak RSS |
laterite.compat peak RSS |
ratio |
|---|---|---|---|
| 4.9 MB | 149 MB | 198 MB | 0.75× |
| 25.0 MB | 314 MB | 388 MB | 0.81× |
| 103.0 MB | 918 MB | 1098 MB | 0.84× |
| 276.5 MB | 2205 MB | 2556 MB | 0.86× |
Peak RSS of one fresh process per cell; the ratio is python-ags4's peak over laterite's, so above 1 laterite holds less. The largest rung is time-only. Read → strings measured on the pyarrow accelerator hop.
The compat door holds more than python-ags4 — those ratios sit below 1 on
purpose: that is the cost of the pandas bridge, and publishing it is the
point. laterite.validate
and laterite.read are the recommended paths, on memory as on time.
The time ratio holds as files grow — the gap is a constant factor, not a head
start that erodes. Reproduce any of this with
uv run python tools/bench-vs-python-ags4.py in the repo: it generates the
rungs, verifies each against a pinned SHA-256 so a change to the generator can't
move the numbers unnoticed, and prints these exact tables.
Parity + clean-room
121 / 131 of python-ags4 1.2.0's own test suite passes through
laterite.compat (92 %); the 10 remaining are deliberate non-closures,
documented rule by rule. A weekly job compares the two public surfaces, so a
function added upstream can't quietly go missing here.
compat is a package mirroring upstream's own layout, so upstream's import
shapes work with one token changed:
from python_ags4 import AGS4 # becomes:
from laterite.compat import AGS4
from python_ags4.AGS4 import AGS4_to_dataframe # becomes:
from laterite.compat.AGS4 import AGS4_to_dataframe
AGS4, check, utils and data are real, distinct submodules — so code that
depends on module identity (monkeypatching check.X and expecting AGS4 not to
see it) behaves as it does upstream. from laterite import compat as AGS4 also
still works: the flat namespace is unchanged.
Two caveats before you swap. No top-level python_ags4 package ships, and
that is deliberate and permanent — inside this wheel it would collide with the
real distribution in site-packages. So the import token changes; the rest of
the line does not. And compat mirrors the library API only: python-ags4's
ags4_cli command is not mirrored, because laterite ships lat instead with
its own JSON shapes.
The validator is clean-room from the published AGS4 specification, not adapted from another library's source — python-ags4 is LGPL-3.0, and that separation is what lets laterite ship under MIT. Details: COMPAT.md · OBSERVATIONS.md.
One engine, every stack
laterite on PyPI is the Python surface of one Rust AGS4 engine, shared
across:
| Surface | Package | Get it |
|---|---|---|
| Python | laterite — PyPI |
pip install laterite |
| Node.js | laterite — npm |
npm install laterite |
| CLI | lat |
bundled with this wheel |
| DuckDB | laterite_ags4 — community extension |
INSTALL laterite_ags4 FROM community; |
| Browser | @laterite/ags4-wasm — npm, or the validator + data explorer |
npm install @laterite/ags4-wasm |
Scriptable output is byte-identical across all of them, so a CI gate and a notebook can't disagree.
Docs
Full documentation — Learn, Cookbook, Concepts, and the Python API reference — at https://docs.laterite.dev/.
Metadata
Release files for laterite 0.15.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| laterite-0.15.0.tar.gz | 852.3 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| laterite-0.15.0-cp312-abi3-win_amd64.whl | CPython 3.12 | abi3 | Windows x86-64 | Details |
| laterite-0.15.0-cp312-abi3-manylinux2014_x86_64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| laterite-0.15.0-cp312-abi3-manylinux2014_aarch64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| laterite-0.15.0-cp312-abi3-macosx_11_0_arm64.whl | CPython 3.12 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 28.4 MB
Release files / laterite-0.15.0.tar.gz
| Download URL | laterite-0.15.0.tar.gz |
|---|---|
| Size | 852.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a078d6a14953df40304eb3078ed14f87c14a4a3724f0cb02d35b1137a171510b
|
|
BLAKE2b-256 checksum How to use checksums |
cc9eaeb03435d562b3e25dc5999b2466cc63b82e5c785c118faa78396ed52603
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / laterite-0.15.0-cp312-abi3-win_amd64.whl
| Download URL | laterite-0.15.0-cp312-abi3-win_amd64.whl |
|---|---|
| Size | 7.4 MB |
| Tags | CPython 3.12 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
9c0ebc3e52aeb8e7940f935e1f040a21bf6a1230f35710da7c1d83cf6eab2ee4
|
|
BLAKE2b-256 checksum How to use checksums |
7f0d30df3495cf576391cc7a32f4a0c75348a6b49563679e563f8f4d1c0559dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / laterite-0.15.0-cp312-abi3-manylinux2014_x86_64.whl
| Download URL | laterite-0.15.0-cp312-abi3-manylinux2014_x86_64.whl |
|---|---|
| Size | 7.1 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f2530b4f46fbd8d9e429dfab4737a2128678dca972efe6746fb4788b8891edfb
|
|
BLAKE2b-256 checksum How to use checksums |
ec43a3f5d23fe8ff95d007246b5265df8eb00618013f898c3230dc7546952f98
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / laterite-0.15.0-cp312-abi3-manylinux2014_aarch64.whl
| Download URL | laterite-0.15.0-cp312-abi3-manylinux2014_aarch64.whl |
|---|---|
| Size | 6.7 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
da05cc1b0b5b098394a494c58c98c67a6f675d4835868635c1ee4547a6ab35ad
|
|
BLAKE2b-256 checksum How to use checksums |
01f6f303bb955d74a4660a4459dc4eb6aa283c940ff0eee9c9f8538658c1c14e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / laterite-0.15.0-cp312-abi3-macosx_11_0_arm64.whl
| Download URL | laterite-0.15.0-cp312-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 6.4 MB |
| Tags | CPython 3.12 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
2b55c432498fbec2c821d35b9cf528eadfd56448e9ba0894815c70d02c7b68f7
|
|
BLAKE2b-256 checksum How to use checksums |
dae5b14ab433f9e9d591d2bdd98cdb91acdfd00fa51b6433bf422df2453572bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency log