docs(dn): record task 6 resolution evidence

This commit is contained in:
DEls 2026-09-19 07:39:49 +02:00
parent 75a1d85444
commit e59861393f

View file

@ -0,0 +1,99 @@
# 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
existing `findBestPoMatch` API.
## Decisions
- The batch's confirmed `site` is authoritative. The runner queries
`purchase_orders` with `pb.filter("site = {:site}", { site: batch.site })`
(with a JSON-escaped fallback for lightweight SDK doubles) and requests the
real `site`, `supplier_ref`, and PO fields.
- `resolveEntities` is pure and consumes already scoped rows. A unique
normalised PO is authoritative and derives `supplier_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.
- `findBestPoMatch` remains available to existing callers. `rankPoMatches`
exposes all scored candidates and preserves legacy first-on-tie behavior for
`findBestPoMatch`.
- 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:
```text
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:
```text
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:
```text
npm run typecheck
```
Result: passed (`tsc --noEmit`).
Diff check:
```text
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
JSON `match_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`.
Report commit: recorded by the commit that adds this report.