Supplier price lists in PDF or Excel: which increases actually raise your product cost
A supplier sends a price list with hundreds of lines, and the real question is how much more a specific product now costs to make. This n8n workflow reads the list in PDF, XLSX or CSV, compares it with the prices you accepted last time and turns the changes into the material cost of every product in your bill of materials.
Author
- Adrian HuniaCo-founder of SEVENEDGE
- Published:
- Last verified:
- n8n version:
- 2.8.4
In short
What it does
- Reads a supplier price list in PDF, XLSX or CSV uploaded through a form
- Checks every code and price the model read against the PDF text
- Converts prices to a base unit: packs, metres, kilograms
- Shows a person only the uncertain lines for approval
- Calculates the material cost change of every product per unit and per year
- Writes a negotiation brief from already calculated numbers only
What it does not do
- Does not read scanned PDFs without a text layer
- Does not convert currencies
- Does not handle quantity-based tiered prices
- Does not replace an ERP that already imports price lists
What it needs
- n8n with Data Tables (tested on 2.8.4)
- An Anthropic API key
- A BOM table: product, supplier code, quantity, unit, annual volume
Supplier price lists in PDF or Excel: which increases actually raise your product cost
A supplier sends a new price list. A few hundred lines, some priced per pack, some per metre, a few codes renamed without notice. Purchasing does not need a list of what went up. It needs to know how much more a specific product now costs to make, and what that adds up to over a year.
This n8n workflow reads a price list in PDF, XLSX or CSV, compares it with the prices you accepted last time, turns the changes into the material cost of every product in your bill of materials, and ends with a short brief for the conversation with the supplier. Uncertain lines are approved by a person before they touch anyone's costing.

Why "paste the price list into a chatbot" is not enough
The obvious idea is to give a model both lists and ask for a comparison. With a dozen lines it even works. With a real price list, three things break.
Units and packs
In January an M8x20 screw cost 34.00 PLN per pack of 100. In November the same screw is listed at 0.36 PLN per piece. A price-to-price comparison shows a 99% drop. In reality it is a 5.9% increase.
A model sometimes catches this and sometimes does not. And this is the worst direction to get it wrong, because the increase looks like a bargain.
Rows that were never there
A model reading a PDF can skip a row or add one that is not in the document. In a full price list a skipped row looks like a discontinued item. An added row looks like a new product with an invented price.
The model does not know your BOM
Even a perfect list comparison answers the wrong question. A 16.6% rise in powder coating matters more than a 114% rise in a lock if you use two kilograms of coating per product and buy the lock once a quarter.
The rule: the model copies, code calculates
The whole workflow rests on a division of labour. The language model only does what code cannot do well, which is reading messy documents. Everything that can be calculated is calculated by code.
Excel and CSV
The model gets the column names and a few sample rows. It returns only a mapping: which column is the code, which is the price, which is the unit. Code then reads every row.
A 5,000-line list costs one model call, the same as a 20-line one.
The model copies rows exactly as printed. The price stays text such as "1 234,50". Nothing is converted or rounded.
Code then looks up every copied code and every price in the original PDF text.
That check is the most important part of the workflow:
const squash = s => String(s || '').toUpperCase().replace(/\s+/g, '');
const haystack = squash(fullText);
const digitsOf = s => String(s || '').replace(/[^0-9]/g, '');
const numberTokens = new Set([
...(fullText.match(/\d+(?:[.,]\d+)?/g) || []),
...(fullText.match(/\d{1,3}(?:[ \u00a0.]\d{3})+(?:,\d+)?/g) || []),
].map(digitsOf));
if (!haystack.includes(squash(r.supplier_sku))) issues.push(`code "${r.supplier_sku}" not found in the PDF`);
if (!numberTokens.has(digitsOf(r.price))) issues.push(`price "${r.price}" not found in the PDF`);
In testing we added a row to the model's output that was not in the price list. It did not get through: it went to review with a note that neither the code nor the price appears in the document.
One number to compare: price per base unit
Every price is converted to a price per piece, metre, kilogram, litre, m² or m³. Only those numbers are compared.
| In the price list | Base unit | Factor |
|---|---|---|
| pack (100 pcs) | pcs | price ÷ 100 |
| 100 pcs | pcs | price ÷ 100 |
| mb, lm | m | 1 |
| g | kg | 0.001 |
| t | kg | 1000 |
| ml | l | 0.001 |
Pack size comes from a "quantity per pack" column, or from a description such as "opak. 100 szt." when there is no such column. Prices written like "1 234,50 zł" are parsed by code too, not by the model. Polish and other European number formats are handled.
When a person decides
A second page of the same form shows only the lines an automation should not decide on its own. Each has a reason and a choice: accept or reject.
| Situation | Why not automatic |
|---|---|
| unit changed (e.g. tube from metres to 6 m bars) | prices are not comparable |
| currency changed | the exchange rate is a decision, not arithmetic |
| price moved 40% or more | usually a pack-size error, sometimes a real increase |
| a new code resembles one that disappeared | the supplier may have renamed it |
| a row not confirmed in the PDF | the model may have invented it |
| an item missing from a full list read from PDF | it may have been skipped while reading |

