What's wrong
Comparing the same two bill files gives a report with a different heading depending on which
way you asked. The published example on the project's site, the same comparison run from the
command line, and the same two files uploaded to the web page each name the versions
differently.
Measured on the committed fixture pair tests/corpus/118-hr-8752, comparing
1_reported-in-house.pdf to 2_engrossed-in-house.pdf:
| Surface |
Report heading |
scripts/render_examples.py, which builds the published examples |
v1: reported-in-house → v2: engrossed-in-house |
./diff_pdf.py |
reported-in-house → engrossed-in-house |
| Uploading the two files to the web page |
1_reported-in-house → 2_engrossed-in-house |
The same three-way split appears inside each report's embedded diff.json, the
machine-readable copy of the comparison that the download button hands out: the two CLI
variants and the upload disagree on both the label and the version_number of each side.
Two causes, which is why they are filed together. They share a fix and touch the same call
sites.
Version ordinals are known but not passed. compare_pdfs_html in
src/deltatrack/compare/pdf.py accepts start_version_number and end_version_number, the
bill's legislative ordinals (the 1 and 2 in 1_reported-in-house.pdf, meaning first and
second published version of that bill). scripts/render_examples.py passes them.
render_pdf_diff_html in src/deltatrack/diff_pdf.py, which is what ./diff_pdf.py calls,
does not, even though its own docstring names exactly that caller:
Pass the version numbers when the caller knows the bill's legislative ordinals (rendering
a numbered corpus file, not an upload) so the report heads itself identically to the XML
report for the same pair.
Two different filename-to-label algorithms. label_from_stem in
src/deltatrack/version_stems.py strips a leading <n>_ ordinal prefix, turning
1_reported-in-house into reported-in-house. _label_from_filename in web/app.py
strips path components and the file extension only, so the prefix survives into the heading.
It is the only one of the two with no test.
Between them, resolving a source document to its version identity is implemented at six
places in production code:
| Site |
Derives label |
Derives ordinal |
compare/xml.py::compare_xml_files_html |
yes |
yes |
diff_pdf.py::render_pdf_diff_html |
yes |
no |
diff_bill.py::cmd_compare, HTML branch |
inline |
inline |
diff_bill.py::cmd_compare, JSON branch |
no |
separate inline loop |
web/app.py::_label_from_filename |
different algorithm |
no |
scripts/render_examples.py::render_pdf_diff |
inline |
inline |
tests/test_canonical_baseline.py and tests/test_pdf_canonical_baseline.py re-derive it
again, which is how a change to the rule can leave the baselines agreeing with a copy of
itself rather than with the code.
A contributing cause worth naming, because it is what makes the duplication load-bearing:
compare/xml.py offers a file-level entry point, compare_xml_files_html, that takes two
paths and does the whole resolution. compare/pdf.py has no equivalent, so every PDF caller
hand-rolls it, and they hand-rolled it differently.
How it surfaced
An audit of the command-line and web diff paths on develop at commit 8fb0fa16. The
headings above were read out of four rendered reports: the two committed examples in
examples/, and two rendered during the audit by driving diff_pdf.main() and by posting
to /api/compare through Starlette's TestClient. The probe is
docs/research/cli-ui-parity/probes/probe_htmldiff.py on branch
worktree-audit-cli-ui-diff-parity, and it prints the embedded version metadata for each:
cli_pdf: versions={"v1": {"label": "reported-in-house", "version_number": null, ...},
"v2": {"label": "engrossed-in-house", "version_number": null, ...}}
ui_pdf: versions={"v1": {"label": "1_reported-in-house", "version_number": null, ...},
"v2": {"label": "2_engrossed-in-house", "version_number": null, ...}}
Aside from these two lines and the visible heading built from them, the two reports are
byte-identical.
Why it matters
Two docstrings and one README section state an invariant the code does not hold, which is
worse than the inconsistency itself because it means a reader is being told the wrong thing.
compare_xml_files_html says deriving the labels and ordinals from filename stems "is what
makes a rendered example identical to the report a reader would get by uploading the same
two files." The upload keeps the 1_ prefix and carries a null ordinal, so it is not.
- README's HTML report section documents the heading as
v1: reported-in-house → v2: engrossed-in-house, then says diff_pdf.py "writes the same
HTML report described above." For PDF input from the command line, it does not.
The practical consequence is for anyone checking a figure in a published example against the
same comparison run locally, or against their own upload: the documents are the same but do
not present as the same, and the version identity is exactly the field they would use to
confirm they are looking at the right pair.
The maintenance cost is the second reason. One rule with six implementations means a change
to how versions are named has six places to land and no gate that notices if it lands in five.
What to do
The three changes are separable but touch the same call sites, so doing them together avoids
re-editing the same six places three times.
- One resolver. Extend
src/deltatrack/version_stems.py with a single function that
takes a filename and returns both the label and the ordinal, sanitized so it is safe for an
uploaded name (stripping path components, which _label_from_filename does today and
label_from_stem does not). Every surface calls it, and _label_from_filename is deleted.
- Pass the ordinals from
diff_pdf.py, doing what render_pdf_diff_html's docstring
already describes.
- Add
compare_pdf_files_html to src/deltatrack/compare/pdf.py, mirroring
compare_xml_files_html, so render_examples.py and diff_pdf.py both drop their
hand-rolled derivation rather than being fixed to hand-roll it the same way.
One question is genuinely open and should be decided rather than assumed: whether an upload
should derive ordinals at all. Deriving them means someone who uploads the numbered corpus
filenames gets the heading the published example shows, which is the invariant
compare_xml_files_html claims. Not deriving them means the heading never asserts a
legislative ordinal for a file whose provenance the tool cannot check, since a user can name
an upload anything. The first is more consistent; the second is more honest about what is
known. The current behaviour is the second by accident rather than by decision.
Verification
Rendering the committed examples and comparing is not sufficient on its own, because
render_examples.py is already the surface that behaves correctly, so a fix that changed
nothing elsewhere would still leave them passing.
What shows the fix landed: the same fixture pair rendered through diff_pdf.main(), through
/api/compare, and through render_examples.py produces the same heading and the same
versions block. Reverting change 2 alone should make that check fail, which is worth
confirming before trusting it.
Unverified
Whether any consumer outside this repository reads the versions block and depends on the
current, surface-dependent values. It is part of the published canonical contract
(schema/canonical-diff.md), so a conforming consumer could be reading it; none was checked.
Part of #691 (the epic making every surface produce the same answer). Related to
#606 (the documented snippet for running the PDF comparison locally fails on its first line),
which is the same documentation drift in the web-compare guide.
Refs #691, #606
What's wrong
Comparing the same two bill files gives a report with a different heading depending on which
way you asked. The published example on the project's site, the same comparison run from the
command line, and the same two files uploaded to the web page each name the versions
differently.
Measured on the committed fixture pair
tests/corpus/118-hr-8752, comparing1_reported-in-house.pdfto2_engrossed-in-house.pdf:scripts/render_examples.py, which builds the published examplesv1: reported-in-house → v2: engrossed-in-house./diff_pdf.pyreported-in-house → engrossed-in-house1_reported-in-house → 2_engrossed-in-houseThe same three-way split appears inside each report's embedded
diff.json, themachine-readable copy of the comparison that the download button hands out: the two CLI
variants and the upload disagree on both the
labeland theversion_numberof each side.Two causes, which is why they are filed together. They share a fix and touch the same call
sites.
Version ordinals are known but not passed.
compare_pdfs_htmlinsrc/deltatrack/compare/pdf.pyacceptsstart_version_numberandend_version_number, thebill's legislative ordinals (the
1and2in1_reported-in-house.pdf, meaning first andsecond published version of that bill).
scripts/render_examples.pypasses them.render_pdf_diff_htmlinsrc/deltatrack/diff_pdf.py, which is what./diff_pdf.pycalls,does not, even though its own docstring names exactly that caller:
Two different filename-to-label algorithms.
label_from_steminsrc/deltatrack/version_stems.pystrips a leading<n>_ordinal prefix, turning1_reported-in-houseintoreported-in-house._label_from_filenameinweb/app.pystrips path components and the file extension only, so the prefix survives into the heading.
It is the only one of the two with no test.
Between them, resolving a source document to its version identity is implemented at six
places in production code:
compare/xml.py::compare_xml_files_htmldiff_pdf.py::render_pdf_diff_htmldiff_bill.py::cmd_compare, HTML branchdiff_bill.py::cmd_compare, JSON branchweb/app.py::_label_from_filenamescripts/render_examples.py::render_pdf_difftests/test_canonical_baseline.pyandtests/test_pdf_canonical_baseline.pyre-derive itagain, which is how a change to the rule can leave the baselines agreeing with a copy of
itself rather than with the code.
A contributing cause worth naming, because it is what makes the duplication load-bearing:
compare/xml.pyoffers a file-level entry point,compare_xml_files_html, that takes twopaths and does the whole resolution.
compare/pdf.pyhas no equivalent, so every PDF callerhand-rolls it, and they hand-rolled it differently.
How it surfaced
An audit of the command-line and web diff paths on
developat commit8fb0fa16. Theheadings above were read out of four rendered reports: the two committed examples in
examples/, and two rendered during the audit by drivingdiff_pdf.main()and by postingto
/api/comparethrough Starlette'sTestClient. The probe isdocs/research/cli-ui-parity/probes/probe_htmldiff.pyon branchworktree-audit-cli-ui-diff-parity, and it prints the embedded version metadata for each:Aside from these two lines and the visible heading built from them, the two reports are
byte-identical.
Why it matters
Two docstrings and one README section state an invariant the code does not hold, which is
worse than the inconsistency itself because it means a reader is being told the wrong thing.
compare_xml_files_htmlsays deriving the labels and ordinals from filename stems "is whatmakes a rendered example identical to the report a reader would get by uploading the same
two files." The upload keeps the
1_prefix and carries a null ordinal, so it is not.v1: reported-in-house → v2: engrossed-in-house, then saysdiff_pdf.py"writes the sameHTML report described above." For PDF input from the command line, it does not.
The practical consequence is for anyone checking a figure in a published example against the
same comparison run locally, or against their own upload: the documents are the same but do
not present as the same, and the version identity is exactly the field they would use to
confirm they are looking at the right pair.
The maintenance cost is the second reason. One rule with six implementations means a change
to how versions are named has six places to land and no gate that notices if it lands in five.
What to do
The three changes are separable but touch the same call sites, so doing them together avoids
re-editing the same six places three times.
src/deltatrack/version_stems.pywith a single function thattakes a filename and returns both the label and the ordinal, sanitized so it is safe for an
uploaded name (stripping path components, which
_label_from_filenamedoes today andlabel_from_stemdoes not). Every surface calls it, and_label_from_filenameis deleted.diff_pdf.py, doing whatrender_pdf_diff_html's docstringalready describes.
compare_pdf_files_htmltosrc/deltatrack/compare/pdf.py, mirroringcompare_xml_files_html, sorender_examples.pyanddiff_pdf.pyboth drop theirhand-rolled derivation rather than being fixed to hand-roll it the same way.
One question is genuinely open and should be decided rather than assumed: whether an upload
should derive ordinals at all. Deriving them means someone who uploads the numbered corpus
filenames gets the heading the published example shows, which is the invariant
compare_xml_files_htmlclaims. Not deriving them means the heading never asserts alegislative ordinal for a file whose provenance the tool cannot check, since a user can name
an upload anything. The first is more consistent; the second is more honest about what is
known. The current behaviour is the second by accident rather than by decision.
Verification
Rendering the committed examples and comparing is not sufficient on its own, because
render_examples.pyis already the surface that behaves correctly, so a fix that changednothing elsewhere would still leave them passing.
What shows the fix landed: the same fixture pair rendered through
diff_pdf.main(), through/api/compare, and throughrender_examples.pyproduces the same heading and the sameversionsblock. Reverting change 2 alone should make that check fail, which is worthconfirming before trusting it.
Unverified
Whether any consumer outside this repository reads the
versionsblock and depends on thecurrent, surface-dependent values. It is part of the published canonical contract
(
schema/canonical-diff.md), so a conforming consumer could be reading it; none was checked.Part of #691 (the epic making every surface produce the same answer). Related to
#606 (the documented snippet for running the PDF comparison locally fails on its first line),
which is the same documentation drift in the web-compare guide.
Refs #691, #606