# Ingredient Quoting Across 22 Supplier Portals

> A Python pipeline (Prefect, Skyvern and GPT-4.1) that quotes all 22 approved suppliers for every ingredient reorder and delivers a ranked comparison in about four hours instead of 3.5 days.

Section: Python Automation

Page: https://showcase.growlyze.cloud/case/ingredient-quoting-22-supplier-portals

**At a glance**

Mid-size generic drug manufacturer, ~40 SKUs in production. Five weeks. Python 3.12, Prefect, Skyvern Cloud, OpenAI GPT-4.1, Postgres, S3, Slack. Went from quoting a fifth of their supplier base per order to quoting all of it.

## The 6% nobody could see

Production planning flags a reorder for an active ingredient. A procurement officer opens a portal, searches the CAS number, pulls a price, downloads the spec sheet, pastes it into Excel. Then the next portal. Then the next.

Twenty-two suppliers were on the approved list. In practice the officer got through five or six before the purchase order had to go out, and the same five or six every time, the ones with the least annoying logins.

The waste wasn't the officer's afternoon. It was the sixteen vendors nobody had time to ask. When the full list was finally quoted, the cheapest compliant supplier turned out not to be one of the usual five surprisingly often.

## Why this wasn't a scraping job

Three things ruled out the obvious approach, and a hand-written Playwright suite had already been abandoned for exactly these reasons.

Vendors don't agree on what anything is called. Metformin HCl USP, metformin hydrochloride BP/EP, or a bare product code, same molecule, three listings, no shared key except the CAS number buried somewhere on the page.

Quotes don't arrive in one shape. Some portals render a price table. Some hand back a PDF. Some won't tell you anything until you've submitted an RFQ form and waited for an email.

And it's a regulated purchase. Pharmacopoeia grade, GMP status, and whether the supplier holds a DMF or CEP are pass/fail gates, not preferences. A cheap quote from a vendor without the right filing isn't a cheap quote. It's not a quote at all. Every choice has to survive an audit two years later.

## Following one RFQ through

Image: Multi-vendor ingredient quoting pipeline: RFQ intake (CAS number, grade, quantity), a Skyvern browser handles login, 2FA and CAPTCHA, prices come from an in-page table or from a downloaded and parsed PDF or emailed quote, GPT-4.1 normalises and validates them, and the ranked comparison goes to a Slack thread with an audit log. (https://showcase.growlyze.cloud/case-content/ingredient-quoting/image1.png)

A reorder request arrives as a CAS number, a grade, and a quantity. Before anything opens a browser, that gets resolved against an internal ingredient register, deterministically, on the CAS number, never on the product name.

Prefect then fans the request out to twenty-two isolated subflows, one per vendor. Each launches its own Skyvern cloud browser and logs in. Nine of these portals want 2FA and two sit behind CAPTCHAs; that's handled inside the login step rather than being a per-vendor engineering problem.

From there the paths diverge by portal. Where the price sits in the page, it's read straight off the page against a schema. Where it only exists as a PDF or an emailed quote sheet, the file is downloaded and parsed separately.

Everything then funnels back into a single normalisation pass, one model call, one strict schema, twenty-two vendors reduced to comparable rows: unit price, currency, unit of measure, MOQ, lead time, incoterm, grade, GMP status, DMF reference.

The ranked comparison lands in Slack about four hours later, with every row linking to the evidence behind it.

## Four forks in the road

### Skyvern rather than raw Playwright

The rejected option was writing and maintaining twenty-two scripts. That's what had already failed. At this many portals somebody is always redesigning something, and the maintenance load quietly exceeded the hours the automation was saving. Buying the resilience rather than building it was the entire reason the project was viable a second time.

### 1Password rather than a second vault

Skyvern can read credentials from several providers, and the client was already on 1Password. The alternative, standing up a dedicated secrets store, would have meant twenty-two sets of credentials living in two places, which is worse security dressed up as better engineering.

### Selectors first, model second

Every stable element on a page gets a selector and a prompt. The selector runs, costs nothing, and usually works; the model only engages when it misses. Going model-first on everything would have been simpler to write and roughly ten times the running cost.

### The model never decides what an ingredient is

It was tempting to let a model reconcile vendor product names. It isn't allowed to. Where a listing has no CAS field, the model may suggest a match, and that suggestion goes to a human. Misidentifying an ingredient in this industry isn't a data quality issue, and no accuracy figure would have made that trade acceptable.

## When it breaks

Image: Route Memorization in practice: a memorized route replays deterministically. If the replay works the quote is captured with its evidence hash; if the route breaks it is regenerated by AI with a diff alert; if it keeps failing it goes to a review queue with the session recording attached. (https://showcase.growlyze.cloud/case-content/ingredient-quoting/image2.png)

Skyvern's Route Memorization compiles a successful run into a deterministic script and replays it. When a portal changes and the replay fails, it re-plans with AI, finds the new path, and regenerates the script. That mechanism does the heavy lifting here.

What it doesn't do is announce itself. A regenerated route is a silent event, and silent events don't belong in a regulated purchasing process.

So the wrapper watches for it. Each subflow records its action count and wall-clock duration; a memorised replay sits in a tight, predictable band, and an AI re-plan blows straight through it. That signal tags the run as a regeneration, diffs the new route against the old one, and drops a summary into Slack so someone can see what the portal changed before the next cycle.

If a regenerated route produces a quote that can't be evidence-checked, that vendor goes to a review queue with its session recording attached instead of into the comparison. About 4% of quotes land there, mostly from two portals that render prices as images.

The result is that failure is local. One portal breaking means twenty-one quotes still land and the RFQ is still useful.

## Impact

|  | Before | After |
| --- | --- | --- |
| Vendors quoted per RFQ | 5–6 | 22 |
| Time to full comparison | 3.5 days | ~4 hours |
| Procurement hours per month | ~26 | ~3 |