A rejected line keeps its old price and is written to the change log with the decision. In testing, a lock jumping from 6.50 to 13.90 PLN was rejected and did not affect any costing.
Impact on the bill of materials
The bom_lines table holds one row per component: product, supplier, supplier code, quantity per unit, unit and annual volume. For each product, code calculates the material cost before and after the change.
| Product | Per unit | % | Per year |
|---|---|---|---|
| REG-200 shelving unit | +16.64 PLN | +3.51 | +9,984 PLN |
| SZF-90 tool cabinet | −11.40 PLN | −1.49 | −2,052 PLN |
| WOZ-50 workshop trolley | +4.20 PLN | +0.75 | +1,050 PLN |
For the shelving unit the biggest drivers are powder coating (+11.52 PLN) and the 40x40 profile (+11.04 PLN), partly offset by cheaper sheet steel (−7.00 PLN). That is what you take to the supplier.
Code also catches things that quietly break costing: a discontinued component, a BOM counted in metres while the price is per bar, a component with no price on file.
The negotiation brief
At the end the model writes a short brief: what changed, which increases matter for our products, what to ask the supplier, and what to fix internally. It receives only numbers that are already calculated and is told not to compute new ones.
Use only the numbers in DATA. Every figure you mention must appear there; do not calculate new ones.
The brief is an extra, not the source of truth. The numbers in the table above come from code and do not depend on the model.
Configuration
All thresholds live in one Settings node.
| Parameter | Value | Purpose |
|---|---|---|
alertThresholdPct | 3 | increases from this percentage go on the alert list |
reviewAbovePct | 40 | changes from this percentage need approval |
fuzzyMatchMin | 0.6 | description similarity that suggests a renamed code |
maxReviewRows | 25 | how many lines fit on the approval page |
defaultCurrency | PLN | currency when the list does not state one |
csvEncoding | utf8 | windows1250 for older Central European exports |
A separate branch, run once, creates the three n8n Data Tables (supplier_prices, price_change_log, bom_lines). CSV files with ; as the separator, common wherever the comma is the decimal mark, are detected automatically.
Limitations
Scanned PDFs are not supported; you need a text layer or the Excel/CSV version.
No currency conversion. Lines in a currency other than the default are flagged and left out of totals.
Tiered prices (one price from 100 units, another from 1,000) are not modelled. When mapping columns, the model picks the base price and adds a note.
Renamed codes are matched on description similarity. A supplier who changes both the code and the description shows up as one new item plus one discontinued item.
The first list from a supplier compares nothing; it only saves the baseline.
We tested on fictional data on n8n 2.8.4, with the model responses substituted during the test. All logic outside the model ran on the instance, but we have not measured how often Claude maps columns wrongly or skips rows on real price lists from different suppliers.
When it does not make sense
With a handful of suppliers and stable prices, comparing lists by hand once a quarter takes less time than maintaining the BOM table. The workflow starts paying off when price lists arrive more than once a month, run to hundreds of lines, and your own selling prices depend on material cost. When supplier prices, BOMs, orders and stock need to live in one place, that is no longer a workflow but a production management system.
How to cite this
Adrian Hunia (2026). Supplier price lists in PDF or Excel: which increases actually raise your product cost. SEVENEDGE. https://sevenedge.pl/en/workflows/cenniki-dostawcow-koszt-bom (accessed: November 5, 2026)
Sources
- n8n docs — Data tablesn8n docs
- n8n docs — n8n Formn8n docs
- n8n docs — Extract From Filen8n docs
- n8n docs — Structured Output Parsern8n docs
Who is behind this
Adrian Hunia
Co-founder of SEVENEDGE
Co-founder of SEVENEDGE. Designs and deploys business systems for small companies, from production management to time tracking. Works with Next.js, FastAPI and PostgreSQL. SEVENEDGE has no subcontractors — both owners write the code.
Related documents
Other workflows we documented the same way.
- Contract review against your playbook: clause by clause, with a gate for the lawyerA contract lands in a folder, Claude extracts the clauses, an agent assesses each one against your negotiating positions, and code computes the verdict. While writing this up we found a bug in the published version that dropped two of every three assessments. We describe it here along with the fix.
- A three-agent AI support desk: triage, documentation research, reply draft and a human gateOne agent classifies the ticket, a second looks for the answer in your own documentation, a third drafts the reply. Only a reply that passes three conditions at once goes out on its own. A person sees the rest, and every question your docs cannot answer becomes a documentation task.


