10 · Batch files

Validate an entire SBF-80 payment batch

Validate an invented 80-character fixed-width payment file and reject the whole batch on any error.

Pythonmedium

What the hardened grader checks

Solutions

Reference

reference

Slices fixed columns, then checks counts and totals in a second pass.

Naive: accepted (3/3)Hardened: accepted (39/39)

Alternative

alt_valid

Regular-expression record decoding, a regex line splitter and a single streaming pass with incremental totals, versus column slicing and a two-pass validation and control phase.

Naive: accepted (3/3)Hardened: accepted (39/39)

wrong_crlf_slicing

Validates physical line width before normalizing CRLF.

Naive: accepted (3/3)Hardened: rejected (34/39)

wrong_ignores_hash_total

Does not check the trailer account hash total.

Naive: accepted (3/3)Hardened: rejected (31/39)

wrong_partial_accept

Accepts valid details from a batch containing invalid detail records.

Naive: accepted (3/3)Hardened: rejected (38/39)

Held-out solutions

Tests × solutions

Each row groups a test’s parameterized cases; any failing case marks the row as Fail. * marks a wrong solution’s declared must_fail test.

Hardened · 39 recorded cases · 13 test rows
TestStarterReferenceAlternativecrlf slicingignores hash totalpartial accept
CrlfFailPassPassFail *PassPass
Hash totalFailPassPassPassFail *Pass
Count mismatchFailPassPassPassPassPass
Atomic batch rejectionFailPassPassPassFailFail *
Multiple errors and line numbersFailPassPassPassFailPass
Invalid recordsFailPassPassPassPassPass
Missing and orderFailPassPassPassPassPass
Field formatsFailPassPassPassFailPass
Supported currencies preserve minor unitsPassPassPassPassPassPass
Mixed terminators and optional finalPassPassPassFailPassPass
Terminator blank and missing trailer errorsFailPassPassPassPassPass
Reference preserves leading spacesPassPassPassPassPassPass
Roundtrip and single digit corruptionFailPassPassFailPassPass
Naive grader
Naive · 3 recorded cases · 3 test rows
TestStarterReferenceAlternativecrlf slicingignores hash totalpartial accept
Valid lfPassPassPassPassPassPass
Ticket worked examplePassPassPassPassPassPass
Count mismatchFailPassPassPassPassPass

The ticket

Ticket: validate invented SBF-80 batch files atomically

batch.parser.parse(data:bytes)->BatchResult returns frozen dataclasses exported from that module: Payment(payee_account:str,amount_minor:int,reference:str), BatchError(line_no:int,code:str), BatchResult(accepted:bool,payments:list[Payment],errors:list[BatchError]).

SBF-80 is invented for this synthetic task. Each record is exactly 80 printable ASCII characters, followed by LF or CRLF. A final terminator is optional; mixed LF/CRLF is accepted. Strip the terminator before checking width and slicing. Interior blank lines are records and invalid. Bare CR, non-ASCII or control bytes are invalid_encoding. All positions below are 1-based inclusive.

RecordColumnsField
H1H
H2–13batch id: 12 uppercase ASCII letters/digits
H14–21creation date YYYYMMDD, valid calendar date
H22–24currency CAD, USD, EUR, GBP, JPY, KWD or BHD
H25–80spaces
D1D
D2–1312 ASCII digits, payee account (retain leading zeros)
D14–2512 ASCII digits, positive zero-padded integer minor units
D26–4520 printable chars, nonblank reference, right-space padded
D46–80spaces
T1T
T2–7detail count, 6 ASCII digits
T8–22control total of amounts, 15 ASCII digits
T23–32hash total, 10 ASCII digits
T33–80spaces

Exactly one header first, then one or more details, then exactly one trailer last. The count is the number of D records, including malformed D records. Control total and hash total use only fully valid detail records. Hash = sum(int(last 8 digits of each payee account)) mod 10**10. Money is in header-currency minor units: CAD/USD/EUR/GBP=2 dp, JPY=0, KWD/BHD=3. Do not convert. Returned references remove right padding only. Preserve detail order and account strings.

Any error rejects the whole batch: accepted=False and payments=[]. Report all detectable errors, deduplicated by (line_no,code), sorted by line_no then code. Continue checking other records. For a wrong-width or invalid-encoding record, report only that local error (no field errors), but structural/order and trailer comparisons can still be reported. Field errors are one code per record even if multiple fields are invalid. Trailer totals compare independently when its fields are valid, even if other records failed.

Codes: invalid_encoding, line_length, record_type, record_order, invalid_header, invalid_detail, invalid_trailer, count_mismatch, control_total_mismatch, hash_total_mismatch, missing_header, missing_trailer. H outside first position, T outside last, or D at first/last -> record_order. Unknown type -> record_type. A missing first H -> missing_header at line1; missing final T -> missing_trailer at the last line (line1 for empty input). An empty detail section -> count_mismatch on the trailer. Header/trailer field errors include invalid reserved spaces. No other codes.

Worked example (each record is padded with spaces to 80 characters; trailing spaces are not shown):

HSYNTHBATCH0120260115CAD
D000000012345000000010000INV-2026-001
D000000067890000000002550INV-2026-002
T0000020000000000125500000080235

The batch is accepted with two payments: Payment("000000012345", 10000, "INV-2026-001") and Payment("000000067890", 2550, "INV-2026-002"). Count 2; control total 10000 + 2550 = 12550; hash total 12345 + 67890 = 80235. Changing the trailer count to 000003 rejects the whole batch with BatchError(4, "count_mismatch") and payments == [].

Run from starter: python -m pytest tests -q. No real batch format, institution or customer is used.

Source