Repository: moov-io/ach
Pull request #1357 (2024-01-20 03:20:39)

Use constructor to create IATEntryDetails when parsing IAT batches
- Use `NewIATEntryDetail()` (rather than `new(IATEntryDetail)`) to create IAT entry details when parsing a file; this sets a couple important defaults including `Category`, which is currently blank for all parsed IAT forwards.
- Strip extra whitespace by default when parsing account numbers of IAT entries, to match the behavior of parsed account numbers for other entry details (we can leave this change off if it feels potentially disruptive.)


Patch:
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index dcca4e67..fd7977ad 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -24,12 +24,12 @@ jobs:
         uses: actions/checkout@v3
 
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v3
+        uses: github/codeql-action/init@v2
         with:
           languages: go
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v3
+        uses: github/codeql-action/analyze@v2
         env:
           GOOS: js
           GOARCH: wasm
diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
index 1d97b82f..62722c21 100644
--- a/.github/workflows/fuzz.yml
+++ b/.github/workflows/fuzz.yml
@@ -15,7 +15,7 @@ jobs:
 
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
@@ -25,7 +25,7 @@ jobs:
       with:
         fetch-depth: 0
 
-    - uses: actions/cache@v4
+    - uses: actions/cache@v3
       with:
         path: |
           ~/.cache/go-build
@@ -45,7 +45,7 @@ jobs:
 
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
@@ -55,7 +55,7 @@ jobs:
       with:
         fetch-depth: 0
 
-    - uses: actions/cache@v4
+    - uses: actions/cache@v3
       with:
         path: |
           ~/.cache/go-build
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 5e95961e..df611f4c 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -19,7 +19,7 @@ jobs:
         os: [ubuntu-latest, macos-latest, windows-latest]
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
@@ -49,7 +49,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4b2de8ac..943d7596 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,7 +17,7 @@ jobs:
         os: [ubuntu-latest, macos-latest, windows-latest]
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
@@ -65,7 +65,7 @@ jobs:
         os: [ubuntu-latest, macos-latest, windows-latest]
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
@@ -163,7 +163,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v5
+      uses: actions/setup-go@v4
       with:
         go-version: stable
       id: go
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index ffc205e5..8fd73679 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -58,7 +58,7 @@ jobs:
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v3
         with:
           name: SARIF file
           path: results.sarif
