A skincare seller on a major e-commerce marketplace, with a catalog where two thirds of the items are sets and multipacks. Four tools pull the price in different directions: do not run out of stock, follow the median up, do not sell below the margin floor, do not lose to rivals. All four run through one engine, and every recommendation can explain itself.
The client changed prices manually in the seller account. The decision was made on the seller price, while the customer sees a different one: the marketplace adds a discount of its own. Comparison against rivals therefore ran on numbers that do not exist on the shelf.
The seller price and the shelf price differ by almost half. While the interface showed only the seller price, a manager kept the conversion in their head and got it wrong, and any comparison with a rival was meaningless.
Three days of stock and two weeks of stock need different reaction speeds. A single "raise by N percent" rule is not enough for both: in the first case it is late, in the second it gets in the way.
A read-only column and an edit field elsewhere on the screen. A person picked between them at random, and the two drifted apart silently.
Two thirds of the catalog are bundles. Cost is tracked per whole item and never split by contents: that is how the client counts money, and the system has to count the same way.
The syncs collect the catalog, orders, finance and advertising. The finance module turns transactions into cost per unit. The pricing engine computes the recommendation. The repricer runs the same engine in read mode and lays the result out across four tabs: duplicating the logic on the screen is not allowed, otherwise there would be two prices and two answers to one question.
| Tab | What it shows | What it configures |
|---|---|---|
| Out of Stock | stock, order speed, how many days it lasts, the step of the raise ladder | the target in days of stock and the step: shared for the account, own per item |
| Median price | the 30 day median, the growth in percent, the price row as a chart, the raise rule | minimum and maximum price, two comparison periods, the raise step |
| Margin control | cost edited right in the cell, cost per unit, margin, advertising, ad share, promotions at a loss | minimum margin, whether advertising counts, the window of actual costs |
| Rivals | rival prices after the marketplace discount, how much dearer we are, when it was checked | how much cheaper to be, whether to chase the price, how many rivals to take |
The account value is the default, the item value wins. An empty cell shows the inherited account value in grey, a filled one shows its own. Clearing a cell means going back to the shared value: the server receives empty, not zero. Empty and zero are different things here and must not be confused: a zero in the raise step would mean "never raise".
It writes the value into the account settings and clears per-item values for that field. Otherwise the button would skip exactly the items somebody had tuned by hand, and would look broken.
The list of rungs is read from the sharpest shortage to the mildest. One step for every case behaved the same at six days of stock and at one day, although the second case needs a different reaction speed.
When a minimum price is not set by hand, the floor comes from the required margin. That needs the cost per unit, and here two sources exist which lie in opposite directions.
The tariff knows nothing about oversized parcels and distant warehouses, so it understates the cost. The actual over a lucky window understates it too: the expensive shipments did not fall into it. A floor set too low sells at a loss, a floor set too high merely leaves some orders on the table, so the larger number wins.
Under the number it says either max(actual over the window; tariff) or price-list tariff. The second means the item had no revenue in the window, and that number cannot be leaned on.
The tariff does not contain them at all, so they are not an alternative to it. Alternatives are combined with max, additions with a plus: different things are counted differently.
Not roubles divided by pieces. Money comes from transactions while the piece counter comes from postings, and they disagree: a day can have revenue with a zero counter. Dividing by such a counter made logistics cost more than the product.
The order in which they apply is pinned down in one place instead of being scattered across the tools. Otherwise two layers start moving the price in circles and there is nothing left to explain the result with.
Running out of stock costs more than losing on price. That is why Out of Stock sits after the rival and may override the chase. This case is always written into the disputes: the row gets a "dispute" badge with an explanation instead of a number that changed silently.
A night strategy and a promotion raise lift the ceiling above the base price: that is their whole point. The minimum price is moved by nothing: it is checked last and beats every add-on.
Every row shows which tool turned out to be decisive. Without that column a price table becomes an oracle: there is a number, and nobody to ask.
Under every price, current and suggested alike, a second line carries the shelf price after the marketplace discount. A manager compares against rivals by the shelf price, not by the seller price.
As a pair, and here is why. A single shelf price reads as "this is what the shelf shows right now". The client opened a product card, saw one number on the marketplace and could not match it with the one on the screen. There was no error: the number on the screen was the shelf price of the recommendation, while today's seller price gives a different shelf price. Since then the two always stand together.
The shelf price is computed by the same pipeline as the recommendation. A conversion of its own on the screen would mean different discounts in different places: the last measurement against the period average.
If the marketplace discount could not be measured, a dash stands there instead of a price, not the seller price. A substituted default would show a number nobody can compare with rivals.
The marketplace storefront is closed by a bot challenge even with a browser fingerprint. So the discount is computed from the prices of actual postings: the accuracy is about half a percent, and it is stated honestly.
The spec was written from the marketplace documentation. The live account answered differently, and every such answer became a line in a report with a sample.
| What the documentation promised | What actually happened |
|---|---|
| The funnel returns impressions, clicks, cart and conversions. | It returns only revenue and ordered units, the rest fails with a deprecated metrics error. Those columns are dashes with the reason, not zeroes. |
| The brand sits in an attribute with a fixed number. | The number depends on the category and is resolved through the attribute reference. |
| The price method returns the marketplace item id. | It does not: only the product id and the seller code. The id is filled in from the catalog, otherwise the table looks populated while the join is empty. |
| The storefront can be read to measure the discount. | The storefront is closed by a bot challenge even from a local address. The discount is computed from actual postings. |
| The catalog is made of single products. | Two thirds of it are sets and multipacks. Cost is tracked per whole item. |
| Several reports can be active at once. | Only one report can be active, so its id is stored and after a restart the report is downloaded rather than ordered again. |
| Discount limits can be any value. | Only fixed values from a list are accepted, the rest are rejected. |
| A posting contains unique item codes. | The same code can appear twice, and the whole batch was rejected. Duplicates now collapse before the write and the quantities are summed. |
| Before | After |
|---|---|
| Prices were changed by hand, on the seller price. | A recommendation with a pair of prices: the seller price and the shelf price, for the current one and the suggested one alike. |
| Rules conflicted and moved the price in circles. | The order of application is pinned down in one place, disputes are recorded and shown as a badge with an explanation. |
| The minimum price was set by eye. | The floor comes from the required margin on the conservative max(actual; tariff) principle, with the source visible. |
| Cost appeared in the table twice. | One cell, and the same cell is editable, and the edit goes to the cost module with its own history and author. |
| Rivals were picked automatically. | The list is filled in by hand: automatic picking returned neighbouring category products, not rivals. Records are muted rather than deleted, so a rival's price history survives. |
| It was unclear why the price was what it was. | A "decided by" column and a per-item message feed: what the engine did with the price and why. |
Test mode by the client's decision. The system writes nothing back to the marketplace: the result is a recommendation on the product card, an alert and a message in the feed. There is not a single write method in the marketplace clients. That is how trust in the engine is earned before it is given the wheel.
Business analyst and systems builder. I start with the business process and the data contract, then automate. Here the pricing decision stopped being a guess and learned to explain itself in one line.