Taxes ledger — unverified buckets, payment-not-recorded, 1099 watch rules

This article explains the console's Taxes tab as a filing ledger: which obligations it enumerates and their due dates, how each row gets its status, what the Unverified and Needs your verification buckets mean, why a payment not recorded flag exists, and the rules behind the 1099-NEC watch — both the Mercury-based list on the Taxes tab and the 1099-NEC pill in Payroll's People · YTD table.

A ledger, not a calendar

The first version of this tab computed "next due date on or after today". That cannot hold anyone accountable: a period that was never filed silently rolls to the next one, and the tab would have shown "ST-1 — due Aug 20, OK" while May and June sat unfiled. So the module (pepper-ui/backend/taxes-data.js) enumerates every period that was expected, joins it against a record of what was actually filed, and treats "expected, past due, no record" as overdue until someone marks it.

The record is data/tax-filings.json, keyed obligation:period (e.g. st1:2026-07, 1099:2026, est:2026-Q3). It was seeded once from scheduled-tasks/st1-filing/st1_state.json, which the ST-1 automation has written since 2025-12; on import, an ST-1 period with il_paid becomes paid, one with only il becomes filed, and a Places for Eating rf payment becomes paid. The ledger starts at the earliest period on file, or 2025-12 if empty.

The page is served from a 6-hour cache — the longest in the console — so a page load busts it and the refresh button forces a rebuild. Every mark also busts it and pushes a taxes broadcast to other open consoles.

Obligations and due dates

ObligationPeriodDueLead (shows as "Due soon")Owner
ST-1 Sales TaxMonthly20th of the following month5 daysMaciej — /sales-tax
Places for Eating TaxMonthly20th of the following month5 daysMaciej — /sales-tax
IL LLC Annual ReportAnnualJan 121 daysMaciej
1099-NEC/MISC filing (Mercury)AnnualJan 3121 daysMaciej
Annual return (1120-S + K-1s)AnnualMar 1530 daysCPA
Estimated tax — Q1 / Q2 / Q3 / Q4Quarterly, labelled by tax yearApr 15 / Jun 15 / Sep 15 / Jan 15 of the next year14 daysMaciej + CPA

Monthly rows are generated from the ledger start through the current month, but only where the due date is within 45 days; annual and quarterly rows run from the ledger's first year through next year, capped 400 days out, so one upcoming occurrence is always visible. Q4's estimated payment for year Y lands on Jan 15 of Y+1 and is labelled by the tax year, not the calendar year.

How a row gets its status

Checked in this order:

StatusRuleBucket on screen
PendingThe ledger entry is pending — Pepper staged something and is waiting for an explicit yes. Checked before any date math, so it shows even if the due date is still ahead.Needs your verification
Paid / Filed / N/AMarked in the ledger.On record
UnverifiedDue date is past, no entry, and the due date is earlier than the first recorded filing for that obligation.Unverified — due before Pepper tracked it
OverdueDue date is past, no entry, and it falls after that obligation's tracking start.Overdue — filed nothing, past due
Due soonDue within the obligation's lead days.Due soon
UpcomingEverything else.Upcoming

The Unverified bucket

Tracking for each obligation starts at its own earliest recorded filedDate — ST-1 has automation records back to 2025-12, Places for Eating only from 2026-04, and the annual and estimated items were never tracked before the ledger existed. Missing periods after that date are real misses; missing periods before it are unknown. Conflating the two produces a wall of red that trains you to ignore the tab, which is the same failure as showing nothing. So those rows sit in a collapsible list, are deliberately excluded from the nav badge, and each one only needs confirming once (Confirm opens the same mark form). Pepper having no record does not mean the period was missed.

Needs your verification (Pending)

