Skip to content
This repository was archived by the owner on Jun 23, 2026. It is now read-only.

Repository files navigation

Procurement Spend Intelligence Agent

Procurement is the largest controllable cost line in mid-market manufacturing and distribution that almost no one actively manages -- this demo shows an AI agent surfacing the EBITDA inside it for a single portco, then proving the play scales fund-wide.

Point the agent at a portfolio company's AP / invoice export. It cleans the vendor master, classifies spend into a standard procurement taxonomy, sizes the recoverable opportunity lever by lever, and turns the result into a board-ready EBITDA bridge -- for one portfolio company, then across the fund.

Built as a portfolio-company value-creation demo. All data in this repo is synthetic; the figures are illustrative, but the logic is production-grade.


Why this exists

Procurement is the single largest controllable cost line a mid-market manufacturer or distributor never actively manages. Fragmented vendor masters, no category strategy, no fund-level leverage -- a procurement workstream sits in nearly every PE 100-day plan, and it is the lever where portfolio scale most clearly beats any single company's negotiating power. Published procurement-optimization programs commonly target 8-15% addressable-spend reduction in indirect categories.

Today that work is bought from procurement consultants. A mid-market sourcing engagement from a firm like Insight Sourcing Group, Alvarez & Marsal, McKinsey Operations or a Big Four procurement practice may be a six-figure engagement that runs 8-16 weeks and produces a point-in-time deck plus a vendor-by-vendor RFP plan. The diagnostic itself, though, is now automatable. Re-pricing vendor consolidation, sizing maverick spend, and isolating category-level opportunity is arithmetic, and arithmetic scales.

Procurement-sourcing engagement Procurement Spend Intelligence Agent
Cost Six figures, or a contingency fee on "savings found" API usage -- single-digit dollars per run
Time to first output 8-16 weeks Under a minute
Cadence One-off, point-in-time Re-run on every AP-data refresh
Output Savings deck + RFP plan EBITDA bridge + sensitivity + negotiation playbook
Portfolio coverage One company per engagement Same agent across every portfolio company

The unlock isn't the single-company saving. It's that the same agent runs against every portfolio company -- turning a one-off cost cut into a repeatable, fund-level value-creation lever, and surfacing the multi-portco vendor pool no single company has on its own.


The scenario: Meridian Industrial Supply

Meridian Industrial Supply is a ~$220M-revenue, fourteen-branch Midwest industrial distributor of MRO, safety, and packaging products to manufacturers across the region. Acquired by Hadrian Capital Partners, Fund III via a roll-up of three regional distributors, Meridian now runs on three different ERP instances (NetSuite, Sage Intacct, and a legacy AS/400) that have never been consolidated. Its vendor master shows ~1,800 active suppliers; the actual count is closer to 1,100 -- the rest is duplicate aliases. The procurement function is two AP clerks and a controller. With Hadrian's next portfolio-ops review on the calendar, procurement is the largest controllable cost line that no one has actually examined -- which is exactly where this agent starts.


What the agent does

Part 1 -- The single-portco diagnostic. Point the agent at Meridian's raw AP export. It normalizes the vendor master with rapidfuzz and light clustering (collapsing alias variants down to canonical suppliers), classifies every line into a standard taxonomy (rule-based on GL account + description keywords), and surfaces the three diagnostics every procurement operator asks for: tail spend (the bottom 80% of vendors driving a small share of dollars), maverick spend (off-PO purchases inside contracted categories), and concentration risk (single-source exposure in categories where that isn't a feature). Savings are decomposed lever by lever -- vendor consolidation, category rationalization, payment-terms standardization, and contract-coverage uplift -- never a single black-box percentage. The result rolls into a board-ready EBITDA bridge with downside / base / upside flow-through, a sensitivity table, and implementation-risk notes.

Part 2 -- The portfolio rollup. One company is not a fund -- and procurement is the lever where the fund-level math matters most. Part 2 re-runs the diagnostic across the seven Hadrian Capital Partners, Fund III portfolio companies, builds a unified fund-wide spend cube (vendor x category x portco), and isolates where the real leverage lives: vendors used in three or more portcos with no master agreement; categories where fund-pooled volume crosses tier thresholds suppliers actually price against; and payment-terms misalignment costing the fund free working- capital financing. Apply a tiered consolidation-discount model -- 8% / 15% / 22% for light pooling / base / full bloc, mirroring the freight agent's tiered structure -- and the output is a Fund-Wide Negotiation Playbook, a category-by-category cheat sheet (top vendor, total pooled spend, # of portcos, target discount, recommended action) an operating partner can take into a kickoff meeting the next day. The portfolio savings roll into a fund-level EBITDA and enterprise-value bridge, each company valued at its own entry multiple. No multiple expansion; value creation is purely operational.


Architecture

flowchart LR
    G[data_gen.py] --> I[invoices.csv · Meridian]
    G --> IS[invoices_*.csv<br/>other six portcos]
    I --> N[normalize.py<br/>rapidfuzz + clustering]
    IS --> N
    N --> C[categorize.py<br/>GL + keyword rules]
    TX[category_taxonomy.json] --> C
    C --> AN[diagnostic.py<br/>Part 1 — tail · maverick · concentration<br/>four-lever savings · EBITDA bridge]
    C --> PF[portfolio.py<br/>Part 2 — fund spend cube · tiered discount<br/>fund EBITDA · EV · MOIC lift]
    AN --> PB[playbook.py<br/>category negotiation cheat sheet<br/>model-written commentary]
    PF --> PB
    SEED[scripts/seed_llm_cache.py] -->|one-time, real API| PC[playbook_cache.json]
    PC --> PB
    AN --> VZ[viz.py · Plotly]
    PF --> VZ
    PB --> VZ
    AN --> APP[app.py · Streamlit]
    PF --> APP
    PB --> APP
    AN --> NB[notebook.ipynb]
    VZ --> APP
    VZ --> NB
Loading

The agent automates the end-to-end workflow a procurement consultant runs by hand. The analytics underneath -- vendor canonicalization, category classification, the savings math, and the entire fund rollup -- are fully deterministic: every figure traces to an invoice and explicit arithmetic, and no model ever sits between an input and a number, so the output is auditable line by line. A language model is used at one step only -- writing the category-level negotiation talking points -- and that layer is provider-agnostic: Claude by default, swappable for any model. Its output is cached alongside the data, so mock mode runs fully offline and reproducibly; the model is called only when the cache is explicitly rebuilt. The Streamlit app and the walkthrough notebook share the same src/ modules, so the interactive demo and the deep-dive can never drift apart.


Methodology & assumptions

This section earns the trust of a finance reader: every assumption the agent makes is stated here, and every one is exposed as a named, tunable parameter in config.py.

Data. The agent's input is invoices.csv -- an ordinary AP / invoice export of the kind any ERP produces: NetSuite, QuickBooks, Sage Intacct, Microsoft Dynamics. A roll-up like Meridian rarely keeps that data in one place -- it is usually spread across three to five disconnected systems, and consolidating it into a single file is the real first step of any engagement (which is itself a symptom of the fragmentation the demo sets out to fix). All ~30,000 invoice lines for Meridian are synthetic, generated by src/data_gen.py and modeled for realism: the same vendor shows up under 3-6 name variants, ~30% of lines are GL-miscategorized, payment terms are scattered Net 15 / 30 / 45 / 60 / EOM, there is a long tail of one-off vendors, and ~12-18% of lines in contracted categories carry no PO reference (the maverick- spend signal). Meridian's $220M revenue and seven-portco fund context are scenario premises, not derived figures.

Vendor normalization. A two-pass clusterer collapses raw vendor strings to canonical suppliers. Stage A is exact-match on a noise- stripped blocking key (lowercased, with corporate suffixes and division tags removed). Stage B is rapidfuzz token_set_ratio above VENDOR_MATCH_THRESHOLD (default 88), with a numeric-token guard -- two strings whose numeric identifiers diverge (account IDs, vendor codes) are never merged regardless of textual similarity. The threshold is the one knob that matters; the seeded data scores precision >= 0.99 and recall >= 0.96 against the ground-truth answer key.

Category taxonomy. Ten categories -- MRO, IT & Software, Shipping & Logistics, Office Supplies, Professional Services, Raw Materials, Travel & Entertainment, Utilities, Facilities, Other Indirect -- committed to data/category_taxonomy.json. The classifier is rule-based: GL account ranges set the primary category, description keywords override when the GL is ambiguous. On the seeded data the GL pass catches roughly 77% of lines cleanly; the keyword override resolves the ~23% miscategorized at the GL level.

Savings levers (Part 1). Gross savings are decomposed into four named levers -- vendor consolidation, category rationalization, payment- terms standardization, contract-coverage uplift -- never a single black-box percentage. Each lever's gross modeled opportunity is then cut by a uniform realization haircut (default 33%) -- the share that supplier-transition friction, change-management drag, and genuinely necessary off-PO purchases keep off the table. Realization mirrors the freight agent's 33% haircut for consistency across the suite.

Tiered portfolio discount (Part 2). The 8% / 15% / 22% tiers for light pooling / base / full bloc are headline procurement-consulting benchmarks (Insight Sourcing Group, A&M, McKinsey Operations -- not supplier quotes). They are applied to the addressable pooled spend net of standalone savings, then scaled by CONTESTABLE_SHARE (default 40%): the share of indirect spend genuinely open to fund-level re-negotiation in a 12-18 month window. The remainder is locked into existing multi- year contracts, specialty-vendor switching costs, or ad-hoc spend too unstructured to consolidate. Tune the contestable share if a portfolio- ops team has hard data on portfolio contract coverage.

Cross-agent guardrails. Shipping & Logistics is owned by the Freight Carrier Optimization Agent at the fund level -- it is shown in the procurement category map but is flagged and does NOT contribute to the procurement EBITDA bridge. Payment-terms standardization is the payables-side analogue to the Working Capital Agent's AR / DSO lever; the two are not the same pool of dollars (AR float is customer-side, AP float is supplier-side), so both flow into the suite-level value bridge, but the overlap is flagged explicitly so it does not double- count.

EBITDA & value. Procurement savings flow to EBITDA at 70% / 80% / 90% (downside / base / upside) to account for leakage and trade-offs. They are treated as recurring, above-the-line opex reduction. Enterprise value created equals the EBITDA lift times the entry multiple -- no multiple expansion. The one-time cash release from payment-terms float is valued separately and goes 1:1 to deleveraging. All figures are pre-tax.

Reproducibility & scope. Mock mode is the default -- the seeded generator and the cached LLM playbook produce byte-identical results offline, and the real API is touched only by the one-time seed script. Scope is the seven Hadrian Fund III portfolio companies' indirect and addressable-direct AP spend; payroll, capex projects, and acquisition financing are out of scope.


Illustrative results

Two synthetic runs -- one company, then the fund.

Part 1 -- Meridian Industrial Supply. The four-lever model on Meridian's ~30,000-line synthetic year ($69.0M of AP spend), with a uniform 33% realization haircut applied to every lever:

  • $2.50M of realized annual procurement savings -- 3.6% of AP spend -- decomposed lever by lever: category rationalization $1.49M, contract-coverage uplift $0.51M, vendor consolidation $0.44M, payment-terms standardization (opex-equivalent) $0.07M.
  • A base-case EBITDA bridge (80% flow-through): a $2.00M EBITDA lift and $29.59M of enterprise value created at the 9.5x entry multiple.
  • Across downside / base / upside flow-through (70 / 80 / 90%): a $1.75M-$2.25M EBITDA lift and $25.9M-$33.3M of enterprise value.

Part 2 -- the Fund III rollup. The diagnostic re-run across the seven Hadrian Capital Partners, Fund III portfolio companies ($938M combined revenue, $266M combined fund-wide AP spend):

  • $8.89M of standalone realized procurement savings -- the sum of each portco optimizing alone, before any portfolio leverage.
  • A tiered portfolio-leverage discount (8 / 15 / 22% for light pooling / base / full bloc) on the $114M leverage base (net of standalone savings and the 40% contestable-share filter) adds a further $2.4M-$6.7M -- buying power no single mid-market portco commands.
  • At the base case (15% pooling, 80% flow-through): a $13.48M combined realized savings, a $10.79M fund-level EBITDA lift -- 30.8% of the seven companies' $35.1M combined entry EBITDA -- and $100.3M of enterprise value created, each portco held at its own entry multiple.
  • The one-time payment-terms cash release adds $5.22M of deleveraging.
  • On $153.4M of invested equity, an implied +0.69x MOIC lift (operating +0.65x, cash deleveraging +0.03x).

All figures are synthetic and illustrative. Reproduce them with python run_demo.py.


Quickstart

pip install -r requirements.txt

python run_demo.py                      # full pipeline in the terminal
streamlit run app.py                    # interactive app
jupyter notebook notebook.ipynb         # walkthrough notebook

Mock mode (default). With no API key set, the agent runs end-to-end against the cached playbook committed in data/playbook_cache.json. No key, no network, byte-identical results on every run.

Live mode. To rebuild the playbook cache against a live model, copy .env.example to .env and add an ANTHROPIC_API_KEY, then run python scripts/seed_llm_cache.py. The LLM layer is provider-agnostic -- set PROCUREMENT_MODEL, PROCUREMENT_ENDPOINT and PROCUREMENT_KEY_ENV in the environment to point at any provider.

The Streamlit app is built to deploy as-is to Hugging Face Spaces for the public demo.


Repo structure

procurement-spend-intelligence-agent/
├── README.md
├── LESSONS_LEARNED.md
├── requirements.txt
├── .env.example              live-mode key (mock needs none)
├── app.py                    Streamlit app
├── run_demo.py               command-line runner
├── notebook.ipynb            walkthrough notebook
├── config.py                 every tunable assumption, in one file
├── data/
│   ├── invoices.csv          ~30k synthetic Meridian invoice lines
│   ├── invoices_*.csv        per-portco invoice files for the other six
│   ├── portfolio.csv         fund master -- entry economics per portco
│   ├── category_taxonomy.json  standard taxonomy + GL/keyword rules
│   ├── playbook_cache.json   cached negotiation talking points
│   └── _canonical_vendors.csv  ground-truth answer key for normalize.py
├── src/
│   ├── data_gen.py           seeded synthetic AP generator (all 7 portcos)
│   ├── normalize.py          vendor master cleanup (rapidfuzz + clustering)
│   ├── categorize.py         GL + keyword classifier
│   ├── diagnostic.py         Part 1 -- single-portco diagnostic
│   ├── portfolio.py          Part 2 -- fund rollup + tiered discount
│   ├── playbook.py           negotiation playbook (LLM layer, cached)
│   └── viz.py                Plotly visualizations
└── scripts/
    └── seed_llm_cache.py     one-time real-API call to refresh the cache

What this demo signals

This repository is a hiring artifact as much as a tool. Here is what it is built to show:

  • He finds the lever. Procurement is rarely the headline lever in a 100-day plan; payroll and pricing usually are. The demo opens by identifying procurement as the largest controllable, unmanaged cost line at Meridian -- before optimizing anything. That is the judgment a value-creation team hires for: knowing where margin actually hides, even when it is unglamorous.

  • He speaks the language of a value-creation plan. The output isn't "savings." It's an EBITDA bridge with downside / base / upside flow- through, a sensitivity table, a fund-level enterprise-value roll-up, and a MOIC lift on invested equity -- structured the way a deal team and a board expect to receive it.

  • He thinks at the fund level. Part 2 takes the single-portco play and runs it across seven Hadrian companies. He isn't optimizing one company's procurement -- he's building a repeatable, portfolio-wide value-creation lever, identifying the multi-portco vendor pool, and pricing what fund-level negotiation actually captures over what a single portco can do. That is how operating partners are measured.

  • He builds, not just models. This is a working application: a modular codebase, a deterministic analytical pipeline, a provider- agnostic LLM layer with reproducible offline mode, an interactive app, and a walkthrough notebook. He can carry a thesis from data to model to shipped tool without a team behind him.

  • He doesn't oversell. Every assumption is explicit and tunable, the Raw Materials category is excluded from fund-wide leverage where it doesn't belong, the freight category is flagged as owned by the freight agent (not double-counted), and implementation risk is stated up front. The analysis is built to survive scrutiny from someone who does this for a living.


Who built this

Built by Peter Hanselmann -- a Chicago-based finance and accounting professional with a private-equity background, including Kirkland & Ellis and Kinzie Capital Partners. All code, data, and analysis in this repo are his own.


Get in touch

Two ways to take this further.

If you build value-creation or operations teams -- this repo is a more honest writing sample than a resume. It shows how I frame an EBITDA opportunity, how I think across a portfolio, and that I can ship the tool, not just the deck. If that's the kind of operator you want on your team, I'd like to talk.

If you run a portfolio and want this pointed at real procurement data -- the demo runs fully offline in mock mode, so you can see the logic end to end before anything touches your numbers. Moving it onto a real company's AP file is a column-mapping exercise, not a rebuild. Happy to walk a team through what that would take.

Reach me on LinkedIn: linkedin.com/in/peter-c-hanselmann-cpa.


Disclaimer

Every invoice, vendor, portfolio company, and figure in this repository is synthetic. Meridian Industrial Supply, the Hadrian Capital Partners portfolio, and every supplier name in the data set are fictional. The savings and EBITDA figures are illustrative and exist to demonstrate the agent's logic. This tool supports procurement-spend analysis; it is not legal, tax, or financial advice.

© 2026 Peter Hanselmann. All rights reserved. This repository is public so it can be read and evaluated; it is not licensed for reuse, redistribution, or commercial use.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages