Skip to content

Changes the renderer cannot locate are listed after the whole bill, so Front Matter reads last and a title appears twice #701

Description

@willhea

What's wrong

In a comparison report's Changes view, some of a bill's changes are listed after every other change instead of in bill order. In the published HR 8752 example this puts Front Matter, which is the first thing in the bill, below Title V, and splits Title V into two separate groups with Front Matter sitting between them:

TITLE I, TITLE II, TITLE III, TITLE IV, TITLE V, Front Matter, TITLE V

The Full bill view of the same report has Front Matter correctly first. The two views disagree because only the Changes view groups changes; the Full bill view renders the tree directly and never goes through the grouping.

Change cards are grouped under the node of the structure tree that contains them, by a span-containment join added in #172 (regroup change cards under leaf tree nodes). A change carries full_text_span, a character range into the rendered bill text, and each tree node carries one too; the join assigns a change to the deepest node whose span contains it.

A change with no span on either side cannot be placed by that join. Those changes fall to a flat bucket that the renderer emits after every tree-ordered group, and their position in the bill is discarded even though they still carry it.

How it surfaced

Reported from reading the published HR 8752 example. Measured against the committed artifacts on develop at 475d9bf0, by re-parsing the canonical JSON each report embeds and re-running the renderer's own grouping function.

The set of changes the join cannot place is exactly the set with no span, and every one of them carries a path breadcrumb naming where it belongs:

Report Changes Unplaced Spanless Same set Unplaced carrying a breadcrumb
hr4366 committee vs floor (XML) 25 0 0 yes 0
hr8752 (XML) 39 1 1 yes 1
hr8752 (PDF) 37 3 3 yes 3
hr4366 house vs senate (XML) 703 55 55 yes 55

The three unplaced changes in HR 8752 (PDF), with the breadcrumb each already carries:

[0]  modified  span {v1: null, v2: null}  path v1 ['Front Matter']  v2 ['Front Matter']
[20] removed   span {v1: null, v2: null}  path v1 ['TITLE V', 'GENERAL PROVISIONS', 'SEC. 552']  v2 null
[36] added     span {v1: null, v2: null}  path v1 null  v2 ['TITLE V', 'GENERAL PROVISIONS', 'SEC. 567']

Front Matter is not an unknown location to the renderer. The v2 tree ranks it first, at position 0, with the key ('Front Matter', 'preamble'), and the renderer builds that ranking in the same function call that then ignores it for this change.

Why it matters

The report's claim is that a reader can follow a bill in its own order. A reader scrolling the Changes view reaches what looks like the end of the bill and then finds a second, unordered run of changes.

The omnibus is where this stops being cosmetic. In hr4366 house vs senate, 55 changes land in ten trailing buckets, and those buckets include three whole Divisions (A, B and C), four titles, and three standalone sections. Someone auditing that report for what changed in Division B has no reason to expect part of Division B to be below the end of Division C.

The duplicate heading is its own problem. Title V appears twice as a top-level group, so a reader who has found "the Title V changes" has no signal that a second Title V group exists further down.

Nothing here is a regression: this is how the grouping has behaved since #172 shipped. There is no data loss, every change is still rendered, and the Full bill view is unaffected.

What to do

The information needed to place these changes is already in the contract, so this does not need a diff-engine or schema change.

  1. Place a spanless change by its breadcrumb. path names the containing division, title and section on whichever side exists. The renderer already builds a (label, level) ranking of the v2 tree for ordering, so a spanless change could be ranked by matching its breadcrumb against that map instead of being appended. The mismatch to resolve is that path carries bare labels while the ranking is keyed by label and level together.

  2. Merge same-label groups rather than emitting a second one. Whatever ordering is chosen, a group whose label already exists at that level should join it. This alone removes the duplicate Title V without solving the ordering.

  3. Investigate why these changes have no span. Both HR 8752 cases are pure additions or removals, where one side legitimately has no position, but the spans are null on both sides. Regroup change cards under leaf tree nodes (span-containment join) #172 specifies "prefer v2, fall back to v1 for removals", so the v1 fallback is not producing a span here. This may be the more useful fix, since a change with a real span is placed correctly by the existing join and needs no new ordering path.

Worth deciding whether this is the view's problem to solve at all. #653 (make the view a consumer of the diff, not a second producer of it) argues the grouping is a decision the diff should settle rather than something the renderer re-derives; if that direction is taken, options 1 and 2 are workarounds inside a layer that should not be making the choice.

Verification

The suite passes today with this defect present, so a green run is not evidence. The check is that the Changes view's top-level group order matches bill order, on a report that has spanless changes: HR 8752 (PDF) has three and the omnibus has 55, while hr4366 committee vs floor has none and would pass vacuously.

For the duplicate specifically, assert that no label appears twice among the top-level groups of one report.

Unverified

Whether the 55 unplaced changes in the omnibus are spanless for the same reason as the two in HR 8752 was not investigated; only that they are spanless and carry breadcrumbs.

Whether any report in the wider corpus has enough unplaced changes to reorder a majority of its groups is unmeasured. Only the four published examples were checked.

Refs #172 (regroup change cards under leaf tree nodes, the span-containment join this grouping came from), #653 (make the view a consumer of the diff, not a second producer of it), #163 (display and functionality directions, where the finer-grained blocks work started).

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions