Skip to content

Maxed-OSS/ofx-normalizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ofx-normalizer

Parse OFX/QFX bank downloads and CSV bank exports into one normalized transaction schema. A small Go library and a single static CLI binary, with no runtime dependencies.

Banks hand you two very different things: an OFX or QFX download (SGML or XML), or a CSV with whatever column names that bank happened to choose. This tool reads both and emits the same clean JSON every time: signed decimal amounts, a consistent sign convention, dates as YYYY-MM-DD, and an explicit debit/credit direction. Drop it into an import pipeline and the rest of your code only ever sees one shape.

What it does

  • OFX / QFX - reads both export styles in one tokenizer: OFX 1.x SGML (the unclosed <TRNAMT>-42.00 tag style with a colon-delimited header) and OFX 2.x XML. Pulls account id, type, routing number, and currency from the statement, and per-transaction date, amount, type, memo, check number, and the FITID stable id.
  • CSV - handles both the single signed-amount layout and the split debit/credit layout, with fuzzy header detection across a broad alias table (Date / Posting Date / Transaction Date, Description / Memo / Payee, and so on), so the common exports from major banks parse without per-bank config.
  • One schema - every parse yields the same Statement shape with a stable schema version, so downstream code is written once.
  • Money-safe - amounts stay decimal strings, never binary floats. It understands currency symbols, thousands separators, accounting parentheses for negatives, and trailing-minus signs.

Install

Build the CLI from source (Go 1.22 or newer):

go build -o ofxnorm ./cmd/ofxnorm

Or install it onto your PATH:

go install github.com/maxed-oss/ofx-normalizer/cmd/ofxnorm@latest

Use it as a library:

go get github.com/maxed-oss/ofx-normalizer

CLI usage

# Auto-detect format from the file extension and content.
ofxnorm statement.ofx
ofxnorm export.csv

# Read from stdin and force a format.
cat download.qfx | ofxnorm --format ofx

# Some banks export spending as a positive number; flip the sign convention.
ofxnorm --format csv --invert-amounts export.csv

# Compact output.
ofxnorm --pretty=false statement.ofx

Flags:

Flag Default Meaning
--format auto auto, ofx, or csv
--pretty true pretty-print the JSON
--invert-amounts false flip the sign of the CSV signed-amount column
--json-errors false on failure, print a JSON error envelope to stderr
--version print version and exit

Exit codes are stable: 0 on success, 1 on a parse or input error, 2 on a flag/usage error. With --json-errors, a failure prints {"ok": false, "error": {"type": "parse_error", "message": ...}} to stderr.

Output schema

The tool speaks one format, version 1.0:

{
  "schema_version": "1.0",
  "source_format": "ofx",
  "account": {
    "id": "0000123456789",
    "type": "CHECKING",
    "routing_number": "121000248"
  },
  "transactions": [
    {
      "date": "2024-01-05",
      "amount": "-42.00",
      "currency": "USD",
      "direction": "debit",
      "description": "COFFEE SHOP - CARD PURCHASE",
      "type": "DEBIT",
      "id": "202401050001"
    }
  ]
}

Field notes:

  • amount is a signed decimal string. Money leaving the account is negative (a debit); money entering is positive (a credit). This matches the OFX TRNAMT convention and is applied uniformly to CSV input too.
  • direction is "debit" or "credit", derived from the sign of amount and provided explicitly so consumers do not re-derive it.
  • date is always YYYY-MM-DD.
  • id is the source stable id when present (the OFX FITID), which makes a strong dedup key.
  • Optional fields (currency, type, check_number, account metadata) are emitted only when the source provides them.

Library usage

package main

import (
	"fmt"

	"github.com/maxed-oss/ofx-normalizer/pkg/csvbank"
	"github.com/maxed-oss/ofx-normalizer/pkg/normalize"
	"github.com/maxed-oss/ofx-normalizer/pkg/ofx"
)

func main() {
	stmt, err := ofx.Parse(ofxContent)
	if err != nil {
		panic(err)
	}
	fmt.Println(len(stmt.Transactions), "transactions")

	// CSV with options.
	csvStmt, err := csvbank.Parse(csvContent, csvbank.Options{InvertAmounts: true})
	if err != nil {
		panic(err)
	}

	// Detect the format when you do not know it up front.
	switch normalize.Detect(content, "download.qfx") {
	case normalize.FormatOFX:
		// ...
	case normalize.FormatCSV:
		// ...
	}
}

Packages:

  • pkg/normalize - the shared Statement and Transaction schema, plus the amount, date, and format-detection helpers.
  • pkg/ofx - the OFX/QFX parser.
  • pkg/csvbank - the CSV parser.

The standards it speaks

  • OFX / QFX - Open Financial Exchange, the long-standing bank-download format, in both the 1.x SGML and 2.x XML serializations.
  • Output - the normalized JSON schema documented above, version 1.0.

Determinism

Given the same input, ofx-normalizer produces the same output, byte for byte. Parsing does not depend on machine locale, and amounts are kept as decimal strings, so the data round-trips cleanly into any bookkeeping or reconciliation step that consumes it.

Development

go vet ./...
go test ./...

Tests are table-driven and run against the fixture files in testdata/.

Agent interface

For AI agents and automation: pipe a statement to ofxnorm - and read normalized JSON on stdout; add --json-errors for a structured error envelope on stderr and branch on the stable exit codes above. The full machine interface is in AGENT.md and llms.txt. This binary backs the normalize_ofx tool in maxed-mcp.

License

MIT. See LICENSE.

About

Parse OFX/QFX bank downloads and CSV bank exports into one normalized transaction schema. A small Go library plus a single static CLI binary, no runtime dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages