Food-safety compliance via tagged COAs
Vendors (Bongards Creameries, USDA cheese suppliers, co-packers like Right Start) send Certificates of Analysis as PDFs or DOCX to vendors@ai-globalfoodsolutions.co. Each COA records the laboratory tests for a specific lot: moisture %, fat %, salt %, microbial counts, allergen status, and an overall pass/fail.
The Data Tagger applies a vendor-specific COA template, extracts 5 field tags, and writes to the vendor_coas D1 table. This table has NOT YET BEEN CREATED — its migration (proposed mig 144) needs to land before this path becomes operational at runtime. The path is fully documented as a contract so Agent BB-1 (migration owner) can land the table alongside template metadata.
COAs are not on the SO/PO/WO golden path. They sit alongside as food-safety compliance: regulatory trail, customer-facing COA-on-request, lot tracking for recall scenarios. Diagram: ns-data-tagger-path-2-vendor-coa-to-compliance.html.
Trigger conditions
- Vendor (Bongards, co-packer, USDA supplier) emails a COA to
vendors@. - Vendor identified via sender domain or name_synonyms fuzzy.
- An active COA template exists for
(vendor x coa x vendor_coas). - (First-time vendor) Operator trains a new COA template at
/data-tagger.html.
Path 2 cannot write end-to-end until the vendor_coas migration lands. Schema is sketched in the diagram step 11. Until then, extractions sit in data_tagger_extractions only.
Confidence threshold is 0.90 (vs 0.85 for the SO path). Food-safety scrutiny gets a tighter auto-stage door.
Bongards cheddar barrel COA
Bongards Creameries ships USDA cheddar barrels weekly. Each shipment carries a COA. The QC lab at Bongards emails COA-LOT-2026053-BNG.pdf to vendors@ai-globalfoodsolutions.co.
Pipeline runs: email + R2 + parse. Sender domain resolves vendor_id = VEN-Bongards. doc_type classifier returns coa. Template lookup finds tpl_bongards_coa_v1.
5 strategies run:
regex_after_labelLot # ->lot_number = "LOT-2026053-BNG"regex_after_labelItem # ->item_code = "SKU-419"(mild cheddar barrel)regex_after_labelTest Date ->test_date = "2026-05-22"table_with_headersParameter/Result/Spec ->parameters_tested = [{moisture: 36.4, fat: 32.1, salt: 1.7, ...}]formulaall params in spec ->pass_fail = "pass"
Confidence 0.93. Above 0.90 threshold. Staged as compliance proposed_action. Mike approves. INSERT into vendor_coas (once table exists). events.coa.received fires.
If a parameter were out of spec (e.g. moisture 38.5%, spec max 37%), pass_fail = "fail" -> the fail-fork workflow stages a second proposed_action: hold the lot in inventory (status=quarantine), notify warehouse, prep CS template for downstream customer notice.
15 steps from vendor COA to compliance record
- 01-03
Intake (email + R2 + parse)
vendors@ ->
inbound_email_log+ R2 +document_converter - 04-06
Identify (vendor + doc + template)
VEN-Bongards / coa / vendor_coas ->
tpl_bongards_coa_v1 - 07
Apply 5 strategies
lot / item_code / test_date / parameters[] / pass_fail
- 08
Confidence (threshold 0.90)
compliance scrutiny higher
- 09-10
Compliance HITL + review
proposed_actionskind=data_tagger_coa_extraction - 11
INSERT vendor_coas
TBD migration 144 - blocking gap
- 12
Update item lot tracking
items.lots_json OR item_lots TBD
- 13
events.coa.received
compliance subscribers react
- 14
Fail fork (if pass_fail=fail)
hold lot + warehouse notify + CS template
- 15
Reflexion
template hit_count + success_count
What's different after the cycle
What can go wrong
Path can't complete write step. Today extractions sit in data_tagger_extractions and Mike has to manually transfer if needed. Blocked on mig 144.
Some vendor COAs use a free-form list instead of a table. table_with_headers fails. Fallback strategy is llm_with_schema which is expensive; train a vendor-specific template using multi_line_span per parameter.
If pass_fail=fail but the fail-fork workflow is not yet wired, the lot stays available in inventory. Manual quarantine until workflow lands.
Adjacent flows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Mailbox | vendors@ai-globalfoodsolutions.co |
| Email pipeline | src/email.ts |
| Parser | src/document_converter.ts (DOCX native) |
| Template | tpl_bongards_coa_v1 in data_tagger_templates |
| Destination | vendor_coas (TBD mig 144) |
| Lot tracking | items.lots_json OR item_lots (design open) |
| Event | events.coa.received |
| Fail-fork | proposed_actions kind=coa_fail_hold_lot (workflow TBD) |
Dated trail
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-27 | R598 | Path 2 wiki + diagram shipped. vendor_coas schema sketched; mig 144 proposed but not yet landed. | Mike + Claude |
5 field tags for COA template
| # | strategy | field | example |
|---|---|---|---|
| 1 | regex_after_label | lot_number | LOT-2026053-BNG |
| 2 | regex_after_label | item_code | SKU-419 |
| 3 | regex_after_label | test_date | 2026-05-22 |
| 4 | table_with_headers | parameters_tested[] | moisture 36.4%, fat 32.1%, salt 1.7% |
| 5 | formula | pass_fail | pass |
It broke - what now
Scenario · COA staged but extraction not visible in compliance log
Mike approved but the COA doesn't appear in compliance lookup.
- Check vendor_coas exists:
SELECT name FROM sqlite_master WHERE type='table' AND name='vendor_coas' - If missing: mig 144 hasn't landed. Path is blocked until it does.
- Check fallback:
SELECT extracted_fields_json FROM data_tagger_extractions WHERE ns_record_type='vendor_coas' ORDER BY created_at DESC
Scenario · Fail lot not quarantined
pass_fail=fail but lot still appears available.
- Check fork fired:
SELECT * FROM proposed_actions WHERE kind='coa_fail_hold_lot' - Manual quarantine: update inventory status until fail-fork workflow lands
- Notify warehouse + CS manually
Logs to check
data_tagger_extractionsvendor_coas(when it exists)proposed_actions(kind=data_tagger_coa_extraction+coa_fail_hold_lot)events(coa.received)
Open items for Path 2
- STUBMigration 144: vendor_coas table
The blocking gap. Schema sketched above; needs to land before this path writes anything. Probably bundled with item_lots design.
- DECISIONItem lot tracking design
items.lots_jsoncolumn vs separateitem_lotstable. Recall scenarios + lot-level inventory ops argue for the separate table. - STUBFail-fork workflow contract
hold-lot + notify-warehouse + CS-template chain has no formal workflow definition. Needs ADR + workflow contract.
- OPENFDA-trail export
events.coa.received fires but no subscriber yet builds the FDA-trail report.
- OPENCustomer-facing COA-on-request
Downstream feature: customer asks for a COA proof for a lot they received; system pulls from vendor_coas.