This was generated by AI during triage.
Parent
#205
What to build
rebuild_agency_bids re-derives every agency_bid row from the held PDFs on each store pass — correct when derivation is regex and costs milliseconds, ruinous when it is a model call per document. This ticket adds a cache keyed on the document's sha256 plus a model+prompt version stamp, so a document that has not changed and whose extraction prompt has not changed is never re-extracted.
The existing regex derivation moves behind this seam unchanged. 800 tests green, zero behaviour change. A second extractor (the model path) becomes a drop-in behind it.
"Skip if rows exist" is the wrong guard — that is what makes stale rows persist. The version stamp is what makes re-extraction trigger on a prompt change, not on whether the table is empty.
Acceptance criteria
Blocked by
None — can start immediately.
Parent
#205
What to build
rebuild_agency_bidsre-derives everyagency_bidrow from the held PDFs on each store pass — correct when derivation is regex and costs milliseconds, ruinous when it is a model call per document. This ticket adds a cache keyed on the document'ssha256plus a model+prompt version stamp, so a document that has not changed and whose extraction prompt has not changed is never re-extracted.The existing regex derivation moves behind this seam unchanged. 800 tests green, zero behaviour change. A second extractor (the model path) becomes a drop-in behind it.
"Skip if rows exist" is the wrong guard — that is what makes stale rows persist. The version stamp is what makes re-extraction trigger on a prompt change, not on whether the table is empty.
Acceptance criteria
(sha256, extractor_version), that short-circuits extraction when the key matchesagency_bidrows through the new seamextractor_versioncauses re-extraction on the next store passBlocked by
None — can start immediately.