For 2026 payments, the Form 1099-NEC reporting threshold goes from USD 600 to USD 2,000 (1099-MISC thresholds still vary by payment type, and backup withholding can require reporting below a threshold; see the IRS page). If you keep the books for a small business, or write scripts for someone who does, this year's vendor list needs a fresh pass.
The awkward part isn't the threshold; it's the export. Here's a small, defensive way to turn a messy payments export into a reviewable vendor worksheet. This is data preparation, not tax advice.
The problems in a typical export
- The same vendor appears as
Acme Plumbing LLC,ACME PLUMBINGandAcme Plumbing, L.L.C. - QuickBooks reports (e.g. Transaction List by Vendor) put title lines above the header, put the vendor name on its own "group" row, and add
Total for …rows, which double-count if you sum naively - Dates in several formats, some blank or
TBD - Amounts like
$1,200.00,(450.00)and-$75 - Payments from other years mixed in
Rules that keep the totals honest
- Find the real header row. Skip leading lines until a row has at least two header-like cells (date, amount, name, vendor, memo).
-
Inherit the vendor from group rows and skip
Total for/TOTALrows. -
Group by vendor ID if you have one; otherwise by exact name (case, punctuation and spacing normalised). Don't merge
Acme LLCandAcme Incautomatically. Flag them as possible same vendor and let a human confirm. - Never guess a date. Unparseable or missing dates go to a review list, not into the year's total.
- Never drop a bad amount silently. Invalid amounts go to the review list too.
- Reconcile. Input total = this year's total + other years + review rows. If that doesn't hold, stop.
- Report TIN presence, not TIN values. The worksheet only needs a yes/no.
A compact Python version
import csv, re, datetime, collections
def money(s):
s = (s or "").replace("$", "").replace(",", "").replace("USD", "").strip()
neg = s.startswith("(") and s.endswith(")") or s.startswith("-")
s = s.strip("()-")
try:
return -float(s) if neg else float(s)
except ValueError:
return None
def year_of(s):
for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%m/%d/%y", "%b %d, %Y", "%d %b %Y"):
try:
return datetime.datetime.strptime((s or "").strip(), fmt).year
except ValueError:
pass
return None # -> review, never assumed
def key(name):
k = re.sub(r"['\u2019`.]", "", name.lower())
return re.sub(r"[^a-z0-9]+", " ", k).strip()
raw = list(csv.reader(open("payments.csv", encoding="utf-8-sig")))
hi = next(i for i, r in enumerate(raw)
if sum(bool(re.search(r"date|amount|name|vendor|memo", c, re.I)) for c in r) >= 2)
hdr = [h.strip() for h in raw[hi]]
iD, iA = hdr.index("Date"), hdr.index("Amount")
iN = hdr.index("Name") if "Name" in hdr else None
totals, review, group = collections.defaultdict(float), [], ""
tin_total = inc = other = rev = 0.0
for line, r in enumerate(raw[hi + 1:], start=hi + 2):
cells = [c.strip() for c in r]
if not any(cells):
continue
first = next(c for c in cells if c)
if re.match(r"^(total|grand total)\b", first, re.I):
continue
if sum(bool(c) for c in cells) == 1 and money(first) is None:
group = first
continue
name = (cells[iN] if iN is not None else "") or group
amt, yr = money(cells[iA]), year_of(cells[iD])
if amt is not None:
tin_total += amt
if amt is None or yr is None or not name:
review.append((line, name, cells[iA], cells[iD]))
rev += amt or 0
continue
if yr != 2026:
other += amt
continue
totals[key(name)] += amt
inc += amt
assert abs(tin_total - (inc + other + rev)) < 0.005, "totals don't reconcile"
for k, v in sorted(totals.items(), key=lambda kv: -kv[1]):
print(f"{k:40s} {v:12,.2f} {'AT/OVER 2,000' if v >= 2000 else ''}")
print(len(review), "rows need review")
Swap the column names for your export. In real use you'd also keep the original spelling for display, add the look-alike check (strip llc|inc|ltd|co|corp and compare) as a flag rather than a merge, and carry a tin_present column if the export has one.
Don't want to run Python?
Disclosure: we built a browser version of exactly these rules: https://csv.thetrilliumcompany.com/1099/tool/ . It runs entirely in the browser, so the file is never uploaded. The preview is free; downloading the worksheet, the review list and the change log is USD 19. There's a before/after sample with fictional data at https://csv.thetrilliumcompany.com/1099/ . I'm Peter at The Trillium Company (Waterloo, Ontario); questions are welcome in the comments.
Top comments (0)