@@ -66,6 +66,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@v3
+        uses: github/codeql-action/upload-sarif@v2
         with:
           sarif_file: results.sarif
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09d002aa..90e7528b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,70 +1,3 @@
-## v1.33.6 (Released 2024-01-19)
-
-IMPROVEMENTS
-
-- api: fixup EntryDetail required fields
-- api: label required fields on EntryDetail
-- feat: Addenda99.SetOriginalEntryReturnDate
-- feat: Parse Addenda99.AddendaInformation
-- feat: improve addenda99 for dishonored returns
-- feat: add CheckingReturnNOCCredit to list of valid ATX txn codes (#21)
-- fix BatchHeader.EffectiveEntryDateField (#20)
-- fix: Don't truncate Addenda99.AddendaInformation
-- fix: trim "R" in dishonored and contested setter
-- test: verify "!" is allowed in isAlphanumeric
-
-BUILD
-
-- build(deps): bump actions/download-artifact from 1 to 4
-- build(deps): bump actions/upload-artifact from 1 to 4
-- build(deps): bump github.com/moov-io/base from 0.48.2 to 0.48.3 (#1341)
-- build(deps): bump github.com/aws/aws-lambda-go from 1.41.0 to 1.43.0 (#1346)
-- build(deps): bump github/codeql-action from 2 to 3 (#1342)
-- build(deps): bump github.com/prometheus/client_golang
-- build(deps): bump github.com/aws/aws-lambda-go from 1.43.0 to 1.44.0
-- build(deps): bump golang.org/x/net from 0.19.0 to 0.20.0
-- build(deps): bump github.com/moov-io/base from 0.48.3 to 0.48.5
-- build(deps): bump actions/cache from 3 to 4
-- build(deps): bump github.com/aws/aws-lambda-go from 1.44.0 to 1.45.0
-- chore(deps): update dependency jekyll-feed to v0.17.0 (#1337)
-
-## v1.33.4 (Released 2023-12-07)
-
-IMPROVEMENTS
-
-- all: fix errors found by nilaway
-- api: update amount to be long to allow handling of amounts larger than 21M (#1335)
-- webui: better overflow layout
-
-BUILD
-
-- build(deps): bump actions/setup-go from 4 to 5
-- build(deps): bump golang.org/x/net from 0.18.0 to 0.19.0
-
-## v1.33.3 (Released 2023-11-14)
-
-IMPROVEMENTS
-
-- docs: improve TraceNumber
-- fix: check .UnequalAddendaCounts in CTX validation
-
-BUILD
-
-- build(deps): bump golang.org/x/net from 0.17.0 to 0.18.0
-- fix(deps): update module github.com/moov-io/base to v0.48.2
-
-## v1.33.2 (Released 2023-10-31)
-
-IMPROVEMENTS
-
-- fix: Parse FileIdModifier from file header; don't assume 'A'
-- fix: revert "reduce allocations when parsing FileHeader"
-- test/issues: verify FileIDModifier is read
-
-BUILD
-
-- build(deps): bump github.com/moov-io/base from 0.47.0 to 0.47.1
-
 ## v1.33.1 (Released 2023-10-26)
 
 This release contains multiple performance improvements ontop of what v1.33.0 has included. The comparison to v1.32.2 is below:
diff --git a/addenda99.go b/addenda99.go
index 3e20f1a1..533cdf31 100644
--- a/addenda99.go
+++ b/addenda99.go
@@ -18,7 +18,6 @@
 package ach
 
 import (
-	"fmt"
 	"strings"
 	"unicode/utf8"
 )
@@ -137,7 +136,7 @@ func (Addenda99 *Addenda99) Parse(record string) {
 			Addenda99.OriginalDFI = Addenda99.parseStringField(reset())
 		case 79:
 			// 36-79
-			Addenda99.AddendaInformation = reset()
+			Addenda99.AddendaInformation = strings.TrimSpace(reset())
 		case 94:
 			// 80-94
 			Addenda99.TraceNumber = strings.TrimSpace(reset())
@@ -245,75 +244,6 @@ func (Addenda99 *Addenda99) TraceNumberField() string {
 	return Addenda99.stringField(Addenda99.TraceNumber, 15)
 }
 
-// format: reserved (3), return trace number (15), return settlement date (3), return reason (2), addenda (21)
-// Ref: https://www.nachaoperatingrulesonline.org/2.16334/s020
-func (Addenda99 *Addenda99) SetDishonoredAddendaInformation(
-	returnTraceNumber string,
-	returnSettlementDate string,
-	returnReasonCode string,
-	addenda string,
-) {
-	// This record drops the "R"
-	returnReasonCode = strings.TrimPrefix(returnReasonCode, "R")
-
-	addendaInformation := fmt.Sprintf("   %s%s%s%s",
-		Addenda99.alphaField(returnTraceNumber, 15),
-		Addenda99.alphaField(returnSettlementDate, 3),
-		Addenda99.alphaField(returnReasonCode, 2),
-		Addenda99.alphaField(addenda, 21),
-	)
-	Addenda99.AddendaInformation = Addenda99.alphaField(addendaInformation, 44)
-}
-
-// Ref: https://www.nachaoperatingrulesonline.org/2.16334/s020
-func (Addenda99 *Addenda99) SetContestedAddendaInformation(
-	originalSettlementDate string,
-	returnTraceNumber string,
-	returnSettlementDate string,
-	returnReasonCode string,
-	dishonoredReturnTraceNumber string,
-	dishonoredReturnSettlementDate string,
-	dishonoredReturnReasonCode string,
-) {
-	// This record drops the "R"
-	returnReasonCode = strings.TrimPrefix(returnReasonCode, "R")
-
-	addendaInformation := fmt.Sprintf("%s%s%s%s%s%s%s ",
-		Addenda99.alphaField(originalSettlementDate, 3),
-		Addenda99.alphaField(returnTraceNumber, 15),
-		Addenda99.alphaField(returnSettlementDate, 3),
-		Addenda99.alphaField(returnReasonCode, 2),
-		Addenda99.alphaField(dishonoredReturnTraceNumber, 15),
-		Addenda99.alphaField(dishonoredReturnSettlementDate, 3),
-		Addenda99.alphaField(dishonoredReturnReasonCode, 2),
-	)
-	Addenda99.AddendaInformation = Addenda99.alphaField(addendaInformation, 44)
-}
-
-func (Addenda99 *Addenda99) AddendaInformationReturnTraceNumber() string {
-	return Addenda99.AddendaInformation[3:18]
-}
-
-func (Addenda99 *Addenda99) AddendaInformationReturnSettlementDate() string {
-	return Addenda99.AddendaInformation[18:21]
-}
-
-func (Addenda99 *Addenda99) AddendaInformationReturnReasonCode() string {
-	return fmt.Sprintf("R%s", Addenda99.AddendaInformation[21:23])
-}
-
-func (Addenda99 *Addenda99) AddendaInformationExtra() string {
-	return Addenda99.AddendaInformation[23:]
-}
-
-func (Addenda99 *Addenda99) SetOriginalEntryReturnDate(date string) {
-	Addenda99.DateOfDeath = Addenda99.stringField(date, 6)
-}
-
-func (Addenda99 *Addenda99) OriginalEntryReturnDate() string {
-	return Addenda99.DateOfDeath
-}
-
 // ReturnCodeField gives the ReturnCode struct for the given Addenda99 record
 func (Addenda99 *Addenda99) ReturnCodeField() *ReturnCode {
 	code, ok := returnCodeDict[Addenda99.ReturnCode]
diff --git a/batchATX.go b/batchATX.go
index fa7229db..5f7b6eb8 100644
--- a/batchATX.go
+++ b/batchATX.go
@@ -60,7 +60,7 @@ func (batch *BatchATX) Validate() error {
 			return batch.Error("Amount", ErrBatchAmountNonZero, entry.Amount)
 		}
 		switch entry.TransactionCode {
-		case CheckingZeroDollarRemittanceCredit, SavingsZeroDollarRemittanceCredit, CheckingReturnNOCCredit:
+		case CheckingZeroDollarRemittanceCredit, SavingsZeroDollarRemittanceCredit:
 		default:
 			return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
 		}
diff --git a/batchCTX.go b/batchCTX.go
index 63ff88ea..3a12ca5f 100644
--- a/batchCTX.go
+++ b/batchCTX.go
@@ -75,9 +75,7 @@ func (batch *BatchCTX) Validate() error {
 		// use 0 value if there is no Addenda records
 		indicator, _ := strconv.Atoi(entry.CATXAddendaRecordsField())
 		if addendaCount != indicator {
-			if batch.validateOpts == nil || !batch.validateOpts.UnequalAddendaCounts {
-				return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(addendaCount, indicator))
-			}
+			return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(addendaCount, indicator))
 		}
 		// Verify TransactionCode for prenotes and regular entries
 		switch entry.TransactionCode {
diff --git a/batchErrors.go b/batchErrors.go
index a2324af7..9eff7106 100644
--- a/batchErrors.go
+++ b/batchErrors.go
@@ -87,12 +87,10 @@ func (b *Batch) Error(field string, err error, values ...interface{}) error {
 		return err
 	}
 	be := BatchError{
-		FieldName: field,
-		Err:       err,
-	}
-	if b != nil {
-		be.BatchNumber = b.Header.BatchNumber
-		be.BatchType = b.Header.StandardEntryClassCode
+		BatchNumber: b.Header.BatchNumber,
+		BatchType:   b.Header.StandardEntryClassCode,
+		FieldName:   field,
+		Err:         err,
 	}
 	// only the first value counts
 	if len(values) > 0 {
diff --git a/batchHeader.go b/batchHeader.go
index 4b7840df..de9e372a 100644
--- a/batchHeader.go
+++ b/batchHeader.go
@@ -376,7 +376,7 @@ func (bh *BatchHeader) CompanyDescriptiveDateField() string {
 // EffectiveEntryDateField get the EffectiveEntryDate in YYMMDD format
 func (bh *BatchHeader) EffectiveEntryDateField() string {
 	// ENR records require EffectiveEntryDate to be space filled. NACHA Page OR108
-	if bh.CompanyEntryDescription == "AUTOENROLL" && bh.StandardEntryClassCode == ENR {
+	if bh.CompanyEntryDescription == "AUTOENROLL" {
 		return bh.alphaField("", 6)
 	}
 	return bh.stringField(bh.EffectiveEntryDate, 6) // YYMMDD
diff --git a/cmd/achcli/describe.go b/cmd/achcli/describe.go
index 65e6f56d..cb44fa75 100644
--- a/cmd/achcli/describe.go
+++ b/cmd/achcli/describe.go
@@ -19,9 +19,7 @@ func dumpFiles(paths []string, validateOpts *ach.ValidateOpts) error {
 		if err != nil {
 			fmt.Printf("WARN: problem reading %s:\n %v\n\n", paths[i], err)
 		}
-		if f != nil {
-			files[i] = f
-		}
+		files[i] = f
 	}
 
 	if *flagMerge {
@@ -40,9 +38,7 @@ func dumpFiles(paths []string, validateOpts *ach.ValidateOpts) error {
 			if err != nil {
 				fmt.Printf("ERROR: problem flattening file: %v\n", err)
 			}
-			if file != nil {
-				files[i] = file
-			}
+			files[i] = file
 		}
 	}
 
diff --git a/cmd/webui/assets/index.html b/cmd/webui/assets/index.html
index 5f42a07b..b760dff7 100644
--- a/cmd/webui/assets/index.html
+++ b/cmd/webui/assets/index.html
@@ -53,8 +53,6 @@
         }
         [id="contentoutput"] {
             grid-area: output;
-            white-space: pre;
-            overflow: auto;
         }
         header {
             grid-area: header;
diff --git a/cmd/writeACH/main.go b/cmd/writeACH/main.go
index e52a740a..fe8d91f9 100644
--- a/cmd/writeACH/main.go
+++ b/cmd/writeACH/main.go
@@ -67,8 +67,7 @@ func write(path string) {
 
 	f, err := os.Create(path)
 	if err != nil {
-		fmt.Printf("%T: %v\n", err, err)
-		return
+		fmt.Printf("%T: %s", err, err)
 	}
 
 	// To create a file
@@ -92,11 +91,7 @@ func write(path string) {
 		bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102")
 		bh.ODFIIdentification = "121042882"
 
-		batch, err := ach.NewBatch(bh)
-		if err != nil {
-			fmt.Printf("%T: %v\n", err, err)
-			return
-		}
+		batch, _ := ach.NewBatch(bh)
 
 		// Create Entry
 		entrySeq := 0
@@ -126,8 +121,7 @@ func write(path string) {
 
 		// Create the batch.
 		if err := batch.Create(); err != nil {
-			fmt.Printf("%T: %v\n", err, err)
-			return
+			fmt.Printf("%T: %s", err, err)
 		}
 
 		// Add batch to the file
@@ -137,35 +131,30 @@ func write(path string) {
 	// ensure we have a validated file structure
 	if file.Validate(); err != nil {
 		fmt.Printf("Could not validate entire file: %v", err)
-		return
 	}
 
 	// Create the file
 	if err := file.Create(); err != nil {
-		fmt.Printf("%T: %v\n", err, err)
-		return
+		fmt.Printf("%T: %s", err, err)
 	}
 
 	// Write to a file
 	if *flagJson {
 		// Write in JSON format
 		if err := json.NewEncoder(f).Encode(file); err != nil {
-			fmt.Printf("%T: %v\n", err, err)
-			return
+			fmt.Printf("%T: %s", err, err)
 		}
 	} else {
 		// Write in ACH plain text format
 		w := ach.NewWriter(f)
 		if err := w.Write(file); err != nil {
-			fmt.Printf("%T: %v\n", err, err)
-			return
+			fmt.Printf("%T: %s", err, err)
 		}
 		w.Flush()
 	}
 
 	if err := f.Close(); err != nil {
-		fmt.Printf("%T: %v\n", err, err)
-		return
+		fmt.Println(err.Error())
 	}
 
 	fmt.Printf("Wrote %s\n", path)
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index ab44c647..8256ebfc 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -6,7 +6,7 @@ GEM
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       tzinfo (~> 2.0)
-    addressable (2.8.6)
+    addressable (2.8.5)
       public_suffix (>= 2.0.2, < 6.0)
     bulma-clean-theme (0.13.3)
       jekyll (>= 3.9, < 5.0)
@@ -21,7 +21,7 @@ GEM
     coffee-script-source (1.11.1)
     colorator (1.1.0)
     commonmarker (0.23.10)
-    concurrent-ruby (1.2.3)
+    concurrent-ruby (1.2.2)
     dnsruby (1.61.9)
       simpleidn (~> 0.1)
     em-websocket (0.5.3)
diff --git a/entryDetail.go b/entryDetail.go
index ae486a63..65315263 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -62,20 +62,11 @@ type EntryDetail struct {
 	// A value of "1" indicates that one ore more addenda records follow,
 	// and "0" means no such record is present.
 	AddendaRecordIndicator int `json:"addendaRecordIndicator,omitempty"`
-	// TraceNumber is assigned by the ODFI or software vendor and used as part of identification.
-	//
-	// The format of trace numbers is the first 8 digits of the ODFI's routing number followed by
-	// 7 digits chosen by the ODFI or software vendor.
-	//
-	// Sequentual or random numbers can be chosen. The only requirement of Nacha is unique trace
-	// numbers within a batch and file.
-	//
-	// Trace Numbers are included in each Entry Detail Record, Corporate Entry Detail Record,
-	// and addenda Record.
-	//
+	// TraceNumber assigned by the ODFI in ascending sequence, is included in each
+	// Entry Detail Record, Corporate Entry Detail Record, and addenda Record.
+	// Trace Numbers uniquely identify each entry within a batch in an ACH input file.
 	// In association with the Batch Number, transmission (File Creation) Date,
 	// and File ID Modifier, the Trace Number uniquely identifies an entry within a given file.
-	//
 	// For addenda Records, the Trace Number will be identical to the Trace Number
 	// in the associated Entry Detail Record, since the Trace Number is associated
 	// with an entry or item rather than a physical record.
diff --git a/examples/http/main.go b/examples/http/main.go
index 287c966a..d4c8f1e4 100644
--- a/examples/http/main.go
+++ b/examples/http/main.go
@@ -104,12 +104,11 @@ func main() {
 	req.Header.Set("Content-Type", "application/json")
 
 	resp, err := http.DefaultClient.Do(req)
-	if resp == nil || err != nil {
+	if err != nil {
 		log.Fatal(err)
 	}
-	if resp != nil && resp.Body != nil {
-		defer resp.Body.Close()
-	}
+	defer resp.Body.Close()
+
 	if resp.StatusCode == 200 {
 		log.Printf("File created!")
 	} else {
diff --git a/file.go b/file.go
index b831cc89..a3963434 100644
--- a/file.go
+++ b/file.go
@@ -544,9 +544,6 @@ func (f *File) Create() error {
 
 // AddBatch appends a Batch to the ach.File
 func (f *File) AddBatch(batch Batcher) []Batcher {
-	if batch == nil {
-		return f.Batches
-	}
 	if batch.Category() == CategoryNOC {
 		f.NotificationOfChange = append(f.NotificationOfChange, batch)
 	}
@@ -987,10 +984,7 @@ func (f *File) SegmentFile(_ *SegmentFileConfiguration) (*File, *File, error) {
 	}
 
 	if f.Batches != nil {
-		err := f.segmentFileBatches(creditFile, debitFile)
-		if err != nil {
-			return nil, nil, err
-		}
+		f.segmentFileBatches(creditFile, debitFile)
 	}
 
 	if f.IATBatches != nil {
@@ -1019,7 +1013,7 @@ func (f *File) SegmentFile(_ *SegmentFileConfiguration) (*File, *File, error) {
 	return creditFile, debitFile, nil
 }
 
-func (f *File) segmentFileBatches(creditFile, debitFile *File) error {
+func (f *File) segmentFileBatches(creditFile, debitFile *File) {
 	for _, batch := range f.Batches {
 		bh := batch.GetHeader()
 
@@ -1036,10 +1030,7 @@ func (f *File) segmentFileBatches(creditFile, debitFile *File) error {
 
 				entries := batch.GetADVEntries()
 				for _, entry := range entries {
-					err := segmentFileBatchAddADVEntry(creditBatch, debitBatch, entry)
-					if err != nil {
-						return err
-					}
+					segmentFileBatchAddADVEntry(creditBatch, debitBatch, entry)
 				}
 				// Add the Entry to its Batch
 				if creditBatch != nil && len(creditBatch.GetADVEntries()) > 0 {
@@ -1063,10 +1054,7 @@ func (f *File) segmentFileBatches(creditFile, debitFile *File) error {
 
 				entries := batch.GetEntries()
 				for _, entry := range entries {
-					err := segmentFileBatchAddEntry(creditBatch, debitBatch, entry)
-					if err != nil {
-						return err
-					}
+					segmentFileBatchAddEntry(creditBatch, debitBatch, entry)
 				}
 
 				if creditBatch != nil && len(creditBatch.GetEntries()) > 0 {
@@ -1084,7 +1072,6 @@ func (f *File) segmentFileBatches(creditFile, debitFile *File) error {
 			}
 		}
 	}
-	return nil
 }
 
 // segmentFileIATBatches segments IAT batches debits and credits into debit and credit files
@@ -1188,45 +1175,29 @@ func (f *File) addFileHeaderData(file *File) *File {
 
 // segmentFileBatchAddEntry adds entries to batches in a segmented file
 // Applies to All SEC Codes except ADV (Automated Accounting Advice)
-func segmentFileBatchAddEntry(creditBatch, debitBatch Batcher, entry *EntryDetail) error {
+func segmentFileBatchAddEntry(creditBatch, debitBatch Batcher, entry *EntryDetail) {
 	switch entry.TransactionCode {
 	case CheckingCredit, CheckingReturnNOCCredit, CheckingPrenoteCredit, CheckingZeroDollarRemittanceCredit,
 		SavingsCredit, SavingsReturnNOCCredit, SavingsPrenoteCredit, SavingsZeroDollarRemittanceCredit,
 		GLCredit, GLReturnNOCCredit, GLPrenoteCredit, GLZeroDollarRemittanceCredit,
 		LoanCredit, LoanReturnNOCCredit, LoanPrenoteCredit, LoanZeroDollarRemittanceCredit:
-		if creditBatch == nil {
-			return errors.New("missing creditBatch")
-		}
 		creditBatch.AddEntry(entry)
-
 	case CheckingDebit, CheckingReturnNOCDebit, CheckingPrenoteDebit, CheckingZeroDollarRemittanceDebit,
 		SavingsDebit, SavingsReturnNOCDebit, SavingsPrenoteDebit, SavingsZeroDollarRemittanceDebit,
 		GLDebit, GLReturnNOCDebit, GLPrenoteDebit, GLZeroDollarRemittanceDebit,
 		LoanDebit, LoanReturnNOCDebit:
-		if debitBatch == nil {
-			return errors.New("missing debitBatch")
-		}
 		debitBatch.AddEntry(entry)
 	}
-	return nil
 }
 
 // segmentFileBatchAddADVEntry adds entries to batches in a segment file for SEC Code ADV (Automated Accounting Advice)
-func segmentFileBatchAddADVEntry(creditBatch Batcher, debitBatch Batcher, entry *ADVEntryDetail) error {
+func segmentFileBatchAddADVEntry(creditBatch Batcher, debitBatch Batcher, entry *ADVEntryDetail) {
 	switch entry.TransactionCode {
 	case CreditForDebitsOriginated, CreditForCreditsReceived, CreditForCreditsRejected, CreditSummary:
-		if creditBatch == nil {
-			return errors.New("missing creditBatch")
-		}
 		creditBatch.AddADVEntry(entry)
-
 	case DebitForCreditsOriginated, DebitForDebitsReceived, DebitForDebitsRejectedBatches, DebitSummary:
-		if debitBatch == nil {
-			return errors.New("missing debitBatch")
-		}
 		debitBatch.AddADVEntry(entry)
 	}
-	return nil
 }
 
 // Validates that the batch numbers are ascending
diff --git a/fileHeader.go b/fileHeader.go
index c41dcdf9..d3f02c05 100644
--- a/fileHeader.go
+++ b/fileHeader.go
@@ -118,37 +118,71 @@ func NewFileHeader() FileHeader {
 //
 // Parse provides no guarantee about all fields being filled in. Callers should make a Validate call to confirm successful parsing and data validity.
 func (fh *FileHeader) Parse(record string) {
-	if utf8.RuneCountInString(record) != 94 {
+	runeCount := utf8.RuneCountInString(record)
+	if runeCount != 94 {
 		return
 	}
-	runes := []rune(record)
 
-	// (character position 1-1) Always "1"
-	// (2-3) Always "01"
+	buf := getBuffer()
+	defer saveBuffer(buf)
+
+	reset := func() string {
+		out := buf.String()
+		buf.Reset()
+		return out
+	}
+
+	// Set hardcoded fields
 	fh.priorityCode = "01"
-	// (4-13) A blank space followed by your ODFI's routing number. For example: " 121140399"
-	fh.ImmediateDestination = trimRoutingNumberLeadingZero(fh.parseStringField(string(runes[3:13])))
-	// (14-23) A 10-digit number assigned to you by the ODFI once they approve you to originate ACH files through them
-	fh.ImmediateOrigin = trimRoutingNumberLeadingZero(fh.parseStringField(string(runes[13:23])))
-	// 24-29 Today's date in YYMMDD format
-	// must be after today's date.
-	fh.FileCreationDate = fh.validateSimpleDate(string(runes[23:29]))
-	// 30-33 The current time in HHmm format
-	fh.FileCreationTime = fh.validateSimpleTime(string(runes[29:33]))
-	// 35-37 Always "A"
-	fh.FileIDModifier = string(runes[33:34])
 	// 35-37 always "094"
 	fh.recordSize = "094"
 	// 38-39 always "10"
 	fh.blockingFactor = "10"
 	// 40 always "1"
 	fh.formatCode = "1"
-	// 41-63 The name of the ODFI. example "SILICON VALLEY BANK    "
-	fh.ImmediateDestinationName = fh.parseStringFieldWithOpts(string(runes[40:63]), fh.validateOpts)
-	// 64-86 ACH operator or sending point that is sending the file
-	fh.ImmediateOriginName = fh.parseStringFieldWithOpts(string(runes[63:86]), fh.validateOpts)
-	// 87-94 Optional field that may be used to describe the ACH file for internal accounting purposes
-	fh.ReferenceCode = fh.parseStringFieldWithOpts(string(runes[86:94]), fh.validateOpts)
+
+	var idx int
+	for _, r := range record {
+		idx++
+
+		// Append rune to buffer
+		buf.WriteRune(r)
+
+		// At each field cutoff save the buffer and reset
+		switch idx {
+		case 4:
+			reset()
+		case 13:
+			// (4-13) A blank space followed by your ODFI's routing number. For example: " 121140399"
+			fh.ImmediateDestination = trimRoutingNumberLeadingZero(fh.parseStringField(reset()))
+		case 23:
+			// (14-23) A 10-digit number assigned to you by the ODFI once they approve you to originate ACH files through them
+			fh.ImmediateOrigin = trimRoutingNumberLeadingZero(fh.parseStringField(reset()))
+		case 29:
+			// 24-29 Today's date in YYMMDD format must be after today's date.
+			fh.FileCreationDate = fh.validateSimpleDate(reset())
+		case 33:
+			// 30-33 The current time in HHmm format
+			fh.FileCreationTime = fh.validateSimpleTime(reset())
+		case 36: // 35-37 Always "A"
+			reset()
+		case 37:
+			// 35-37 Always "A"
+			reset()
+			fh.FileIDModifier = "A"
+		case 39, 40:
+			reset()
+		case 63:
+			// 41-63 The name of the ODFI. example "SILICON VALLEY BANK    "
+			fh.ImmediateDestinationName = fh.parseStringFieldWithOpts(reset(), fh.validateOpts)
+		case 86:
+			// 64-86 ACH operator or sending point that is sending the file
+			fh.ImmediateOriginName = fh.parseStringFieldWithOpts(reset(), fh.validateOpts)
+		case 94:
+			// 87-94 Optional field that may be used to describe the ACH file for internal accounting purposes
+			fh.ReferenceCode = fh.parseStringFieldWithOpts(reset(), fh.validateOpts)
+		}
+	}
 }
 
 func trimRoutingNumberLeadingZero(s string) string {
diff --git a/go.mod b/go.mod
index 67833943..bb77e19e 100644
--- a/go.mod
+++ b/go.mod
@@ -4,17 +4,17 @@ go 1.19
 
 require (
 	github.com/Pallinder/go-randomdata v1.2.0
-	github.com/aws/aws-lambda-go v1.45.0
+	github.com/aws/aws-lambda-go v1.41.0
 	github.com/go-kit/kit v0.13.0
 	github.com/go-kit/log v0.2.1
-	github.com/gorilla/mux v1.8.1
+	github.com/gorilla/mux v1.8.0
 	github.com/juju/ansiterm v1.0.0
-	github.com/moov-io/base v0.48.5
+	github.com/moov-io/base v0.47.1
 	github.com/moov-io/iso4217 v0.3.0
-	github.com/prometheus/client_golang v1.18.0
+	github.com/prometheus/client_golang v1.17.0
 	github.com/stretchr/testify v1.8.4
-	golang.org/x/net v0.20.0
-	golang.org/x/text v0.14.0
+	golang.org/x/net v0.17.0
+	golang.org/x/text v0.13.0
 )
 
 require (
@@ -22,16 +22,17 @@ require (
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/go-logfmt/logfmt v0.6.0 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/lunixbochs/vtclean v1.0.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.19 // indirect
-	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
-	github.com/prometheus/common v0.45.0 // indirect
+	github.com/prometheus/common v0.44.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
 	github.com/rickar/cal/v2 v2.1.13 // indirect
-	golang.org/x/sys v0.16.0 // indirect
-	google.golang.org/protobuf v1.32.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 90348bf2..5d02152a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
 github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg=
 github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y=
 github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
-github.com/aws/aws-lambda-go v1.45.0 h1:3xS35Dlc8ffmcwfcKTyqJGiMuL0UDvkQaVUrI5yHycI=
-github.com/aws/aws-lambda-go v1.45.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
+github.com/aws/aws-lambda-go v1.41.0 h1:l/5fyVb6Ud9uYd411xdHZzSf2n86TakxzpvIoz7l+3Y=
+github.com/aws/aws-lambda-go v1.41.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -15,10 +15,15 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
 github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
-github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
-github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg=
 github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -36,20 +41,20 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
-github.com/moov-io/base v0.48.5 h1:QaTyTo6eFFFV35R9l/GdePQN40IJti9knD5hqdWPnnM=
-github.com/moov-io/base v0.48.5/go.mod h1:D5ZV9COV/qtCjTQuYpq7gGInCk64AhOQI6UY4kt4Rq8=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/moov-io/base v0.47.1 h1:kQXBbTesyqb9ETem/jCeG2bYGuOpZYFbTHHVrwcpkrY=
+github.com/moov-io/base v0.47.1/go.mod h1:AVMPYsHbPg+TxqOpfwIIHuT1rLsjxEhIekIKRj91i4I=
 github.com/moov-io/iso4217 v0.3.0 h1:xAxeJEn7QIpEy971eElEGiaLNdj81UwOcCcvE6j2ZAo=
 github.com/moov-io/iso4217 v0.3.0/go.mod h1:/pF1mBInDSJtwyWkLNr8EMdIn1zIl2cyjaVi4Xhpd44=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
-github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
 github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
-github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
+github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rickar/cal/v2 v2.1.13 h1:FENBPXxDPyL1OWGf9ZdpWGcEiGoSjt0UZED8VOxvK0c=
@@ -57,19 +62,23 @@ github.com/rickar/cal/v2 v2.1.13/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdT
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/iatEntryDetail.go b/iatEntryDetail.go
index 1637831d..f3acb36a 100644
--- a/iatEntryDetail.go
+++ b/iatEntryDetail.go
@@ -61,20 +61,11 @@ type IATEntryDetail struct {
 	// A value of "1" indicates that one or more addenda records follow,
 	// and "0" means no such record is present.
 	AddendaRecordIndicator int `json:"addendaRecordIndicator"`
-	// TraceNumber is assigned by the ODFI or software vendor and used as part of identification.
-	//
-	// The format of trace numbers is the first 8 digits of the ODFI's routing number followed by
-	// 7 digits chosen by the ODFI or software vendor.
-	//
-	// Sequentual or random numbers can be chosen. The only requirement of Nacha is unique trace
-	// numbers within a batch and file.
-	//
-	// Trace Numbers are included in each Entry Detail Record, Corporate Entry Detail Record,
-	// and addenda Record.
-	//
+	// TraceNumber assigned by the ODFI in ascending sequence, is included in each
+	// Entry Detail Record, Corporate Entry Detail Record, and addenda Record.
+	// Trace Numbers uniquely identify each entry within a batch in an ACH input file.
 	// In association with the Batch Number, transmission (File Creation) Date,
 	// and File ID Modifier, the Trace Number uniquely identifies an entry within a given file.
-	//
 	// For addenda Records, the Trace Number will be identical to the Trace Number
 	// in the associated Entry Detail Record, since the Trace Number is associated
 	// with an entry or item rather than a physical record.
@@ -201,7 +192,7 @@ func (iatEd *IATEntryDetail) Parse(record string) {
 			iatEd.Amount = iatEd.parseNumField(reset())
 		case 74:
 			// 40-74 The foreign receiver's account number you are crediting/debiting
-			iatEd.DFIAccountNumber = string(reset())
+			iatEd.DFIAccountNumber = iatEd.parseStringFieldWithOpts(reset(), iatEd.validateOpts)
 		case 76:
 			// 75-76 reserved Leave blank
 			reset()
diff --git a/iterator.go b/iterator.go
index 7a114046..fd9a7106 100644
--- a/iterator.go
+++ b/iterator.go
@@ -110,9 +110,6 @@ func (i *Iterator) NextEntry() (*BatchHeader, *EntryDetail, error) {
 			if err != nil {
 				return nil, nil, fmt.Errorf("faking batch for line %d failed: %w", i.reader.lineNum, err)
 			}
-			if i.reader.currentBatch == nil {
-				return nil, nil, fmt.Errorf("failed to create %s batch: %v", bh.StandardEntryClassCode, err)
-			}
 			if err := i.reader.readLine(line); err != nil {
 				return nil, nil, fmt.Errorf("reading line %d with fake BatchHeader failed: %w", i.reader.lineNum, err)
 			}
diff --git a/makefile b/makefile
index e8d01212..69116f72 100644
--- a/makefile
+++ b/makefile
@@ -28,7 +28,7 @@ ifeq ($(OS),Windows_NT)
 else
 	@wget -O lint-project.sh https://raw.githubusercontent.com/moov-io/infra/master/go/lint-project.sh
 	@chmod +x ./lint-project.sh
-	GOLANGCI_LINTERS=prealloc GOLANGCI_SKIP_DIR=test EXPERIMENTAL=nilaway,shuffle \
+	GOLANGCI_LINTERS=prealloc GOLANGCI_SKIP_DIR=test EXPERIMENTAL=shuffle \
 	GOCYCLO_LIMIT=26 COVER_THRESHOLD=90.0 \
 	GOOS=js GOARCH=wasm time ./lint-project.sh
 endif
diff --git a/merge.go b/merge.go
index dae00c56..f0929e53 100644
--- a/merge.go
+++ b/merge.go
@@ -18,7 +18,6 @@
 package ach
 
 import (
-	"fmt"
 	"time"
 )
 
@@ -230,10 +229,7 @@ func (fs *mergableFiles) findOutfile(f *File) *File {
 }
 
 func mergeEntries(b1, b2 Batcher) (Batcher, error) {
-	b, err := NewBatch(b1.GetHeader())
-	if err != nil {
-		return nil, fmt.Errorf("mergeEntries: %w", err)
-	}
+	b, _ := NewBatch(b1.GetHeader())
 	entries := sortEntriesByTraceNumber(append(b1.GetEntries(), b2.GetEntries()...))
 	for i := range entries {
 		b.AddEntry(entries[i])
diff --git a/openapi.yaml b/openapi.yaml
index eec34bc1..9f4dcf31 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -795,12 +795,10 @@ components:
         totalDebit:
           description: Accumulated Batch debit totals within the file.
           type: integer
-          format: int64
           example: 100
         totalCredit:
           description: Accumulated Batch credit totals within the file.
           type: integer
-          format: int64
           example: 20
       required:
         - ID
@@ -940,12 +938,10 @@ components:
         totalDebit:
           description: Contains accumulated Entry debit totals within the batch.
           type: integer
-          format: int64
           example: 100
         totalCredit:
           description: Contains accumulated Entry credit totals within the batch.
           type: integer
-          format: int64
           example: 100
         companyIdentification:
           description: |
@@ -981,11 +977,11 @@ components:
     EntryDetail:
       required:
         - id
-        - amount
-        - DFIAccountNumber
+        - transactionCode
         - RDFIIdentification
         - checkDigit
-        - transactionCode
+        - DFIAccountNumber
+        - amount
         - individualName
       properties:
         id:
@@ -1020,7 +1016,6 @@ components:
           example: "181141847"
         amount:
           type: integer
-          format: int64
           description: Number of cents you are debiting/crediting this account
           example: 1235
         identificationNumber:
@@ -1045,25 +1040,9 @@ components:
         traceNumber:
           type: string
           description: |
-            TraceNumber is assigned by the ODFI or software vendor and used as part of identification.
-
-            The format of trace numbers is the first 8 digits of the ODFI's routing number followed by
-            7 digits chosen by the ODFI or software vendor.
-
-            Sequentual or random numbers can be chosen. The only requirement of Nacha is unique trace
-            numbers within a batch and file.
-
-            Trace Numbers are included in each Entry Detail Record, Corporate Entry Detail Record,
-            and addenda Record.
-
-            In association with the Batch Number, transmission (File Creation) Date,
-            and File ID Modifier, the Trace Number uniquely identifies an entry within a given file.
-
-            For addenda Records, the Trace Number will be identical to the Trace Number
-            in the associated Entry Detail Record, since the Trace Number is associated
-            with an entry or item rather than a physical record.
-
-            Use TraceNumberField for a properly formatted string representation.
+            TraceNumber assigned by the ODFI in ascending sequence, is included in each Entry Detail Record, Corporate Entry Detail Record, and Addenda Record.
+            Trace Numbers uniquely identify each entry within a batch in an ACH input file. In association with the Batch Number, transmission (File Creation) Date, and File ID Modifier, the Trace Number uniquely identifies an entry within a given file.
+            For Addenda Records, the Trace Number will be identical to the Trace Number in the associated Entry Detail Record, since the Trace Number is associated with an entry or item rather than a physical record.
           example: "124782618117"
         addenda02:
           $ref: '#/components/schemas/Addenda02'
@@ -1086,7 +1065,6 @@ components:
           type: string
           description: Category defines if the entry is a Forward, Return, or NOC
           example: "Forward"
-
     Addenda02:
       properties:
         id:
@@ -1220,7 +1198,6 @@ components:
           example: TEL
         foreignPaymentAmount:
           type: integer
-          format: int64
           description: For inbound IAT payments this field should contain the USD amount or may be blank.
           example: 1021441
         foreignTraceNumber:
@@ -1999,7 +1976,6 @@ components:
           example: 1
         amount:
           type: integer
-          format: int64
           description: Number of cents you are debiting/crediting this account
           example: 1235
         DFIAccountNumber:
@@ -2111,7 +2087,6 @@ components:
           example: "181141847"
         amount:
           type: integer
-          format: int64
           description: Number of cents you are debiting/crediting this account
           example: 1235
         adviceRoutingNumber:
@@ -2184,12 +2159,10 @@ components:
         totalDebit:
           description: Contains accumulated Entry debit totals within the batch.
           type: integer
-          format: int64
           example: 100
         totalCredit:
           description: Contains accumulated Entry credit totals within the batch.
           type: integer
-          format: int64
           example: 100
         achOperatorData:
           description: Alphanumeric code used to identify an ACH Operator
@@ -2240,12 +2213,10 @@ components:
         totalDebit:
           description: Accumulated Batch debit totals within the file.
           type: integer
-          format: int64
           example: 100
         totalCredit:
           description: Accumulated Batch credit totals within the file.
           type: integer
-          format: int64
           example: 20
       required:
         - ID
diff --git a/reader.go b/reader.go
index b79b5a5d..003f9ecb 100644
--- a/reader.go
+++ b/reader.go
@@ -575,30 +575,22 @@ func (r *Reader) parseAddenda() error {
 
 // parseADVAddenda takes the input record string and create an Addenda99 appended to the last ADVEntryDetail
 func (r *Reader) parseADVAddenda() error {
-	if r.currentBatch == nil {
-		return ErrFileAddendaOutsideBatch
-	}
 	if len(r.currentBatch.GetADVEntries()) == 0 {
 		return ErrFileAddendaOutsideEntry
 	}
-
 	entryIndex := len(r.currentBatch.GetADVEntries()) - 1
 	entry := r.currentBatch.GetADVEntries()[entryIndex]
 
 	if entry.AddendaRecordIndicator != 1 {
 		return r.parseError(r.currentBatch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator))
 	}
-
 	addenda99 := NewAddenda99()
 	addenda99.Parse(r.line)
-
 	if err := maybeValidate(addenda99, r.File.validateOpts); err != nil {
 		return r.parseError(err)
 	}
-
 	r.currentBatch.GetADVEntries()[entryIndex].Category = CategoryReturn
 	r.currentBatch.GetADVEntries()[entryIndex].Addenda99 = addenda99
-
 	return nil
 }
 
@@ -686,7 +678,7 @@ func (r *Reader) parseIATEntryDetail() error {
 		return ErrFileEntryOutsideBatch
 	}
 
-	ed := new(IATEntryDetail)
+	ed := NewIATEntryDetail()
 	ed.Parse(r.line)
 	if err := maybeValidate(ed, r.File.validateOpts); err != nil {
 		return r.parseError(err)
diff --git a/server/files.go b/server/files.go
index 27b6bb8c..79f6135f 100644
--- a/server/files.go
+++ b/server/files.go
@@ -161,11 +161,7 @@ func decodeCreateFileRequest(_ context.Context, request *http.Request) (interfac
 	}
 
 	for _, name := range validationNames {
-		q := request.URL.Query()
-		if q == nil {
-			continue
-		}
-		input := q.Get(name)
+		input := request.URL.Query().Get(name)
 		if input == "" {
 			continue
 		}
@@ -241,9 +237,7 @@ func decodeCreateFileRequest(_ context.Context, request *http.Request) (interfac
 	// Set the fileID from the request
 	fileID, ok := mux.Vars(request)["fileID"]
 	if ok && fileID != "" && fileID != "create" {
-		if req.File != nil {
-			req.File.ID = fileID
-		}
+		req.File.ID = fileID
 	}
 
 	return req, nil
diff --git a/server/repository.go b/server/repository.go
index d423331f..62f038c8 100644
--- a/server/repository.go
+++ b/server/repository.go
@@ -133,9 +133,7 @@ func (r *repositoryInMemory) StoreBatch(fileID string, batch ach.Batcher) error
 	}
 
 	// Add the batch to the file
-	if f := r.files[fileID]; f != nil {
-		f.AddBatch(batch)
-	}
+	r.files[fileID].AddBatch(batch)
 
 	return nil
 }
@@ -205,11 +203,7 @@ func (r *repositoryInMemory) cleanupOldFiles() {
 	tooOldStr := tooOld.Format("060102") // YYMMDD
 
 	for i := range r.files {
-		file := r.files[i]
-		if file == nil {
-			continue
-		}
-		if file.Header.FileCreationDate < tooOldStr {
+		if r.files[i].Header.FileCreationDate < tooOldStr {
 			removed++
 			delete(r.files, i)
 		}
diff --git a/server/routing.go b/server/routing.go
index ca3c86ab..05f2eda7 100644
--- a/server/routing.go
+++ b/server/routing.go
@@ -242,9 +242,7 @@ func marshalStructWithError(in interface{}, w http.ResponseWriter) error {
 	for i := 0; i < v.NumField(); i++ {
 		name := v.Type().Field(i).Name
 		value := v.Field(i).Interface()
-		if value == nil {
-			continue
-		}
+
 		if err, ok := value.(error); ok {
 			out["error"] = err.Error()
 		} else {
diff --git a/version.go b/version.go
index ba09119f..00819f4c 100644
--- a/version.go
+++ b/version.go
@@ -18,4 +18,4 @@
 package ach
 
 // Version Number
-const Version = "v1.33.6"
+const Version = "v1.33.1"


Test patch:
diff --git a/addenda99_dishonored_return_test.go b/addenda99_dishonored_return_test.go
index 925b698e..33189098 100644
--- a/addenda99_dishonored_return_test.go
+++ b/addenda99_dishonored_return_test.go
@@ -36,31 +36,6 @@ func mockAddenda99Dishonored() *Addenda99Dishonored {
 	return addenda99
 }
 
-func TestAddenda99Dishonored(t *testing.T) {
-	addenda99 := NewAddenda99()
-	line := "799R6909100001137143222042712114530   1211453000251201170506                   091000011371432"
-	addenda99.Parse(line)
-
-	require.Equal(t, "12114530", addenda99.OriginalDFI)
-	require.Equal(t, "   1211453000251201170506                   ", addenda99.AddendaInformation)
-	require.Equal(t, "091000011371432", addenda99.TraceNumber)
-	require.Equal(t, line, addenda99.String())
-
-	addenda99 = NewAddenda99()
-	addenda99.ReturnCode = "R69"
-	addenda99.OriginalTrace = "091000011371432"
-	addenda99.DateOfDeath = "220427"
-	addenda99.OriginalDFI = "12114530"
-	addenda99.SetDishonoredAddendaInformation("121145300025120", "117", "R05", "06")
-	addenda99.TraceNumber = "091000011371432"
-	require.Equal(t, line, addenda99.String())
-
-	require.Equal(t, "121145300025120", addenda99.AddendaInformationReturnTraceNumber())
-	require.Equal(t, "117", addenda99.AddendaInformationReturnSettlementDate())
-	require.Equal(t, "R05", addenda99.AddendaInformationReturnReasonCode())
-	require.Equal(t, "06                   ", addenda99.AddendaInformationExtra())
-}
-
 func TestAddenda99Dishonored__Fields(t *testing.T) {
 	addenda99 := mockAddenda99Dishonored()
 
diff --git a/addenda99_test.go b/addenda99_test.go
index 2b2c5156..390ec445 100644
--- a/addenda99_test.go
+++ b/addenda99_test.go
@@ -58,7 +58,7 @@ func testAddenda99Parse(t testing.TB) {
 	if addenda99.OriginalDFI != "09101298" {
 		t.Errorf("expected: %s got: %s", "09101298", addenda99.OriginalDFI)
 	}
-	if addenda99.AddendaInformation != "Authorization revoked                       " {
+	if addenda99.AddendaInformation != "Authorization revoked" {
 		t.Errorf("expected: %v got: %v", "Authorization revoked", addenda99.AddendaInformation)
 	}
 	if addenda99.TraceNumber != "091012980000066" {
diff --git a/batchCTX_test.go b/batchCTX_test.go
index d2c20d84..2b120fa1 100644
--- a/batchCTX_test.go
+++ b/batchCTX_test.go
@@ -18,8 +18,6 @@
 package ach
 
 import (
-	"encoding/json"
-	"fmt"
 	"testing"
 
 	"github.com/moov-io/base"
@@ -164,11 +162,6 @@ func testBatchCTXAddendaCount(t testing.TB) {
 	if !base.Match(err, NewErrBatchExpectedAddendaCount(0, 1)) {
 		t.Errorf("%T: %s", err, err)
 	}
-
-	mockBatch.SetValidation(&ValidateOpts{
-		UnequalAddendaCounts: true,
-	})
-	require.NoError(t, mockBatch.Create())
 }
 
 // TestBatchCTXAddendaCount tests validating BatchCTX Addendum count of 2
@@ -572,26 +565,3 @@ func TestBatchCTXAddenda02(t *testing.T) {
 		t.Errorf("%T: %s", err, err)
 	}
 }
-
-func TestBatchCTX_JSON_RoundTrip(t *testing.T) {
-	mockBatch := mockBatchCTX(t)
-	file := NewFile()
-	file.Header = staticFileHeader()
-	file.AddBatch(mockBatch)
-
-	bs, err := json.Marshal(file)
-	require.NoError(t, err)
-	read, err := FileFromJSON(bs)
-	require.NoError(t, err)
-
-	// Check both files against each other
-	require.Equal(t, len(file.Batches), len(read.Batches))
-
-	for i := range file.Batches {
-		ied := file.Batches[i].GetEntries()
-		red := read.Batches[i].GetEntries()
-		require.Greater(t, len(ied), 0)
-
-		require.ElementsMatch(t, ied, red, fmt.Sprintf("batch[%d]", i))
-	}
-}
diff --git a/batchHeader_test.go b/batchHeader_test.go
index 7b3c2241..1441fc5a 100644
--- a/batchHeader_test.go
+++ b/batchHeader_test.go
@@ -323,10 +323,6 @@ func testBatchCompanyIdentificationAlphaNumeric(t testing.TB) {
 	if !base.Match(err, ErrNonAlphanumeric) {
 		t.Errorf("%T: %s", err, err)
 	}
-
-	// Allowed case
-	bh.CompanyIdentification = "Acme Corp!"
-	require.NoError(t, bh.Validate())
 }
 
 // TestBatchCompanyIdentificationAlphaNumeric tests validating company identification is alphanumeric
@@ -350,10 +346,6 @@ func testBatchCompanyEntryDescriptionAlphaNumeric(t testing.TB) {
 	if !base.Match(err, ErrNonAlphanumeric) {
 		t.Errorf("%T: %s", err, err)
 	}
-
-	// Allowed case
-	bh.CompanyEntryDescription = "Acme Corp!"
-	require.NoError(t, bh.Validate())
 }
 
 // TestBatchCompanyEntryDescriptionAlphaNumeric tests validating company entry description is alphanumeric
@@ -511,7 +503,6 @@ func TestBatchHeaderENR__EffectiveEntryDateField(t *testing.T) {
 	bh := mockBatchHeader()
 
 	// ENR batches require EffectiveEntryDate to be space filled
-	bh.StandardEntryClassCode = ENR
 	bh.CompanyEntryDescription = "AUTOENROLL"
 	if v, ans := bh.EffectiveEntryDateField(), "      "; v != ans {
 		t.Errorf("got %q (len=%d), expected space filled (len=6)", v, len(ans))
diff --git a/cmd/achcli/describe/file_test.go b/cmd/achcli/describe/file_test.go
index 800189aa..3d11e4c5 100644
--- a/cmd/achcli/describe/file_test.go
+++ b/cmd/achcli/describe/file_test.go
@@ -35,7 +35,7 @@ func TestDescribeIAT(t *testing.T) {
 	if testing.Verbose() {
 		os.Stdout.Write(buf.Bytes())
 	}
-	require.Equal(t, 5255, buf.Len())
+	require.Equal(t, 5262, buf.Len())
 }
 
 func TestDescribeReturn(t *testing.T) {
diff --git a/fileHeader_test.go b/fileHeader_test.go
index ae465eb7..660af125 100644
--- a/fileHeader_test.go
+++ b/fileHeader_test.go
@@ -195,7 +195,7 @@ func TestFileHeader__trimRoutingNumberLeadingZero(t *testing.T) {
 
 // parseFileHeader validates parsing a file header
 func parseFileHeader(t testing.TB) {
-	var line = "101 076401251 0764012511807291511Q094101achdestname            companyname                    "
+	var line = "101 076401251 0764012511807291511A094101achdestname            companyname                    "
 	r := NewReader(strings.NewReader(line))
 	r.line = line
 	if err := r.parseFileHeader(); err != nil {
@@ -221,8 +221,8 @@ func parseFileHeader(t testing.TB) {
 		t.Errorf("FileCreationTime Expected '1900' got:'%v'", record.FileCreationTimeField())
 	}
 
-	if record.FileIDModifier != "Q" {
-		t.Errorf("FileIDModifier Expected 'Q' got:'%v'", record.FileIDModifier)
+	if record.FileIDModifier != "A" {
+		t.Errorf("FileIDModifier Expected 'A' got:'%v'", record.FileIDModifier)
 	}
 	if record.recordSize != "094" {
 		t.Errorf("RecordSize Expected '094' got:'%v'", record.recordSize)
diff --git a/iatEntryDetail_test.go b/iatEntryDetail_test.go
index 0c71de5f..219d381a 100644
--- a/iatEntryDetail_test.go
+++ b/iatEntryDetail_test.go
@@ -92,7 +92,7 @@ func BenchmarkIATMockEntryDetail(b *testing.B) {
 
 // testParseIATEntryDetail parses a known IATEntryDetail record string.
 func testParseIATEntryDetail(t testing.TB) {
-	var line = "6221210428820007             000010000012345678901234567890123456789012345    1231380100000001"
+	var line = "6221210428820007             0000100000123456789012345678901234567890         1231380100000001"
 	r := NewReader(strings.NewReader(line))
 	r.addIATCurrentBatch(NewIATBatch(mockIATBatchHeaderFF()))
 	r.IATCurrentBatch.SetHeader(mockIATBatchHeaderFF())
@@ -118,8 +118,11 @@ func testParseIATEntryDetail(t testing.TB) {
 	if record.AmountField() != "0000100000" {
 		t.Errorf("Amount Expected '0000100000' got: %v", record.AmountField())
 	}
-	if record.DFIAccountNumberField() != "12345678901234567890123456789012345" {
-		t.Errorf("DfiAccountNumber Expected '12345678901234567890123456789012345' got: %v", record.DFIAccountNumberField())
+	if record.DFIAccountNumberField() != "123456789012345678901234567890     " {
+		t.Errorf("DfiAccountNumber Expected '123456789012345678901234567890     ' got: %v", record.DFIAccountNumberField())
+	}
+	if record.DFIAccountNumber != "123456789012345678901234567890" {
+		t.Errorf("DfiAccountNumber Expected '123456789012345678901234567890' got: %v", record.DFIAccountNumber)
 	}
 	if record.AddendaRecordIndicator != 1 {
 		t.Errorf("AddendaRecordIndicator Expected '0' got: %v", record.AddendaRecordIndicator)
diff --git a/reader_test.go b/reader_test.go
index d30a7e48..d3b53cd1 100644
--- a/reader_test.go
+++ b/reader_test.go
@@ -1802,6 +1802,7 @@ func TestCategoryAssignment(t *testing.T) {
 
 	// "Forward"
 	forwardEntry := mockEntryDetail()
+	forwardEntry.DFIAccountNumber = "1"
 	forwardEntry.Category = CategoryForward
 	forwardEntry.DiscretionaryData = "01"
 	forwardBatch := NewBatchWEB(mockBatchWEBHeader())
@@ -1813,10 +1814,10 @@ func TestCategoryAssignment(t *testing.T) {
 
 	// "Return"
 	returnEntry := mockEntryDetail()
+	returnEntry.DFIAccountNumber = "2"
 	returnEntry.Addenda99 = mockAddenda99()
 	returnEntry.AddendaRecordIndicator = 1
 	returnEntry.Category = CategoryReturn
-	returnEntry.DiscretionaryData = "02"
 	returnBatch := NewBatchWEB(mockBatchWEBHeader())
 	returnBatch.AddEntry(returnEntry)
 	if err := returnBatch.Create(); err != nil {
@@ -1826,10 +1827,10 @@ func TestCategoryAssignment(t *testing.T) {
 
 	// "DishonoredReturn"
 	dishonoredReturnEntry := mockEntryDetail()
+	dishonoredReturnEntry.DFIAccountNumber = "3"
 	dishonoredReturnEntry.Addenda99Dishonored = mockAddenda99Dishonored()
 	dishonoredReturnEntry.AddendaRecordIndicator = 1
 	dishonoredReturnEntry.Category = CategoryDishonoredReturn
-	dishonoredReturnEntry.DiscretionaryData = "03"
 	dishonoredReturnBatch := NewBatchWEB(mockBatchWEBHeader())
 	dishonoredReturnBatch.AddEntry(dishonoredReturnEntry)
 	if err := dishonoredReturnBatch.Create(); err != nil {
@@ -1839,10 +1840,10 @@ func TestCategoryAssignment(t *testing.T) {
 
 	// "DishonoredReturnContested"
 	contestedDishonoredReturnEntry := mockEntryDetail()
+	contestedDishonoredReturnEntry.DFIAccountNumber = "4"
 	contestedDishonoredReturnEntry.Addenda99Contested = mockAddenda99Contested()
 	contestedDishonoredReturnEntry.AddendaRecordIndicator = 1
 	contestedDishonoredReturnEntry.Category = CategoryDishonoredReturnContested
-	contestedDishonoredReturnEntry.DiscretionaryData = "04"
 	contestedDishonoredReturnBatch := NewBatchWEB(mockBatchWEBHeader())
 	contestedDishonoredReturnBatch.AddEntry(contestedDishonoredReturnEntry)
 	if err := contestedDishonoredReturnBatch.Create(); err != nil {
@@ -1852,10 +1853,10 @@ func TestCategoryAssignment(t *testing.T) {
 
 	// "NOC"
 	nocEntry := mockCOREntryDetail()
+	nocEntry.DFIAccountNumber = "5"
 	nocEntry.Addenda98 = mockAddenda98()
 	nocEntry.AddendaRecordIndicator = 1
 	nocEntry.Category = CategoryNOC
-	nocEntry.DiscretionaryData = "05"
 	nocBatch := NewBatchCOR(mockBatchCORHeader())
 	nocBatch.AddEntry(nocEntry)
 	if err := nocBatch.Create(); err != nil {
@@ -1863,6 +1864,17 @@ func TestCategoryAssignment(t *testing.T) {
 	}
 	file.AddBatch(nocBatch)
 
+	// "Forward" (IAT entry)
+	forwardIATEntry := mockIATEntryDetailWithAddendas()
+	forwardIATEntry.DFIAccountNumber = "6"
+	forwardIATEntry.Category = CategoryForward
+	forwardIATBatch := NewIATBatch(mockIATBatchHeaderFF())
+	forwardIATBatch.AddEntry(forwardIATEntry)
+	if err := forwardIATBatch.Create(); err != nil {
+		t.Fatal(err)
+	}
+	file.AddIATBatch(forwardIATBatch)
+
 	if err := file.Create(); err != nil {
 		t.Errorf("%T: %s", err, err)
 	}
@@ -1883,18 +1895,24 @@ func TestCategoryAssignment(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	categoriesByDiscretionaryData := map[string]string{}
+	categoriesByAccountNumber := map[string]string{}
 	for _, batch := range readFile.Batches {
 		for _, entry := range batch.GetEntries() {
-			categoriesByDiscretionaryData[entry.DiscretionaryData] = entry.Category
+			categoriesByAccountNumber[entry.DFIAccountNumber] = entry.Category
+		}
+	}
+	for _, iatBatch := range readFile.IATBatches {
+		for _, iatEntry := range iatBatch.GetEntries() {
+			categoriesByAccountNumber[iatEntry.DFIAccountNumber] = iatEntry.Category
 		}
 	}
 
-	require.Equal(t, CategoryForward, categoriesByDiscretionaryData["01"])
-	require.Equal(t, CategoryReturn, categoriesByDiscretionaryData["02"])
-	require.Equal(t, CategoryDishonoredReturn, categoriesByDiscretionaryData["03"])
-	require.Equal(t, CategoryDishonoredReturnContested, categoriesByDiscretionaryData["04"])
-	require.Equal(t, CategoryNOC, categoriesByDiscretionaryData["05"])
+	require.Equal(t, CategoryForward, categoriesByAccountNumber["1"])
+	require.Equal(t, CategoryReturn, categoriesByAccountNumber["2"])
+	require.Equal(t, CategoryDishonoredReturn, categoriesByAccountNumber["3"])
+	require.Equal(t, CategoryDishonoredReturnContested, categoriesByAccountNumber["4"])
+	require.Equal(t, CategoryNOC, categoriesByAccountNumber["5"])
+	require.Equal(t, CategoryForward, categoriesByAccountNumber["6"])
 }
 
 // TestACHFileTooLongErr checks that it errors on a file that is too long
diff --git a/test/ach-adv-write-multipleBatches/main.go b/test/ach-adv-write-multipleBatches/main.go
index 4cb60d6b..5095d90a 100644
--- a/test/ach-adv-write-multipleBatches/main.go
+++ b/test/ach-adv-write-multipleBatches/main.go
@@ -47,10 +47,7 @@ func main() {
 		bh.ODFIIdentification = "121042882"
 		bh.OriginatorStatusCode = 0
 
-		batch, err := ach.NewBatch(bh)
-		if err != nil {
-			log.Fatalf("%T: %v", err, err)
-		}
+		batch, _ := ach.NewBatch(bh)
 
 		// Create Entry
 		entrySeq := 0
diff --git a/test/issues/issue702_test.go b/test/issues/issue702_test.go
index ab3e2113..7bddd8d3 100644
--- a/test/issues/issue702_test.go
+++ b/test/issues/issue702_test.go
@@ -24,8 +24,6 @@ import (
 	"testing"
 
 	"github.com/moov-io/ach"
-
-	"github.com/stretchr/testify/require"
 )
 
 func TestIssue702(t *testing.T) {
@@ -51,8 +49,6 @@ func TestIssue702(t *testing.T) {
 		t.Error(err)
 	}
 
-	require.Equal(t, "B", file.Header.FileIDModifier)
-
 	if file.Header.ImmediateDestination != "YYYYYYYYY" {
 		t.Errorf("file.Header.ImmediateDestination=%s", file.Header.ImmediateDestination)
 	}
@@ -90,8 +86,6 @@ func TestIssue702_1(t *testing.T) {
 		}
 	}
 
-	require.Equal(t, "B", file.Header.FileIDModifier)
-
 	if file.Header.ImmediateOrigin != "182327390" {
 		t.Errorf("ImmediateOrigin=%s", file.Header.ImmediateOrigin)
 	}
diff --git a/test/issues/testdata/issue702-1.ach b/test/issues/testdata/issue702-1.ach
index bb309b75..0f01da14 100644
--- a/test/issues/testdata/issue702-1.ach
+++ b/test/issues/testdata/issue702-1.ach
@@ -1,4 +1,4 @@
-101 10006XXXX 1823273902003271043B094101PIMRET825324           FISERV
+101 10006XXXX 1823273902003271043 094101PIMRET825324           FISERV
 9000000000001000000000000000000000000000000000000000000
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/issues/testdata/issue702.ach b/test/issues/testdata/issue702.ach
index 34893ec5..03153938 100644
--- a/test/issues/testdata/issue702.ach
+++ b/test/issues/testdata/issue702.ach
@@ -1,4 +1,4 @@
-101 YYYYYYYYY 1823273902003310500B094101PIMRET825324           FISERV                      
+101 YYYYYYYYY 1823273902003310500 094101PIMRET825324           FISERV                      
 5220POSSIBLE FINANCE                    1823273909PPDLOAN            2003270911031101270002607
 621101206101686133344441     000000010260502a49fb51411Prasad Mahendra         1031101278009179
 799R04101206100000001      03110127PSEUDORETN                                  031101278009179
diff --git a/test/server-example/main.go b/test/server-example/main.go
index 98276ee8..f0982a82 100644
--- a/test/server-example/main.go
+++ b/test/server-example/main.go
@@ -27,7 +27,6 @@ import (
 	"github.com/moov-io/ach/server"
 
 	"github.com/go-kit/log"
-	"github.com/stretchr/testify/require"
 )
 
 func TestServer__CreateFile(t *testing.T) {
@@ -56,10 +55,7 @@ func TestServer__CreateFile(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if resp != nil && resp.Body != nil {
-		defer resp.Body.Close()
-	}
-	require.NotNil(t, resp)
+	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
 		t.Errorf("got %d HTTP status code", resp.StatusCode)
diff --git a/test/simple-file-creation/main.go b/test/simple-file-creation/main.go
index eb18c0da..989eb38c 100644
--- a/test/simple-file-creation/main.go
+++ b/test/simple-file-creation/main.go
@@ -47,11 +47,7 @@ func main() {
 	bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD
 	bh.ODFIIdentification = "121042882"
 
-	batch, err := ach.NewBatch(bh)
-	if err != nil {
-		fmt.Printf("%T: %v", err, err)
-		return
-	}
+	batch, _ := ach.NewBatch(bh)
 
 	// To create an entry
 	entry := ach.NewEntryDetail()
@@ -72,13 +68,13 @@ func main() {
 	entry.AddAddenda05(addenda)
 
 	// Entries are added to batches like so:
+
 	batch.AddEntry(entry)
 
 	// When all of the Entries are added to the batch we must create it.
 
 	if err := batch.Create(); err != nil {
 		fmt.Printf("%T: %s", err, err)
-		return
 	}
 
 	// And batches are added to files much the same way:
@@ -96,11 +92,7 @@ func main() {
 	bh2.EffectiveEntryDate = time.Now().AddDate(0, 0, 1).Format("060102") // YYMMDD
 	bh2.ODFIIdentification = "121042882"
 
-	batch2, err := ach.NewBatch(bh2)
-	if err != nil {
-		fmt.Printf("%T: %v", err, err)
-		return
-	}
+	batch2, _ := ach.NewBatch(bh2)
 
 	// Add an entry and define if it is a single or recurring payment
 	// The following is a recurring payment for $7.99
@@ -128,7 +120,6 @@ func main() {
 	// Create and add the second batch
 	if err := batch2.Create(); err != nil {
 		fmt.Printf("%T: %s", err, err)
-		return
 	}
 	file.AddBatch(batch2)
 
@@ -136,7 +127,6 @@ func main() {
 
 	if err := file.Create(); err != nil {
 		fmt.Printf("%T: %s", err, err)
-		return
 	}
 
 	// Finally we wnt to write the file to an io.Writer
diff --git a/validators_test.go b/validators_test.go
index 3cf1b976..ef118a8a 100644
--- a/validators_test.go
+++ b/validators_test.go
@@ -183,7 +183,7 @@ func TestValidators__isAlphanumeric(t *testing.T) {
 func TestValidators__isAlphanumericExamples(t *testing.T) {
 	v := validator{}
 
-	validCases := []string{"Acme Corp!", `|`, `¦`, `¢`, `¬`, `±`}
+	validCases := []string{`|`, `¦`, `¢`, `¬`, `±`}
 	for i := range validCases {
 		err := v.isAlphanumeric(validCases[i])
 		require.NoError(t, err, fmt.Sprintf("input: %q", validCases[i]))