Skip to content

Validate a delivery

Available in: Python · Node · CLI · Browser

Run the numbered AGS Format Rules over a transfer file and get a verdict — in-process from Python or Node, in SQL from DuckDB, at the shell with lat, or drag-and-drop in the browser.

import laterite

# Validate a file. read(...).validate() returns the Ags4File (so it chains);
# the Report is on the .report property.
ags = laterite.read("examples/sample_site.ags").validate()
r = ags.report
print(
    f"is_valid={r.is_valid} count={r.count} "
    f"dict_version={r.dict_version!r} resolution={r.resolution!r}"
)

assert r.is_valid is True
assert r.count == 0
assert r.dict_version == "4.1.1"  # auto-selected from TRAN_AGS
is_valid=True count=0 dict_version='4.1.1' resolution='exact'

read(path).validate() runs the rule engine and returns the Ags4File, so it chains; the verdict rides on .report. is_valid and count are the headline. dict_version and resolution tell you which edition the rules came from — both read straight off the file: the edition is taken from TRAN_AGS, and resolution='exact' means that edition was matched on the nose (otherwise laterite falls back to the nearest dictionary it ships). You never pass an edition.

Because .validate() hands the file back, keep going on the same handle — query it, slice a group, or emit it — without re-reading from disk.

import { validate } from "laterite";
import assert from "node:assert/strict";

// Validate a file. The Report carries the verdict plus which edition the rules
// came from — read straight off the file's TRAN_AGS; you never pass an edition.
const report = validate("examples/sample_site.ags");
console.log(
  `isValid=${report.isValid} count=${report.count} ` +
    `dictVersion=${report.dictVersion} resolution=${report.resolution}`,
);

assert.equal(report.isValid, true);
assert.equal(report.count, 0);
assert.equal(report.dictVersion, "4.1.1"); // auto-selected from TRAN_AGS
assert.equal(report.resolution, "exact");
isValid=true count=0 dictVersion=4.1.1 resolution=exact

Same engine, camelCase verbs: the free validate() returns a Report whose isValid / count / dictVersion / resolution mirror the Python properties one-for-one, and report.toJson() is byte-identical to lat validate --json, so a Node service and a CI gate can share downstream tooling. For the chaining style, read(path).validate() returns the Ags4File with the verdict on .report — exactly the Python shape.

The laterite_ags4 DuckDB extension is a read-only reader — it has no validate function. Run the numbered rules with the lat CLI or the laterite library (the Python/Node tabs above); the extension then reads the file and can consume an externally-minted .ags.idx (from lat certify) for fast single-group reads. See the DuckDB function reference.

A clean file prints one line and exits 0:

lat validate examples/sample_site.ags
examples/sample_site.ags: clean (0 findings)

A file with problems prints a count line, then a findings table — one row per violation — and exits 1. File-level findings (a missing group) carry no line, shown as -:

lat validate delivery.ags
delivery.ags: 4 finding(s)
┌──────┬──────┬───────┬──────────────────────────────────────────────────────┐
│ Rule ┆ Line ┆ Group ┆ Description                                          │
╞══════╪══════╪═══════╪══════════════════════════════════════════════════════╡
│ 14   ┆ -    ┆ TRAN  ┆ TRAN group not found.                                │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 15   ┆ -    ┆ UNIT  ┆ UNIT group not found.                                │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 17   ┆ -    ┆ TYPE  ┆ TYPE group not found.                                │
├╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 4    ┆ 5    ┆ PROJ  ┆ DATA row field count does not match the HEADING row. │
└──────┴──────┴───────┴──────────────────────────────────────────────────────┘

Add --json for machine-readable findings keyed by rule. The exit code is unchanged — 0 for an empty findings, 1 when it's populated — so a CI gate can branch without parsing stdout:

lat validate delivery.ags --json
{
  "file": "delivery.ags",
  "findings": {
    "AGS Format Rule 14": [
      {
        "line": null,
        "group": "TRAN",
        "desc": "TRAN group not found."
      }
    ],
    "AGS Format Rule 15": [
      {
        "line": null,
        "group": "UNIT",
        "desc": "UNIT group not found."
      }
    ],
    "AGS Format Rule 17": [
      {
        "line": null,
        "group": "TYPE",
        "desc": "TYPE group not found."
      }
    ],
    "AGS Format Rule 4": [
      {
        "line": 5,
        "group": "PROJ",
        "desc": "DATA row field count does not match the HEADING row."
      }
    ]
  }
}

Open the web app and drag your file into the Validate pane. The same numbered-rules engine runs compiled to WebAssembly, entirely client-side — your file never leaves your machine, which makes it safe for confidential ground-investigation data. Findings arrive grouped by rule with the resolved edition and severity tiers, and you can carry the same file straight into the Fix or Explore panes.

Every validating door runs the same rule engine and picks the dictionary edition from the file's TRAN_AGS — the Python/Node report and the CLI's --json carry identical findings. Reach for the CLI in a build gate (exit 1 fails the build); reach for .validate() when you want to keep working with the parsed file in the same process.

See also: lat CLI · Certify a file