5.4 KiB
Task 6 report: site-scoped supplier and PO resolution
Status: complete
Files
src/lib/dn-pipeline/resolve-entities.ts— pure ordered resolver and candidate/evidence types.src/lib/dn-pipeline/resolve-entities.test.ts— resolver safety tests.src/lib/dn-pipeline/run.ts— site-scoped PO adapter, supplier/alias loading, deterministic candidate persistence, and fuzzy-review gate.src/lib/dn-pipeline/run.test.ts— updated legacy-runner expectation so fuzzy suggestions remain blocked review candidates.src/lib/po-matching.ts— exported ranked PO evidence while preserving the existingfindBestPoMatchAPI.
Decisions
- The batch's confirmed
siteis authoritative. The runner queriespurchase_orderswithpb.filter("site = {:site}", { site: batch.site })(with a JSON-escaped fallback for lightweight SDK doubles) and requests the realsite,supplier_ref, and PO fields. resolveEntitiesis pure and consumes already scoped rows. A unique normalised PO is authoritative and derivessupplier_ref, even when the printed supplier is blank. Registration, approved document alias, and exact supplier-name evidence are deterministic supplier paths.- Fuzzy PO candidates are ranked with score, rank, threshold, and winner margin, but never auto-selected. The legacy runner blocks a printed-PO note with no deterministic selected PO even if a stale/mock check list says it passed.
findBestPoMatchremains available to existing callers.rankPoMatchesexposes all scored candidates and preserves legacy first-on-tie behavior forfindBestPoMatch.- Supplier and supplier-alias collections are additive adapters. If an older PocketBase lacks them, the runner keeps PO free-text provenance and never widens the site-scoped PO query.
- Receipt/invoice payable semantics and forbidden clock/image-quality/nearest- site paths were not changed. No production PocketBase writes were made.
RED evidence
Command:
npx vitest run src/lib/dn-pipeline/resolve-entities.test.ts
Result before implementation: failed during collection because
./resolve-entities did not exist (Failed to load url ... Does the file exist?; Test Files 1 failed, Tests 0).
GREEN evidence
Resolver and regression command:
npx vitest run src/lib/dn-pipeline/resolve-entities.test.ts src/lib/po-matching.test.ts src/lib/dn-pipeline
Result: Test Files 14 passed (14), Tests 205 passed (205).
The runner test now explicitly proves a near PO is persisted as
match_method=po_fuzzy, with no selected PO and no promotion.
Typecheck:
npm run typecheck
Result: passed (tsc --noEmit).
Diff check:
git diff --check
Result: passed.
Self-review
- Required resolver cases are covered: blank supplier with exact PO, cross-site exclusion, approved alias, ambiguous fuzzy candidates, and zero winner margin.
- Exact PO selection is the only path that returns a purchase-order id from this runner; fuzzy evidence remains review-only.
- Candidate rows persist
supplier,supplier_name_raw,match_method, and JSONmatch_evidence, while the existing checks, receipt, invoice, and promotion paths remain otherwise unchanged. - The only expected test stderr is the existing deliberate promotion-failure
fixture (
[dn-run] promote failed); it is asserted by the existing test. - No production PocketBase writes were made.
Commit
Implementation commit: 75a1d85c875c43f66232aaec448ff6fa1f904523.
Fix-round implementation commit: 33be75b82153cc602aa7838942ea670588cf920c.
Fix Round 1
Review findings addressed:
NoteSegmentnow carries optional supplier name and registration evidence;segmentPagescopies it from the first page and fills missing values from continuations. The legacy runner loads entity data whenever any note has a PO, supplier name, or registration evidence.- Site-scoped PO loading now projects only real fields, including
processing_statusand excluding the nonexistentstatus; the runner test asserts the bound site filter and projection. - The staged schema migration additively/idempotently adds optional supplier
registration_numberandvat_numberfields.SupplierRecord, the pure resolver type, and the loader use those exact fields. No migration was applied. - Fuzzy candidates are capped at 20 but retain score, threshold, rank, and
margin even below threshold; they remain suggestions and never auto-resolve.
The resolver now uses the exported
rankPoMatchesprimitive.
Fix-round RED command:
npx vitest run src/lib/dn-pipeline/resolve-entities.test.ts src/lib/dn-pipeline/segment.test.ts src/lib/dn-pipeline/run.test.ts
Result before fixes: 5 failed, 52 passed. Failures covered dropped segment
fields, the invalid status projection, skipped supplier-only loading, and
discarded below-threshold fuzzy evidence.
Fix-round GREEN and regression commands:
npx vitest run src/lib/dn-pipeline/resolve-entities.test.ts src/lib/po-matching.test.ts src/lib/dn-pipeline
Result: Test Files 14 passed (14), Tests 210 passed (210).
python3 scripts/pb-add-dn-staged-ingestion.py --self-test
Result: self-test: index replacement and idempotence passed.
python3 scripts/pb-add-dn-staged-ingestion.py --dry-run
Result: offline preview completed with DRY RUN. No schema changes written.
npm run typecheck
git diff --check
Both passed. No production PocketBase writes were made.