Skip to main content
DoneThat

AI Adoption GuideProcurementOrder

PO anomaly detection

ML flags POs where quantity, price, or supplier deviates from the underlying contract or historical category patterns before transmission.

Procurement processRequestApproveSourceEvaluateSelectOrderReceiveReview

By Don, DoneThat’s AI coach · updated

Hold the PO before it leaves, do not cancel it

The job is to compare a purchase order to its contract and to category history before the document is transmitted, then park mismatches for a buyer. A flag is a review. It is not a cancel, a vendor notification, or an audit finding.

Correction is cheap only while the PO is still yours. After send, you are in change-order territory: the supplier may already have confirmed, reserved capacity, or started to ship.

This control is about one PO's price, quantity, and supplier. Split-PO detection looks at a sequence of orders that stay under an approval threshold. Approval anomaly flagging scores the requisition while it is still in the queue. Do not fold those into one anomaly score. The buyer needs to see whether they are looking at a unit-price miss or a DoA-splitting pattern.

Source-to-pay suites such as SAP Ariba, Coupa, and Jaggaer are where the PO usually sits at send. GRC tools such as AuditBoard are where a parked review can become an evidence file if the pattern later needs one. Treat the check as a gate on transmission, not as a new system of record.

Compare price, quantity, and supplier to the contract, then to history

Run the contract checks first. They are deterministic when the PO carries the live terms. That depends on contract terms auto-applied to PO. If the line was typed from a quote in someone's inbox, you are comparing a guess to a guess.

If PO auto-generation from requisition filled the line from a catalog or from the contract, still run the check. Generation copies what it was given. It does not prove the contract is current, remaining volume is enough, or the vendor legal entity is the one on the agreement.

Unit price

Compare unit price, currency, and unit of measure to the contracted rate for that SKU, including volume tiers and effective dates. A price above the live contracted rate is a hold. A price that matches a published volume break is not a price error. Match the contract in force on the PO date, not last year's median.

Quantity

Compare ordered quantity to remaining contract volume where you have a quantity cap or a blanket, and to typical call-off size for that material at that site. A call-off many times the usual size can be a launch, a safety-stock build, or an extra zero. The model cannot tell. Park it.

Do not treat remaining-commitment as a void. The buyer amends or documents the exception.

Supplier

The PO vendor should be the contracted legal entity, or an affiliate the contract names. A different vendor on a contracted SKU is a hold even if the unit price looks familiar. Do not swap the vendor number in the background to make the line match.

Category history only after the contract check

History catches what no clause covers: a quantity nobody has ordered at this plant, a price that matches an expired rate card, a vendor on the master who has never supplied this category here.

Compare like with like: same category node, company code, and site, over a window that includes ordinary seasonality. A miss on any of the three fields parks the PO. Show the buyer the contracted value, the PO value, and the peer set. A blended risk number that hides the field is how they click through.

A 45,000-unit packaging call-off that looks wrong until you read the contract

The walkthrough is illustrative, not a measured result.

A plant buyer is covering a new product launch. Converter Co is on a packaging contract: SKU C-200, $0.41 each below 40,000 units, $0.34 each at 40,000 and above, DDP the plant. Typical C-200 call-offs at this site are 6,000 to 10,000 units at the $0.41 rate.

The buyer creates one PO: Converter Co, C-200, 45,000 units, $0.34, DDP. Approvals are done. The document is in the send queue.

Contract comparison: supplier matches Converter Co; unit price $0.34 matches the volume tier for 45,000; quantity is within remaining blanket volume. History comparison: 45,000 is several times the usual call-off at this plant, and $0.34 sits below historical C-200 prices because history is full of sub-tier orders.

The right hold is quantity versus site history. The buyer opens the launch forecast, confirms the volume tier, and releases. The PO goes out at the contracted break price.

Three wrong actions on the same PO:

  • Flag $0.34 against historical $0.41 and treat it as a price error. That blocks a legitimate price-break.
  • If the launch used newly contracted SKU C-210, with no shipments at this site, flag C-210 as an unknown material. That treats a new contracted SKU as an anomaly. The peer set is the contract line, not last year's catalog.
  • Cancel the PO because quantity is unusual. The next urgent buy will then be sent around the control.

The hold did its job when a person looked. Auto-cancel would have been the failure.

Price-breaks, new SKUs, and auto-cancel will burn the control

A history-only price model learns the rate you used to pay, not the rate you negotiated. Volume breaks, indexation, and a newly effective price list all look cheap or expensive against last year's median. Contract first. History second. If the unit price matches the live tier, do not park it as a price anomaly. You can still park quantity.

New contracted SKUs fail the same way. A replacement grade, a dual-source item, or a SKU added at the last negotiation has no site history. Require a miss on the contract (wrong vendor, wrong price, quantity above remaining) before a first-time SKU alone creates a hold. Otherwise every assortment change becomes noise.

Do not auto-cancel. Cancellation withdraws a commitment. It can trigger supplier charges, and it is hard to unwind if the buy was real. A model score cannot carry that. Park, notify the buyer of record (not only a shared mailbox), and require a reason to release or amend.

Urgent MRO will look anomalous. So will a first order after an acquisition, and a first call-off on a new blanket. Give those a service level on the hold and a named buyer who can release.

Treat a hard contract miss and a history-only oddity as different holds. A unit price above the live contract should not release without an amend or a documented exception. A large-but-contracted call-off should release with a reason.

The buyer amends or releases, matching is later

Give the buyer the PO, the contract clause or catalog row, remaining commitment, and recent call-offs for that material at that site. They choose: release, amend price or quantity, change vendor, or return to the requester.

Do not send the flag only to the original approver if they already stamped a typed-from-memory PO. The gate is a commercial look, not another click on the same screen.

If the buyer amends, transmit the amended PO. If they release with a reason, keep the reason. That file is what you show when someone asks why a large call-off went out.

What still goes wrong after send belongs downstream. Three-way match automation compares invoice and goods receipt to the PO you actually transmitted. If you keep sending POs at the wrong price, matching will throw price exceptions forever. Fix the PO here.

Coupa, SAP Ariba, and Jaggaer are where that hold usually lives. AuditBoard is where a workpaper lives if the pattern later looks like a control failure rather than a one-off extra zero. Do not write "the model cancelled the order" into either place. The model parked it. A person decided.

Run the checks silent against recent send history before you hold live POs. Look at what would have fired on volume-tier orders and on SKUs added in the last contract cycle. Keep price, quantity, and supplier as separate reasons so you can switch off a noisy signal without killing the gate.

Is this worth automating for you?

Whether this pays back depends on how much time it takes your team today. Most teams estimate that from memory, and the estimate is usually wrong in one direction or the other.

DoneThat reconstructs where the time actually went, with no timers to forget, so you can measure the baseline before committing to a project and check the gain afterward.

Measure the baseline first