Confirm promotes the entry to paid, keeping whatever amount, confirmation and notes were staged. Not yet clears the entry back to the computed state — it means "don't trust this record yet", not "it didn't happen", the same as the ✕ on an On-record row. Nothing is ever auto-promoted to paid or filed. [check: the only writer of pending is POST /api/taxes/filings/:key with status: "pending"; no script in this checkout calls it yet, so which automation stages a pending tax row is still to be wired — scripts/mercury-pay-request.sh writes pending to its own payment store, not to the tax ledger.]

Payment not recorded

The mark form has three states: Filed and paid, Filed — payment still outstanding, Not applicable this period. A row left at filed shows a payment not recorded flag in On record and keeps it until you mark it paid. The reason is stated on the form: filing without paying is exactly how April 2026 accrued a penalty balance — the return went in, the balance did not, and nothing said so. Amount and confirmation number are optional but worth entering; they show on the row.

1099-NEC/MISC threshold watch (Taxes tab)

The section sums this calendar year's outgoing Mercury payments per counterparty, across every Mercury account, and lists anyone at or approaching the threshold. It mirrors scripts/tax-1099-check.py, which the /taxes skill runs quarterly from a Todoist prompt.

RuleValue
Threshold$600 in the calendar year (THRESHOLD = 600) — badge $600+.
Early warning$400 (WARN_AT = 400) — badge near.
CountedNegative (outgoing) Mercury transactions with a counterparty name.
Excluded by kindInternal transfers, and card payments (debitcardtransaction, creditcardtransaction) — card and network payments are 1099-K territory, reported by the card network, not 1099-NEC.
Excluded by nameSubstring list: IL Dept of Revenue, Village of River Forest, IRS, ComEd, Nicor Gas, Waste Management, Forest Park National Bank, Mercury Credit, Ramp statement, Check Payroll, Dripos monthly, Shopify payout, and the owner/investor names (Filip, Maciej, Monika). Add to EXCLUDED_SUBSTRINGS in both the script and taxes-data.js when a new recurring non-vendor payee appears.
Goods vendorsMay appear in the list. Merchandise does not need a 1099-NEC, only services — judge each name.
W-9Not exposed by the Mercury API. Every flagged row says confirm W-9 on file; cross-check in Mercury → Recipients by hand.
FilingDue Jan 31 (the 1099 ledger row). File through Mercury's native 1099 Filing in the dashboard — $5 per form, or free with Plus/Pro — once W-9s are confirmed.

If the Mercury call fails the section says so (Mercury check failed) rather than showing an empty, reassuring list.

The 1099-NEC pill in Payroll's People · YTD

Payroll's directory (PayrollView.svelte, People · YTD) shows a gold 1099-NEC pill beside a contractor's YTD figure when it has crossed $600. The pill's title was Crosses $600 — 1099-NEC required in January. This is a different counter from the Taxes tab's list:

Payroll pillTaxes tab list
SourceSupabase contractor_payments ledgerMercury transactions, all accounts
WhoRoster contractors paid through the dashboardEvery counterparty — contractors and vendors alike
Counted rowsStatus paid, event date in this calendar year, excluding reimbursements (rows with no hours and no rate)All outgoing non-card, non-excluded payments
TriggerYTD ≥ $600 (nec1099: ytd >= 600)≥ $400 near, ≥ $600 over

A person can carry the Payroll pill and not appear on the Taxes list (paid outside Mercury, or paid by card), or appear on the Taxes list with no pill (a vendor, not a roster contractor). Use the Taxes list for the January filing; use the Payroll pill as the early nudge to get the W-9 before the next payment.

Key facts panel

Static facts held in taxes-data.js#TAX_FACTS: entity is an LLC taxed as an S-Corp; payroll tax is handled entirely by Dripos and is not a Pepper responsibility; the CPA is being replaced; 1099 filing tool is Mercury's dashboard; reasonable compensation — Maciej $60,000/yr effective 2026-04-02, live in Dripos payroll; Monika unaddressed, pending Maciej. Edit the constant, not the screen.


Was this article helpful?