Skip to content

No test checks that a display-only label change leaves the structure tree unchanged #557

Open

Description

@willhea

What needs doing

Add a test that changes only how headings are displayed and fails if the structure DeltaTrack reports changes. Today, nothing checks that display and structure are independent. A change to a label's wording, case or punctuation can retype or regroup the structure tree, and the suite stays green. Work in #778 (the epic to show bills the way GPO prints them) is about to make exactly those changes.

Some background, for anyone new to this:

  • The structure tree. src/deltatrack/structure_tree.py rebuilds each version's flat node list into a nested tree. Each node has a level (division, title, major for a department, agency, account for the named pot of money, section, …). The tree drives the Sections list in the Full bill view and the tree field of the canonical diff JSON (the published output contract).
  • Display label vs match path. display_path is the breadcrumb a reader sees. match_path is the normalised key the diff pairs nodes on. Only the display form is meant to change with styling.
  • Why it's fragile. The tree nests nodes by their display breadcrumb and types some containers by pattern-matching it. Any label change therefore reaches the tree. A change to how a division heading is displayed silently rewires which sections the diff compares #468 (closed 2026-08-04: changing how a division was displayed rewired which sections the diff compared) is the one time this has shipped. Its guard, TestDivisionMatchKeyIndependence (tests/test_diff_bill.py:1581), checks diff pairing only, not the tree.

The gate would be red today (checked 2026-10-05 on develop, d3935ee)

A prototype swapped build_division_label to GPO's DIVISION A—Header and compared trees by shape and level, ignoring label text:

  • XML, 118-hr-4366 enrolled. 13 node positions differ. The seven divisions become heading, and Front Matter loses Sec. 1–Sec. 6 to the top level. Across the committed corpus, all 201 division nodes in 15 files lose their level.
  • PDF, 118-hr-4366 and 118-hr-2882 engrossed amendments. The same happens.

That's #471 (a division's level is read from its display label). So #557 and #471 should ship in one PR: the gate shows red, the fix turns it green.

A second transform, upper-casing every breadcrumb segment, changes the tree in 16 XML files. In 6 of them two nodes merge, because the same account appears with different source casing under one agency. For example, 113-hr-3547 (engrossed amendment) has Salaries and expenses and salaries and expenses under Office of personnel management. So a case-only label change can merge nodes, because the address is the label (#552, the parent epic, point on addresses).

Why it matters

Done when

  • A test builds the tree for real omnibus fixtures (XML and PDF), changes only the division display form, and asserts the trees match on everything except label text. That means the same nodes, in the same nesting, with the same levels and the same source node behind each.
  • It was shown red on develop, and the PR description says so.
  • It's green after A division's level is read back from its display label, so showing divisions GPO-style would strip it in both pipelines #471's fix, in the same PR.
  • Both label seams can be switched by the test, ideally through one shared function. Today they're build_division_label (bill_tree.py:562) for XML and an inline f-string in _assign_divisions (parsers/pdf_anchors.py:857) for PDF.
  • Fast and slow suites pass.

Scope

Where to start

  • Copy the pattern of TestDivisionMatchKeyIndependence. It monkeypatches build_division_label and proves the label really changed before asserting.
  • Fixtures: tests/corpus/118-hr-4366/ has XML and PDF for each version; the engrossed amendment (5_…) has divisions in both. For PDF pages, use tests.pdf_corpus.cached_pages.
  • Compare trees with build_xml_tree / build_pdf_tree from structure_tree.py. Walk them as (depth, level, child count, source id) tuples.
  • Read tests/division_labels.py. It explains why test baselines still parse labels and how it guards against a silent zero.
  • Related PR: Show every heading the XML tags in its breadcrumb, and measure the ledger on each kind of bill that appropriates #739 (a draft PR keeping every XML heading in the breadcrumb) lists this issue as related but doesn't add the gate.

Unverified

  • Whether title labels (TITLE I—…, read by _TITLE_RE) need a variant in the gate. _TITLE_RE is case-insensitive and today's label already uses GPO's form.
  • The PDF prototype rewrote the anchors' division field instead of patching pdf_anchors.py.

History

Filed 2026-08-06 alongside #552. It originally offered either the #66 form or a synthetic transform as the alternate label. The measurements above favour starting with the #66 form.

Activity

  1. changed the title [-]A display-only change to a heading can alter the structure tree, and no test would catch it[/-] [+]No test checks that a display-only label change leaves the structure tree unchanged[/+] on Oct 5, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions