Engineering notes · 2026-09-14
The one-line .strip() that silently renamed every bank capital ratio
A $9,860 dollar figure calling itself a capital ratio is what caught this — not a test suite, not a lint rule, a sanity check against one real bank's actual public numbers.
The setup
Building a weekly read on which small/mid-cap banks show developing balance-sheet stress means pulling every U.S. bank's Call Report straight from the FFIEC's own free bulk data — the same regulatory filing every bank already has to submit, no account, no API key. Each schedule ships as a TSV with two header rows: a row of machine field codes (RCFA7204) and a row of human descriptions ("Tier 1 Leverage Capital Ratio") mapping code to meaning. Read both rows, zip them into a lookup, done — a five-minute parser.
It ran clean. It found every keyword it was asked to find. It produced numbers. Nothing about the output looked wrong, because a description-to-code mapping that's off by one column still returns a real code and a real, plausible-sounding value — the exact same failure shape as the EDGAR table-of-contents bug: wrong, but never empty.
Position 0 is only blank until you clean the line
The code row's first column is IDRSSD (the bank's ID). The description row's first column has no description for an ID field, so FFIEC ships it as a genuinely empty cell — a real, meaningful blank, not a formatting accident. Calling .strip() on the line before splitting on tabs was meant to clean up the trailing newline. It also silently consumes that leading blank cell, because .strip() strips whitespace from both ends of the string — and a leading tab character followed by more content still counts as leading whitespace before the split ever runs.
The result: the description row is now one field shorter than the code row, and every description lines up with the nextfield's code instead of its own. A keyword search across descriptions for "leverage ratio" confidently returns RCFA5311 — which is actually Tier 2 Capital, a dollar amount, not a ratio at all. The real leverage-ratio code, RCFA7204, sits one column over, quietly mislabeled as something else.
What caught it
Not a unit test — the fixture used to build the parser was synthetic and clean, so it couldn't reproduce a bug that only exists in the real file's exact byte layout. What caught it was pulling one real, known bank out of the live quarterly data and checking its "leverage ratio" against that bank's own public regulatory filing. $9,860 is not a ratio for any real bank, in any quarter. That single implausible value was the only signal — everything else about the run looked healthy.
# WRONG — looks harmless, silently shifts every description one column left
with open(path, encoding="latin-1") as f:
code_row = f.readline().strip().split("\t")
desc_row = f.readline().strip().split("\t") # <- the bug
# RIGHT — only strip the trailing newline, never the leading field
with open(path, encoding="latin-1") as f:
code_row = f.readline().rstrip("\n").split("\t")
desc_row = f.readline().rstrip("\n").split("\t")
# FFIEC's Call Report TSVs ship two full-width header rows:
# row 1: "IDRSSD\tRCFA5311\tRCFA7204\t..." (machine codes)
# row 2: "\tTier 2 Capital\tTier 1 Leverage Ratio\t..." (descriptions, position 0 BLANK)
#
# .strip() eats that leading blank cell along with the newline. Position 0
# in the description row is no longer "blank" (matching IDRSSD) — it's now
# "Tier 2 Capital" sitting under RCFA5311's own column, and every field
# after it is shifted one column left of where its code actually is.The general lesson
Government and regulatory data formats encode meaning in things a generic text-cleaning step assumes are noise — a genuinely blank leading cell, a fixed column count, a specific byte offset. Running a default line-cleaning function before you've read the format spec is exactly the kind of thing that passes every synthetic test and every casual glance at the output, then fails silently and specifically on the real file. The fix isn't a smarter parser; it's never trusting a new field-code mapping until you've checked at least one value against a source outside the file itself.
Same discipline as the filing-extraction pipeline behind Edge Thirteen's own picks and ValueFeed's weekly research packets: score and verify, don't trust the first plausible-looking match.
Share:X / TwitterLinkedIn
See the pipeline this feeds
Every week, Edge Thirteen screens roughly 5,000 U.S. stocks, reads the 10-K on every name that clears the numbers, and sends the genuine value picks — for $13/month. Cancel anytime.
Secure checkout via Stripe · cancel anytime · terms & refund policy.
Not ready for $13/month?
Get one real issue free, no card required — the same research, sent straight to your inbox.