UploadedFile already holds the whole attachment in memory; getvalue()
made a second full copy of each of the 230 files before writing. Copy
through the file interface instead. Smoke tests pin the progress
callback contract: once per XML file, including unreadable ones.
Refs #1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
build_fixed_row_by_index evaluated ~2 400 candidate paths per report row
(7 million _pick_value calls per 13 800 rows). A reverse index
"path -> (rule, candidate, probe)" is built once at import; per row we
walk only the record's own fields and keep the best hit per rule, with
the same candidate/probe ordering as _pick_value. Differential test
compares the index with _pick_value on random and parsed payloads.
Also: merged lookup reuses the operation lookup when the participant
payload is empty, and index-free paths skip the regex normalisation.
scripts/bench_report.py reproduces the measurement (0.42 -> 0.26 ms/row).
Refs #1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>