Ordering trades availability against cash.Your system should be able to price it.
Orderbind forecasts demand over the interval you are actually exposed to, turns a service target into an order quantity, and places the order with the supplier. You choose the availability. It tells you what that costs in stock, on your own history, before you commit to it.
Y-axis starts at 88%, not zero, so the curves are legible. Below it, off this chart: min/max at ×0.8 (69% fill) and ×1.0 (80% fill). Lead time 7 days, review every 7. Both policies run the identical consumption simulation against the identical demand.
Same availability as min/max, on 50% less stock. The curve is rolling-origin: at every review the model sees only the days strictly before it.
Synthetic range, 8 SKUs over 24 months. Not a customer result: we have not run this on a real file yet, and that is the ask.
A zero means three things.Only one of them is demand.
sold or genuinely nothing wanted out of stock closed
The location was shut
Sunday, a public holiday, a stocktake. Nobody could have bought it. Counting these days flattens the average across a week that never had seven trading days in it.
You had none to sell
This is the expensive one. The zero is not an absence of demand, it is an absence of stock, and it is indistinguishable from the real thing in every sales export ever produced.
Nobody wanted it
The only zero that is information. On an intermittent line most days look like this and that is normal, which is exactly why the other two hide so well.
Same thirty days, same estimator. Read blind, every zero is demand: 9.1 units a day. Read with the stockouts flagged and excluded from estimation: 13.5 units a day, 49% higher, and the reason the line was dying.
Masking is the minimum honest treatment, not the best one. Treating a stockout as a left-censored observation and estimating through it recovers more, and needs the flag to exist first, which is why the flag is in the data model on day one.
One dial, and no units on it.
A coverage factor is not a quantity, a service level or a promise. It is a multiplier on an average, and the only way to find out what any setting buys you is to run it for a quarter and count what you missed. A service target is the same decision with the units put back on.
| setting | fill rate | avg. stock |
|---|---|---|
| ×0.8 | 68.9% | 272 |
| ×1.0 | 79.6% | 363 |
| ×1.2 | 88.8% | 488 |
| ×1.4 | 93.8% | 630 |
| ×1.6 | 96.4% | 787 |
| ×1.8 | 97.7% | 966 |
| ×2.0 | 98.4% | 1,122 |
| ×2.4 | 98.6% | 1,431 |
| target | fill rate | avg. stock |
|---|---|---|
| 80.0% | 96.8% | 493 |
| 85.0% | 97.2% | 527 |
| 90.0% | 97.8% | 577 |
| 95.0% | 98.4% | 653 |
| 97.5% | 98.8% | 718 |
| 99.0% | 99.5% | 813 |
Same range, same lead time, same simulation as §1. The left column asks you to guess and find out; the right one asks you which row you want. Note that the targets are not the achieved fill rates. A target is an input to a distribution, not a promise, and the gap between the two columns is itself worth reading.
Most of a range is not smooth.
Demand is classified before it is forecast, on the average gap between orders and the variability of their sizes, on the Syntetos-Boylan cutoffs, 1.32 and 0.49. Each class then gets an estimator that suits it. This is not a model-selection contest run per SKU; it is four known shapes and four known treatments, which is both better behaved and possible to explain to the person whose order it is.
| item | gap | size cv² | class | method |
|---|---|---|---|---|
| V-1042 Pilsner, 330 ml, case of 24 | 1.00 | 0.08 | smooth | smoothing |
| V-2210 Sparkling water, 500 ml, case of 12 | 1.00 | 0.09 | smooth | smoothing |
| V-3381 Cola, 330 ml, case of 24 | 1.00 | 1.19 | erratic | smoothing |
| V-4507 Malt extract, 5 kg sack | 3.94 | 0.17 | intermittent | SBA |
| V-5119 Draught coupler, type A | 3.71 | 0.19 | intermittent | SBA |
| V-6023 CO₂ cylinder, 10 kg | 6.01 | 2.46 | lumpy | TSB |
| V-7734 Bar runner, printed | 6.56 | 2.34 | lumpy | TSB |
| V-8890 Glassware, 40 cl, pallet | 6.62 | 1.87 | lumpy | TSB |
- smooth Demand most days, consistent sizes. Smoothing with a day-of-week profile.
- erratic Demand most days, sizes that swing hard. Same estimator, a much wider distribution.
- intermittent Long gaps, small consistent sizes. Croston with the Syntetos-Boylan bias correction.
- lumpy Long gaps AND sizes that swing. Teunter-Syntetos-Babai, which decays towards obsolescence.
Classes above are what the classifier returned, not what the fixture intended. The two agree on all eight, and a test asserts it, because a demonstration that quietly relabels its own data demonstrates nothing.
A forecast is a spreadsheet.This one places the order.
Planning that stops at a recommendation hands the last mile back to the buyer: export, retype, email. That step is where the errors live, and it is why the planning module you already paid for is not being used.
- 01
It reads what you already export
A sales history, a stock position, a supplier catalogue. CSV from the ERP is enough to start. The column names are detected, including Icelandic ones, and dates in dd.mm.yyyy and decimal commas are handled rather than mangled.
- 02
Every line says why
Demand class, method, expected demand over the protection interval, the order-up-to level, current position, resulting cover, and which constraint bound the number: pack size, minimum order, shelf life, maximum cover. A proposal that cannot explain itself gets overridden until it is switched off.
- 03
One purchase order per supplier
The approved lines are split by supplier and each order is handed to a destination connector. The idempotency key is derived from the run and the supplier, not generated, so a retry after a timeout is the same order and not a second one.
- 04
The acknowledgement is business-level
Accepted, rejected, partial or pending, not "HTTP 200". A file written to a folder reports pending, because it has been delivered and not accepted, and a system that conflates those two teaches its users to ignore it.
Connectors are an interface, not a plugin marketplace with three entries. A source declares which capabilities it has (sales, items, positions, lead times, closed days, availability) and the planner uses what is there and says what it is missing. First-party connectors register through the same public method a third party would call, because a privileged internal path is how an extensible interface quietly stops being the one anyone uses.
A planner proposes.A buyer commits.
Everyone at the company gets an account with the rights their job needs. The one boundary that is not negotiable is between producing an order and committing it to a supplier, and it is enforced in a single table that every interface goes through, so the web client, the desktop app and an agent cannot disagree about it.
| capability | owner | buyer | planner | viewer |
|---|---|---|---|---|
| Run a plan run_plan | allowed | allowed | allowed | not allowed |
| Read a plan get_plan | allowed | allowed | allowed | allowed |
| Change a line decide_line | allowed | allowed | allowed | not allowed |
| Approve the rest approve_plan | allowed | allowed | allowed | not allowed |
| Send to suppliers send_orders | allowed | allowed | not allowed | not allowed |
owner Everything, plus connectors and people.
buyer Plans, adjusts, approves, and sends.
planner Plans, adjusts, approves. Cannot send.
viewer Reads.
Disabling an account ends its live sessions rather than waiting for them to expire. An owner cannot demote or disable themselves. A plan belongs to exactly one organisation, and another tenant's plan id returns the same not-found a genuinely missing one does, so an id cannot be probed to learn that someone else has one.
One product, three ways in.
The web app is the product. The desktop shell and the agent interface are the same API and the same bundle reached differently, which is why a feature never has to be built twice and why they cannot disagree about who is allowed to do what.
A browser
The whole product. Nothing to install, nothing to keep updated.
A buyer works thousands of lines, so the review screen is dense and keyboard-driven: j and k to move, a to approve, e to edit, u to zero, / to filter. Twenty-five rows on a laptop screen, because a planner that shows twelve has made the morning longer.
A desktop app
The same build in a native window, for one reason.
Your ERP export lands in a folder. In a browser that is an upload every morning; here it is a path the connector reads directly. Tauri rather than Electron: 2.7 MB against roughly ninety, using the platform's own webview. There is no second codebase: the moment a shell has its own screens they drift and every feature gets built twice.
An agent
The same operations, over MCP, with the same permissions.
Plan, read, adjust, approve, send: the identical seven capabilities the web client uses, because an agent that can do less than a person is a demo and one that can do more is a liability. Authorisation is decided in one place, so an agent inherits the role of whoever it acts for, including the rule that a planner cannot send.
There is deliberately no download button here yet. The macOS build is finished and runs, but it is not signed with a Developer ID, and an unsigned app tells the person who opens it that it “is damaged and can’t be opened”, which is Gatekeeper being careful, and indistinguishable from malware to everyone else. We would rather send a pilot the build directly and put the link up once it is signed and notarised than have your first experience of us be that dialog.
What this does not do.
You would find all of these inside ten minutes. Finding them after we implied otherwise would cost more than putting them here.
We have not run this on a real range
Every number on this page is synthetic. The engine, the backtest and the counterfactual are real and shipping; the demand is not a customer's. Until a wholesaler sends a history file, that is the honest description and it is the reason for the ask below.
Substitution is not modelled
When a customer takes the 500 ml because the 330 is out, the 500 looks like demand and the 330 looks like indifference. Flagging stockouts stops us learning the wrong thing about the 330; it does not stop us learning the wrong thing about the 500. This is unsolved here and unsolved in the systems you already run.
Lead times are a default until you tell us otherwise
The single most sensitive input in the system, and the one most likely to be wrong in an ERP export. Per-supplier, per-item and variable lead times all matter more than the choice of forecasting method, and every proposal says which of them it had to assume.
Promotions are excluded, not predicted
A promotional day is marked and kept out of the baseline so it does not contaminate the normal rate. Forecasting the uplift of a promotion that has not happened yet is a different problem and we are not claiming it.
And what every number here assumes
- Fill rate is simulated, not observed: a sales history contains what was sold, never what was wanted and unavailable.
- Both policies are run against the identical demand estimate, so the comparison between them is like for like even where the level is not exact.
- No stockout days were supplied, so every zero is treated as genuine absence of demand. If the partner can send on-hand history, this assumption goes away and the estimate rises.
Send us eighteen monthsof sales history.
We will send back the frontier for your range: the fill rate you are currently buying, what it is costing you in stock, and what the same availability would cost planned properly. On your numbers, with the counterfactual stated. If it does not beat what you are doing, that is a useful answer and it cost you one export.
Sales history
requiredDate, item, location, quantity. Eighteen months if you have it, twelve is workable. One row per line per day is ideal; anything an ERP can export is fine.
Item catalogue
requiredItem, description, supplier, cost, pack size. Without cost we can still plan; we just cannot rank by what the range is worth.
Current stock
requiredOn hand and on order per item per location, as of a date. One snapshot is enough.
Stock history
optional, and worth the mostOn-hand by day, or any record of which days an item was out. This is what turns a zero from ambiguous into known, and it is the difference between an estimate and a floor.
Supplier terms
optionalLead times, order days, minimums, pack constraints. Defaults are used where these are missing and every proposal says which ones it assumed.
Vestra AI ehf., Reykjavík. Your file is used to produce your result and nothing else: no aggregate view, no benchmark, no other customer sees it. Hosted in the EU. Ask for it back or ask us to delete it at any point and that is the end of it. The method, including where it is weak, is described above rather than kept behind a call.