Cleveland Kitchen Org-Specific Discounts — Full-Stack Implementation Plan
Date: 2026-04-08
Status: Draft for team review
Scope: Extraction → persistence → review state → WhereFour submission
Customer: Cleveland Kitchen
Design goal: org-specific customization implemented in a reusable way behind feature flags
Executive Summary
Section titled “Executive Summary”Cleveland Kitchen’s new data package changes discount handling from a UI-only pricing warning into a structured end-to-end feature.
The two source files are:
Finished Goods - UPC Mapping.xlsxCustomLineItems.csv
These confirm that:
-
UPC is not the canonical key
Article Codeis the internal product identity.RefCodeis the UPC-oriented alias layer.- The same product family can exist as different sellable SKUs by unit of measure (
each,case, etc.). - Pack size often lives in the description, so UPC-only matching is unsafe.
-
Discounts are not net-price edits
- Discounts must be emitted as separate WhereFour custom line items.
- Those custom line items already exist in CK’s system.
- They preserve downstream accounting / GL treatment into QuickBooks.
- Discounts are applied at the SKU line level, not as a single order-level adjustment.
The implementation should therefore create a reusable pipeline with these characteristics:
- org-specific extraction behavior
- reference-data-backed canonical identity resolution
- explicit discount planning and validation
- persisted review-state metadata
- feature-flagged activation per org
What We Promised CK
Section titled “What We Promised CK”Based on the working session and follow-up:
- Ordermatic will resolve the correct SKU from messy inbound PO identifiers.
- Ordermatic will treat discounts as separate allowance/custom line items, not by rewriting product prices.
- The system will validate claimed discounts against the active promotion context.
- Reviewers will clearly understand why the ERP price differs from the customer PO.
- Accounting-safe treatment must be preserved downstream.
That means the implementation must preserve gross line pricing and emit explicit discount lines.
Current State in the Codebase
Section titled “Current State in the Codebase”Good existing extension points
Section titled “Good existing extension points”Org-specific extraction inputs already exist
Section titled “Org-specific extraction inputs already exist”packages/db/src/schema/erp-connections.tspromptConfigextraConfig
packages/pdf-shared/pdf_shared/crud.pyget_document_and_connection_config()already loads:prompt_configextra_config.extraction_schema
Prompt composition already supports org-specific instructions
Section titled “Prompt composition already supports org-specific instructions”packages/pdf-shared/pdf_shared/utils/langfuse_prompts.pyget_pdf_extraction_prompt_from_config(...)build_extraction_schema_instructions(...)
packages/pdf-shared/pdf_shared/llm/dspy_org_module.py- org overlay prompt support already exists conceptually
Review state and extraction schema system already exist
Section titled “Review state and extraction schema system already exist”apps/webapp/src/types/order.tsapps/webapp/src/lib/extraction-schema.tsapps/webapp/src/stores/pdfOrder/order.tsapps/webapp/src/components/orders/DocumentOrderReview.tsxapps/webapp/src/components/orders/OrderEditor.tsx
Important current gaps
Section titled “Important current gaps”No structured discount model exists end-to-end
Section titled “No structured discount model exists end-to-end”ExtractedOrderData/ExtractedOrderItemDatado not contain discount-specific fields.packages/db/src/schema/pdf-documents.tsdoes not store discount review metadata.apps/temporal-worker/activities/database.py::save_extracted_order()persists only a narrow set of fields.
Existing CK pricing logic is a POC stub
Section titled “Existing CK pricing logic is a POC stub”apps/webapp/src/components/orders/DocumentOrderReview.tsx- uses
isCKPOCEnabled - injects
isCKPOCNewCustomerSkuPairing: trueas a synthetic demo flag
- uses
- This is not driven by real extracted metadata.
Totals are not discount-aware
Section titled “Totals are not discount-aware”apps/webapp/src/stores/pdfOrder/order.ts$orderTotalsonly sumsquantity * unitPrice
apps/webapp/src/components/orders/OrderTotalsCard.tsx- only displays subtotal/tax/total
Existing WhereFour validation route is sidecar/PoC only
Section titled “Existing WhereFour validation route is sidecar/PoC only”apps/webapp/src/pages/api/integrations/wherefour/validate-order.tsapps/webapp/src/pages/orders/wherefour-submit.astro
This route is useful as a proving ground, but the real implementation should be wired into the normal PDF review experience.
Implementation Principles
Section titled “Implementation Principles”- Canonical identity first, heuristics second
- Postgres is source of truth; Redis is derived cache
- Gross price stays on product line
- Discount becomes a separate custom line item
- Review page displays persisted backend decisions, not UI-only calculations
- Org-specific behavior is configurable, not hardcoded
- Feature flags control activation and rollout
Proposed Architecture
Section titled “Proposed Architecture”Customer PO / Email / PDF / XLS(X) -> Extraction prompt with org-specific instructions -> Structured extracted fields (UOM, pack size, claimed discounts, UPC aliases) -> Customer + item matching -> Org discount resolver - canonical SKU/UOM resolution - promo validation - proposed custom line item generation -> Persist review metadata -> Review page renders discount-aware state -> On submit, WhereFour uses persisted proposed custom linesData Model Changes
Section titled “Data Model Changes”1. ERP connection config
Section titled “1. ERP connection config”Continue using erp_connections.extraConfig and add a structured discountConfig block.
Example:
{ "discountConfig": { "enabled": true, "mode": "separate_custom_line_items", "pricingDisplayMode": "gross_plus_allowance", "requiresPromotionValidation": true, "identityResolver": "ck_wherefour_v1", "referenceDataVersion": "2026-04-07" }}2. New reference-data tables
Section titled “2. New reference-data tables”Add new tables in packages/db keyed by erpConnectionId.
erp_connection_product_aliases
Section titled “erp_connection_product_aliases”Purpose: canonical product identity + alias lookup
Fields:
iderpConnectionIdcanonicalProductIdarticleCoderefCodealiasValuealiasType(article_code,ref_code,upc12,upc14,description_alias)unitOfMeasurepackSizeisActiveversionTag- timestamps
erp_connection_custom_line_items
Section titled “erp_connection_custom_line_items”Purpose: imported custom line item catalog from CK
Fields:
iderpConnectionIdexternalId(barcode / OID / WhereFour identifier)namepublicDescriptionlineItemCalculationTypeunitOfMeasurementactiveamountpercentagestartMonthstartDayendMonthendDayglMetadata jsonb- timestamps
erp_connection_discount_rules
Section titled “erp_connection_discount_rules”Purpose: explicit rule layer mapping customer/SKU/UOM/location to custom line item behavior
Fields:
iderpConnectionIdcustomerKeylocationKeycanonicalProductIdunitOfMeasurecustomLineItemIdruleTypeeffectiveStarteffectiveEndmetadata jsonb- timestamps
3. Extracted order persistence
Section titled “3. Extracted order persistence”Add to extracted_orders
Section titled “Add to extracted_orders”orgExtractionMetadata jsonbdiscountSummary jsonb(optional if not folded into orgExtractionMetadata)
Add to extracted_order_items
Section titled “Add to extracted_order_items”unitOfMeasure textorgExtractionMetadata jsonb
This avoids forcing a CK-only schema into top-level columns while still making the review state queryable and durable.
Reference Data Ingestion Plan
Section titled “Reference Data Ingestion Plan”Source files
Section titled “Source files”Finished Goods - UPC Mapping.xlsxCustomLineItems.csv
Import behavior
Section titled “Import behavior”UPC mapping import
Section titled “UPC mapping import”- parse the workbook
- normalize alias values
- preserve raw values and normalized values
- generate canonical identity per
Article Code + UOM - store:
- article code
- ref code
- UPC12
- UPC14
- description alias
- pack-size metadata
Custom line item import
Section titled “Custom line item import”- parse custom line item catalog
- preserve:
- active/inactive status
- additive/subtractive behavior
- amount/percentage
- effective windows
- downstream GL/accounting metadata if available
Execution model
Section titled “Execution model”- first pass can be script-driven / one-off ingestion
- medium-term should become a repeatable admin import path or Dagster sync job
- Redis indexes should be materialized from Postgres after import
Extraction Changes
Section titled “Extraction Changes”Teach extraction to identify discount-related information without netting prices directly into product lines.
Update these shared extraction models
Section titled “Update these shared extraction models”packages/pdf-shared/pdf_shared/llm/models.pypackages/pdf-shared/pdf_shared/llm/schemas.py
New item-level extraction fields
Section titled “New item-level extraction fields”Add optional structured item fields:
unit_of_measurepack_size_textraw_upcarticle_coderef_codeclaimed_discount_textclaimed_discount_amountclaimed_discount_percentdiscount_scope(line_item,order,unknown)
New order-level extraction fields
Section titled “New order-level extraction fields”Add optional order-level fields:
location_or_warehouseclaimed_discount_summary_text
Prompt behavior for org-specific discounts
Section titled “Prompt behavior for org-specific discounts”Extend org prompt instructions so the model is told:
- extract gross product pricing
- detect discounts/allowances separately
- extract UOM and pack size explicitly
- do not fold net discounts into product line pricing
- capture identifiers (
Article Code,RefCode, UPC) whenever present
Where prompt content should come from
Section titled “Where prompt content should come from”erp_connections.promptConfig.requirementserp_connections.promptConfig.notesextraConfig.extraction_schema- optional Langfuse org overlay
Backend Resolver Plan
Section titled “Backend Resolver Plan”New shared concept: Org Discount Resolver
Section titled “New shared concept: Org Discount Resolver”Create a shared resolver module that runs after extraction and item matching.
Responsibilities:
-
Canonical item resolution
- use article code, ref code, UPC12/14, description alias, pack size, and UOM
- detect ambiguity rather than guessing
-
Discount planning
- inspect claimed discount metadata from extraction
- validate against active rule window
- match correct custom line item
- produce
proposedCustomLineItems
-
Review metadata assembly
- attach provenance
- attach notes
- attach confidence / ambiguity status
Output shape (conceptual)
Section titled “Output shape (conceptual)”At item level:
{ "identityResolution": { "articleCode": "1616MK", "matchedBy": "ref_code", "confidence": 0.98, "unitOfMeasure": "case", "packSize": "6/16 oz", "ambiguous": false }, "discount": { "claimed": { "amount": 1.5, "scope": "line_item", "text": "promotional allowance" }, "proposedCustomLineItem": { "id": "OID-031", "name": "1616BR UNFI Promotional Allowance" }, "status": "validated", "promoWindow": { "start": "2026-01-01", "end": "2026-12-31" } }}At order level:
{ "discountReview": { "enabled": true, "status": "validated", "grossSubtotal": 86.0, "discountTotal": 6.0, "netSubtotal": 80.0, "notes": ["Promo validated", "Allowance line will be created"] }}Workflow Changes
Section titled “Workflow Changes”Extraction workflow
Section titled “Extraction workflow”Update:
apps/temporal-worker/workflows/pdf_extraction.pyapps/temporal-worker/activities/database.py- likely new activity module for org discount resolution
New pipeline step
Section titled “New pipeline step”Insert a new step between item matching and persistence:
- extract
- match customer
- match items
- resolve org discounts
- save extracted order + item metadata
- agentic validation / next workflow steps
Why here
Section titled “Why here”This ensures the review page is driven by persisted backend decisions, not recomputed UI logic.
API Changes
Section titled “API Changes”GET /api/pdf-documents/[id]
Section titled “GET /api/pdf-documents/[id]”Update response so completed extracted orders include:
- order-level discount summary metadata
- item-level org extraction metadata
- unitOfMeasure
File:
apps/webapp/src/pages/api/pdf-documents/[id].ts
GET /api/extracted-orders/[id]
Section titled “GET /api/extracted-orders/[id]”Same expanded payload for direct order fetch.
File:
apps/webapp/src/pages/api/extracted-orders/[id]/index.ts
PUT /api/extracted-orders/[id]
Section titled “PUT /api/extracted-orders/[id]”Support reviewer-driven persistence of:
- validated discount state
- reviewer notes on discount ambiguity
- manual overrides to metadata where allowed
This does not mean allowing reviewers to net-edit price. It means allowing them to validate or reject discount metadata.
Frontend Review State Plan
Section titled “Frontend Review State Plan”Update frontend types
Section titled “Update frontend types”File:
apps/webapp/src/types/order.ts
Add to ExtractedOrderData:
discountSummary?orgExtractionMetadata?
Add to ExtractedOrderItemData:
unitOfMeasure?orgExtractionMetadata?
Update store shape
Section titled “Update store shape”File:
apps/webapp/src/stores/pdfOrder/order.ts
Make $orderTotals compute:
grossSubtotaldiscountTotalnetTotal
Do not derive discount purely from quantity * unitPrice; use persisted metadata.
Review rendering changes
Section titled “Review rendering changes”Files:
apps/webapp/src/components/orders/DocumentOrderReview.tsxapps/webapp/src/components/orders/OrderItemCard.tsxapps/webapp/src/components/orders/OrderTotalsCard.tsxapps/webapp/src/components/orders/OrderEditor.tsxapps/webapp/src/components/integrations/WhereFourDiscountChip.tsx
Replace the current fake CK block
Section titled “Replace the current fake CK block”Current state:
- fake
isCKPOCNewCustomerSkuPairingblock
New state:
- render from real item/order metadata
- show:
- discount summary
- per-line allowance preview
- ambiguity / invalid promo states
- lock/warning against manual price mutation when discount is validated
Core UI rule
Section titled “Core UI rule”If a line has a validated discount plan:
- product unit price remains gross
- allowance/custom line preview is shown beneath it
- reviewer should not silently overwrite price to net value
WhereFour Submission Plan
Section titled “WhereFour Submission Plan”Submission behavior
Section titled “Submission behavior”The submission path should use the persisted proposedCustomLineItems from review metadata.
That means:
- product lines stay gross
- discount lines become
custom_line_items - no duplicate pricing logic at submit time
- no frontend-derived pricing math at submit time
Existing workflow touch points
Section titled “Existing workflow touch points”apps/temporal-worker/workflows/wherefour_order_submission.pyapps/temporal-worker/activities/wherefour.py
Short-term rule
Section titled “Short-term rule”- keep actual discount submission behind a separate feature flag until sandbox-tested
Feature Flag Strategy
Section titled “Feature Flag Strategy”use_org_discounts- enables extraction + persistence + review rendering
use_org_discount_submission- enables actual custom-line submission to WhereFour
wherefour-order-validation- keep existing POC route alive until main review integration supersedes it
Rollout sequence
Section titled “Rollout sequence”- CK only:
use_org_discounts = true,use_org_discount_submission = false - Validate extraction + review state on historical docs
- Enable sandbox submission
- Validate emitted custom lines
- Enable production submission for CK
- Generalize for future orgs via config + reference data import
Ordered Implementation Sequence
Section titled “Ordered Implementation Sequence”PR 1 — Schema and config foundation
Section titled “PR 1 — Schema and config foundation”- add DB tables for product aliases / custom line items / discount rules
- add
orgExtractionMetadatafields - extend
erp_connections.extraConfigtyping withdiscountConfig
PR 2 — CK data import path
Section titled “PR 2 — CK data import path”- build importer for UPC workbook
- build importer for custom line item catalog
- materialize lookup caches from Postgres
PR 3 — Extraction contract
Section titled “PR 3 — Extraction contract”- extend extraction models/schemas with structured discount/UOM/pack fields
- update prompt composition to include org discount instructions
PR 4 — Org discount resolver
Section titled “PR 4 — Org discount resolver”- add canonical SKU/UOM resolution
- add discount validation + custom line planning
- persist results into extracted order/item metadata
PR 5 — API + frontend review state
Section titled “PR 5 — API + frontend review state”- expand API responses
- update
types/order.ts - update store totals
- replace fake CK review banner with real metadata-driven UI
PR 6 — Sandbox submission path
Section titled “PR 6 — Sandbox submission path”- WhereFour submission consumes
proposedCustomLineItems - discount lines emitted as actual custom line items
- product unit price remains gross
PR 7 — QA / rollout hardening
Section titled “PR 7 — QA / rollout hardening”- regression fixtures
- ambiguity tests
- feature-flag rollout docs
- CK UAT sign-off
Testing & QA Expectations
Section titled “Testing & QA Expectations”Fixture classes needed
Section titled “Fixture classes needed”- leading-zero / trailing-digit UPC variants
- shared UPC root across different pack sizes
- same product with
eachandcase - valid promo window
- expired promo window
- missing custom line item mapping
- multi-line discount orders
Must-have acceptance gates
Section titled “Must-have acceptance gates”- 0 silent SKU mismatches caused by normalization
- 0 net-price mutation when custom allowance line is required
- 100% of emitted discount lines map to approved catalog entries
- ambiguous promo or SKU cases escalate to review
1. Over-normalization of UPC aliases
Section titled “1. Over-normalization of UPC aliases”Risk: wrong SKU match
Mitigation: canonical model + ambiguity escalation
2. Pack-size parsing drift
Section titled “2. Pack-size parsing drift”Risk: wrong each vs case choice
Mitigation: persist pack-size evidence and confidence
3. Reviewers manually “fix” net price
Section titled “3. Reviewers manually “fix” net price”Risk: accounting corruption
Mitigation: UI lock/warning and separate allowance preview
4. Submission logic diverges from review logic
Section titled “4. Submission logic diverges from review logic”Risk: reviewer sees one thing, ERP gets another
Mitigation: submit from persisted proposed custom line items only
Recommended First Slice
Section titled “Recommended First Slice”If we want the smallest safe production-shaping slice first:
- add schema/storage for org extraction metadata
- import CK product + custom line reference data
- extend extraction result contract with discount/UOM/pack fields
- persist review metadata
- render on review page
- leave actual WhereFour discount submission disabled
That gives us a reviewable, auditable implementation before we risk writing anything into CK’s ERP flow.
Final Recommendation
Section titled “Final Recommendation”Do not implement this as a chip plus heuristics.
Implement it as:
- org-specific prompt/config
- canonical reference data in Postgres
- shared discount resolver
- persisted review metadata
- feature-flagged review rendering
- submission reusing the same metadata
That is the cleanest way to support Cleveland Kitchen now while keeping the feature reusable for future orgs.