Skip to content

Trimodal heading sizes make size-band derivation bail, so reconciliation bills get no account structure #508

Description

@willhea

What happens

derive_size_bands (src/deltatrack/parsers/pdf_anchors.py) returns None when a
document's sub-body heading sizes form more than one strong cluster. Account
detection is glyph-size based, so returning None means the bill gets no
account-level anchors at all
— its deepest breadcrumb is TITLE/SEC.

Since #114 retired the For necessary expenses text-trigger fallback (ADR 0018),
there is no second path behind the size bands. Widening their coverage is now the
only way a bill in this state gets its account structure back.

Why it bails

Reconciliation bills print in three heading sizes rather than the two that
appropriations bills use. The clustering step splits the sub-body heading sizes,
finds a second cluster at >=30% of the dominant one, and bails rather than guess
which cluster is the account band:

dominant = max(clusters, key=len)
# More than one *strong* cluster (>=30% of the dominant) => trimodal => bail.
if any(c is not dominant and len(c) >= 0.3 * len(dominant) for c in clusters):
    return None

Bailing was the right call when it was written — a wrong band assignment mislabels
every heading in the document, and there was a fallback to land on. The fallback is
now gone, so the cost of bailing has gone up.

Affected bills

On the current 68-PDF corpus this is 119-hr-1 (engrossed-in-house,
placed-on-calendar-senate, engrossed-amendment-senate — ~1,000 pages each). The
other bills that reach the degraded path are unaffected by this issue: 11
enrolled/public-law files are declined outright as unnumbered layouts (#141, and
#261 tracks a pass for them), and 19 introduced/engrossed shells of 3-14 pages
contain no accounts to find.

Measured 2026-08-04 against develop. Note the PDF corpus is smaller than the XML
corpus, so treat the bill list as a floor rather than a census.

What "done" looks like

derive_size_bands returns usable bands for a trimodal document — picking the
account band on evidence rather than on cluster size alone — and 119-hr-1 gains
account anchors, without changing the anchors any currently-working bill produces.
The anchor goldens (test_pdf_anchor_golden.py) are the regression check for that
second half: they must not move.

Worth confirming before building: that reconciliation bills have an account level
worth recovering. 119-hr-1 is not an appropriations bill, and the one anchor the
retired trigger produced on it was a wrapped section-heading fragment, not an
account. It may be that the right band assignment yields sections, not accounts —
which would still be a coverage improvement, just a different one than the title
suggests.

Context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions