Fix a dirty file¶
Available in: Python · Node · CLI · Browser
Mechanically repair a non-conforming AGS4 file — non-destructively — into a fresh handle.
# what this shows: .fix() mechanically repairs a dirty AGS4 file, non-destructively, into a NEW handle.
from laterite import read
# A dirty file: the data row is SHORT — fewer fields than the HEADING row (Rule 4).
# (AGS4 lines are CRLF-terminated; keep them so the only defect is the short row.)
dirty_text = (
'"GROUP","LOCA"\r\n'
'"HEADING","LOCA_ID","LOCA_TYPE","LOCA_NATE"\r\n'
'"UNIT","","",""\r\n'
'"TYPE","ID","PA","2DP"\r\n'
'"DATA","BH01","BH"\r\n' # <- only 3 fields, HEADING declares 4
)
dirty = read(text=dirty_text)
fixed = dirty.fix() # returns a NEW Ags4File; the original is untouched
kinds = [a["kind"] for a in fixed.fix_report.applied]
print(fixed.fix_report.applied[0]["kind"])
assert fixed is not dirty # non-destructive: a fresh handle
assert fixed.fix_report.applied[0]["kind"] == "pad_short_row"
assert "pad_short_row" in kinds # the short row was padded to width
.fix() runs the safe-repair pass over a file you've already
read and returns a new Ags4File — the original
handle is left untouched (fixed is not dirty). What it changed rides on
fixed.fix_report.applied, a list of {"kind": …} records: here the lone
DATA row had only 3 fields where the HEADING declared 4, so the fixer
padded it to width and recorded kind == "pad_short_row".
Read the kinds off the report to see every repair that was applied:
Because it returns the file, you chain straight on — re-.validate() the
fixed handle, or emit it.
// what this shows: .fix() mechanically repairs a dirty AGS4 file, non-destructively, into a NEW handle.
import { read } from "laterite";
import assert from "node:assert/strict";
// A dirty file: the data row is SHORT — fewer fields than the HEADING row (Rule 4).
// (AGS4 lines are CRLF-terminated; keep them so the only defect is the short row.)
const dirtyText =
'"GROUP","LOCA"\r\n' +
'"HEADING","LOCA_ID","LOCA_TYPE","LOCA_NATE"\r\n' +
'"UNIT","","",""\r\n' +
'"TYPE","ID","PA","2DP"\r\n' +
'"DATA","BH01","BH"\r\n'; // <- only 3 fields, HEADING declares 4
const dirty = read(undefined, { text: dirtyText });
const fixed = dirty.fix(); // returns a NEW Ags4File; the original is untouched
console.log(fixed.fixReport.applied[0].kind);
assert.notEqual(fixed, dirty); // non-destructive: a fresh handle
assert.equal(fixed.fixReport.applied[0].kind, "pad_short_row");
Same behaviour, camelCase: .fix() returns a new
Ags4File and the record of what changed rides on
.fixReport.applied ({kind, label, rule, line?, risk} per fix), with
what couldn't be mechanically repaired left in .fixReport.findings.
Write the repaired bytes out with fixed.save(path), or keep chaining.
The free-function form fix(path) returns the FixResult directly when
you don't need the handle. Pass { risky: true } to add the
intent-guessing repairs on top of the safe pass.
applied 1 fix(es) [pad_short_row] → repaired.ags
repaired.ags: 5 finding(s) remain (not mechanically fixable)
fix applies the same safe-repair pass and writes the repaired file
where --fix-out points (omit it for a sibling <file>.fixed.ags, or use
--in-place to overwrite the source). The summary names each applied kind
and counts what remains for a human — here the padded row was fixed, while
the missing TRAN/UNIT/TYPE groups can't be invented, so the exit code
stays 1. Add --risky for the intent-guessing tier.
Open the web app and drop your file on the Fix pane. It computes the available repairs, shows a before/after diff so you can review each one, and hands back the repaired file as a download — the same safe-fix engine as every other surface, running entirely in your browser.
These are safe repairs — width/whitespace/structural defects that have one unambiguous correction. Anything judgemental (a wrong value, a missing KEY) is left for you; fix will not invent data.
See also: CLI reference · Validate a delivery