Lacuna Index — We read between the lines. Forensic analysis · Pure evidence · Accountable insight.
◂ JOURNAL
·LacunaIndex Team·6 min read

Sep-End vs. Dec-End: The Silent Bug That Breaks Every Peer Comp

Peer-multiple tables assume every row measures the same thing over the same period on the same accounting basis, an assumption that fails silently far more often than diligence analysts check for, and we built (then had to fix) a guard that catches it.

Sep-End vs. Dec-End: The Silent Bug That Breaks Every Peer Comp

Every peer-multiple table starts with an assumption nobody checks: that the numbers in each row measure the same thing, over the same period, in the same currency, on the same accounting basis. In practice that assumption fails constantly, and it fails silently — the spreadsheet still produces a number, the number still looks plausible, and nothing tells you it's wrong.

We ran into this directly while rebuilding how LacunaIndex sources structured financials, and the fix ended up being one of the more useful things we've built: a set of explicit checks that run before any peer median gets calculated, each one designed to catch a specific way "comparable" companies turn out not to be.

Where the numbers actually come from

Until recently, LacunaIndex's structured financials — revenue, net income, earnings per share, total assets, operating cash flow — came from Financial Modeling Prep, a commercial data vendor. That's a normal choice; most tools do this. But a data vendor is itself a wrapper around the source filing, and every layer between you and the original disclosure introduces a chance for rounding, period misalignment, or metric substitution to sneak in unflagged.

For a product whose whole pitch is that its numbers should match what's actually in the filing, that's the wrong foundation. So we moved to making SEC XBRL the primary source for structured financials in peer sets, with the vendor feed demoted to a documented fallback for cases XBRL doesn't cover (foreign filers without U.S. GAAP tags, missing periods, and the like). XBRL — the Securities and Exchange Commission's eXtensible Business Reporting Language — is the machine-readable data every public company tags directly onto its own 10-K and 10-Q filings. It's one hop closer to the source than a vendor's derived feed.

That migration is what surfaced the real problem. Once you're reading facts straight from filings instead of a pre-normalized vendor feed, you inherit every messy real-world difference between filers that the vendor used to quietly paper over.

Seven ways a peer set is not what it looks like

We built a comparability check that runs on a peer set before any median or benchmark gets calculated from it. It evaluates seven separate axes, and each one maps to a real, observed way peer comps go wrong:

  • Fiscal calendar mismatch. Apple's fiscal year ends in late September; most of its peers close in December. Line up "the same quarter" by label and you're comparing periods that don't overlap. The check looks for peers reporting a different fiscal period end within roughly a three-month band and flags the comparison as measuring non-coincident quarters.
  • Accounting taxonomy mix. A peer set that blends U.S. filers (U.S. Generally Accepted Accounting Principles) with foreign filers (International Financial Reporting Standards) is mixing two different rulebooks for what counts as revenue or income. The concepts can be mapped to each other, but the raw values aren't interchangeable without adjustment — the check flags the peer set rather than silently summing across standards.
  • Segment versus consolidated reporting. Some filers report a metric only as a breakdown by business segment for a given period, with no consolidated total tagged. Summing segment rows as if they were the whole company is a specific, easy-to-miss error, and the check refuses to treat segment-only data as if it were a company-wide figure.
  • Currency mixing. A monetary figure tagged in Australian dollars sitting next to one tagged in U.S. dollars is a straightforward category error if nobody converts them first. The check identifies the majority currency in a peer set and flags any filer reporting in a different one.
  • Coverage gaps. If a filer simply has no tagged data for the metric and period in question, the check says so explicitly rather than letting a fallback data source fill the gap invisibly.
  • Restatement awareness. When a filer has amended a filing, more than one version of a fact can exist for the same period. The check flags peer sets touched by a restatement so a reader knows a revised value, not the original one, is in play.
  • Concept-registry drift. The mapping from a filer's XBRL tag to a standardized metric name is versioned. If peers in a set were captured under different mapping versions, the check surfaces that as a low-severity note, since a later mapping change could shift which tag variant gets used.

Each finding carries a severity — informational, a warning, or a hard block — and a block-severity finding on a metric excludes that company from the peer median for that comparison rather than quietly including a number that isn't actually comparable.

The failure that made this non-optional

This isn't a hypothetical concern we designed around in the abstract — it's the direct result of a production incident. Two related defects made it into a shipped comparability feature before anyone caught them: a live read path that checked whether a ticker had been explicitly dropped from a sector's comparison set, but never checked whether that sector had been evaluated at all — so an issuer sitting in a sector that had simply never been assessed was treated as clean by default. Separately, the step that was supposed to persist a sector's evaluation result silently failed against a permissions error and just logged a warning instead of raising an alarm, so the "last evaluated" timestamp stayed empty for every sector for more than two weeks with nothing surfacing the gap.

Neither bug showed up in a unit test, and neither was caught by "it's wired into the pipeline." What caught it was someone actually querying production data and looking at the result by hand. That's now a standing rule for anything touching a production write path here: a component isn't done until a live query against real data confirms the behavior, observed by a person — not just green tests and a deployed pipeline.

The fix that came out of it is the "fail closed" behavior visible in the current code: if a sector has never been evaluated for comparability, or if the evaluation read fails for any reason, the system now treats that peer set as unverified rather than clean, and suppresses a confident verdict rather than defaulting to silence. Unverified and clean are different states, and collapsing them into one was the entire bug.

What this means if you're building comps by hand

You don't need our pipeline to apply the same discipline to your own peer tables. Before trusting a peer median or a relative-multiple screen, it's worth checking, for each name in the set:

  • Do the fiscal period end dates actually line up, or are you averaging a September close against three December closes?
  • Is everyone reporting under the same accounting standard, or are you mixing U.S. GAAP and IFRS filers without adjustment?
  • Is the figure you're using a consolidated total for every peer, or a segment breakdown for some of them?
  • Is everything in the same currency?
  • Has any peer restated the period you're using, and are you using the current or the original figure?

None of these checks require special tooling — they require treating "is this peer set actually comparable" as a question with an answer, rather than an assumption the spreadsheet inherits by default. The uncomfortable finding from building this was how often the answer, once you actually check, is no.

Filed under: data-integrity · peer-comps · xbrl · methodology · diligence-workflow