The Kollen correction was six characters. The file grew by 127, and the filing had already contradicted itself 223 pages later.
Kentucky PSC Case No. 2025-00113 · 4 August 2026
On 9 September 2025 the Kentucky Attorney General and the Kentucky Industrial Utility Customers filed Lane Kollen's joint direct testimony in the KU rate case: 378 pages, 1,024,409 characters. On 30 September they filed a corrected version: 1,024,536 characters. A difference of 127 in a million.
What moved, what it would cost to miss, and why a diff of the two reports 144 changes rather than two. Every number below came out of those files; the recipe is at the foot.
What moved
Two substitutions, both on PDF page 98 — the page the testimony prints as 95.
7 their request for CPCN for Brown BESS. The Companies agree to take necessary
ky-2025-00113-kollen-direct-testimony-original.txt, PDF page 98 · commission copy7 their request for CPCN for Cane Run BESS. The Companies agree to take
ky-2025-00113-kollen-direct-testimony-corrected-clean.txt, PDF page 98 · commission copyThe same swap happens four lines earlier, on line 3. That is the whole correction. The Companies were withdrawing a certificate application for a battery plant and the testimony named the wrong plant. That is not a typo. It is testimony about which asset they have stopped building.
The 127 characters are not the correction
"Brown" to "Cane Run" adds three characters, twice. Six. The file grew by 127.
Count both files again with every space, tab and newline discarded and the corrected copy is shorter: 514,194 non-space characters against 514,259. Its whitespace grew by 192. The OCR jitter below says it was re-scanned rather than re-exported, and the extractor broke the lines differently, so the delta measures the scanner. Watch file size and you would have seen this document grow on the day its text shrank.
The diff reports 144 changes
I ran this product's own diff engine over the pair — app/diff/engine.py, 126
lines around difflib.SequenceMatcher, no model and no network. The splitting
happens before it, in app/ingestion/ingest.py, whose _segment cuts
each file on blank lines: 4,285 passages against 4,287. The engine takes those two lists and
returns 144 changes: 134 modified, 6 added, 4 removed.
It takes about 0.4 seconds on an Apple M5 Pro. Measure your own machine rather than quoting mine, and treat the figure as a scale rather than a benchmark: it is one pair of files on one laptop.
Two of the 144 are the correction.
The other 142
They are the scanner disagreeing with itself. Discard every space, tab and newline and 63 of the 144 disappear; 81 survive. Fifty-three of the 81 change at least one digit. Merely collapsing runs of whitespace removes none of the 144: the engine already does that before it compares.
ISSUED BY: /s/ Robert M. Conroy, Vice President
ky-2025-00113-kollen-direct-testimony-original.txt · commission copyISSUED BY: Isl Robert M. Conroy, Vice President
ky-2025-00113-kollen-direct-testimony-corrected-clean.txt · commission copyThat one is harmless once seen. This one is not, and it runs the other way — the original is corrupt and the correction repairs it, on a page nobody amended.
8,000,000.00 B, 960,000 248,131
ky-2025-00113-kollen-direct-testimony-original.txt · commission copy8,000,000.00 8,960,000 248,131
ky-2025-00113-kollen-direct-testimony-corrected-clean.txt · commission copySo "the later filing is the better text" is not a rule you can use. In Exhibit LK-19 the original misreads Parties as Patties six times against the corrected copy's two — but the corrected copy invents Pa11ies twice and repo11s once where the original had neither.
Ranking will not rescue you. Order all 144 by alignment confidence, least first, and the real changes arrive 38th and 39th. Nothing about a genuine amendment makes it look more changed than a re-scanned signature block.
The cover letter is not a manifest
The corrections are found on page 95 of Mr. Kollen’s testimony, and changes original references to “Brown BESS” to now read “Cane Run BESS.”
ky-2025-00113-kollen-correction-cover-letter.txt · commission copyReferences, plural. One page named — the page printed on the document, which is PDF page 98. No count. The original holds six occurrences of Brown followed by BESS; two were changed. The four that survive, on PDF pages 277 and 279, are right as they stand. They sit inside a data request the Attorney General and KIUC sent themselves, Joint Supplemental Question No. 33, quoted back word for word in KU's response on page 277 and LG&E's on page 279.
depreciation rates for Brown BESS, AMI, Paddy's Run CT pipeline,
ky-2025-00113-kollen-direct-testimony-corrected-clean.txt · commission copyNote the double spaces — and three lines up, the extraction breaks Brown and
BESS across a line. So a plain search for Brown BESS in the corrected
copy returns zero, telling you the term is gone while four remain. Replace what such a search
does find and you misquote the Attorney General's own data request back at the commission.
Widening the search is worse. Brown appears 41 times in the corrected copy and 37 have nothing to do with a battery: the E.W. Brown Generating Station, ten Brown Units in one depreciation table — there is no Unit 4 — Brown Solar and Wind, Brown 12 twice on the corrected page 95 and never wrong, and a man named Elijah Brown. The correction is scoped to a page, not a term, and the letter does not say so.
The filing already knew
The sentence that was wrong carries a footnote.
84 I have attached a copy of the Stipulation and Recommendation in Case 2025-00045 as Exhibit LK-19.
ky-2025-00113-kollen-direct-testimony-original.txt · commission copyExhibit LK-19 begins on PDF page 318 of the same file. Paragraph 1.2 of the stipulation, on PDF page 321, reads:
1.2. Withdrawal of Cane Run Battery Energy Storage System ("BESS") CPCN Request. The Utilities will withdraw their request for the Cane Run BESS without prejudice in this case, but they may re-file a CPCN Application for Cane Run BESS, or a substitute for it, at any time, which would be supported by a competitive procurement process.
ky-2025-00113-kollen-direct-testimony-original.txt · commission copyOne more trap: the mark-up copy
The correction arrived as two documents: a clean copy and a mark-up copy. Extract the text of the mark-up and PDF page 98 reads:
7 their request for CPCN for Brown BESS. The Companies agree to take necessary
ky-2025-00113-kollen-direct-testimony-corrected-markup.txt · commission copyWord for word the superseded original, at the one spot the correction was about. Everywhere else it matches the clean copy: six changed lines out of 15,853, all six on page 98. Whatever a person sees on that page, a machine gets the withdrawn name out of a file whose own name says corrected. I have not established how the mark-up is rendered — the page carries no PDF annotation objects — and would rather say so than guess.
What to do on Monday
None of this needs a purchase. It needs half a day and difflib.
- Compare twice, and report both counts. Once as the text stands, once with every space, tab and newline discarded. Here that is 144 against 81, and the 63 that vanish are pure re-layout.
- Never replace on the strength of a correction notice. It names a page, not a scope. Diff, and act on what moved.
- Check a filing against its own attachments. Take the proper nouns in the testimony — plant names, docket and exhibit numbers — and check them against the exhibits it cites. That one pass would have caught this on 9 September.
- Quote from the clean copy. This mark-up extracts to the superseded text, so the helpful-looking copy is the one likeliest to poison your workflow. Whether every mark-up does that, I do not know.
- Do not assume the later filing is the better text. Where a re-scan is involved, corruption runs both ways. Say which way each difference runs, or do not report it.
How these numbers were made
Two PDFs from the Kentucky PSC's public filing path, extracted with PyMuPDF
using page.get_text("text", sort=True) so the margin line numbers stay on the
lines they number, pages joined with a form feed, nothing cleaned. Character counts are
len() over the decoded text; the non-space counts are the same strings under
re.sub(r"\s", "", …). Passages come from
app/ingestion/ingest.py::_segment, which cuts on blank lines; the 144 changes come
from app/diff/engine.py::diff over those two lists, timed with
time.perf_counter() around the diff call alone. The 81 are the changes whose two
sides still differ under re.sub(r"\s", "", …), the 53 those whose two sides differ
under re.sub(r"\D", "", …). A change is charged to the page its passage starts on,
counting form feeds — the corrected file where there is one, the original otherwise.
Brown BESS is counted with re.findall(r"Brown\s+BESS", …). The files hash
to 51232537…c33b66 and f39fcd14…90ac84, both recomputing against the
corpus provenance records.
Disclosure, and a limit. Verbatim is my product and the diff engine above is its code. It is not what found this: it reported 144 changes and ranked the two that mattered 38th and 39th. I read all 144.
The machine is fast and honest about what it saw. It cannot yet tell you that a testimony contradicts the exhibit it cites, which is the check that would have mattered most here. That is the next thing to build, and it is a rule you can check rather than a judgement.