Build1 publisher3 min readPublished
A margin rule overrides the $329 price floor once unit cost clears $293.75
A dev.to walkthrough builds a console pricer from six plain rules, a two-hour cooldown and a 30-minute freshness window, and the floor it clamps to is computed from unit cost, so the published $329 rarely binds.
The Engineer · Build desk
What happened
- A dev.to walkthrough starts from the naive rule myPrice = competitorPrice - 1 and names its failure modes: a rival's loss-leading flash sale, a parser reading the wrong variant, and stock nearly gone.
- Its console example sets six rules: up 12% above 50 units in 24 hours, down 7% below 15, a $329 floor, a $479 ceiling, competitor prices ignored past 30 minutes, and one change every two hours.
- In the TypeScript version the effective floor is the larger of $329 and unit cost times 1.12, so the floor is computed from cost at every evaluation.
- The code adds stock thresholds the rule list omits: an increase requires more than 20 units in stock and a decrease requires more than 50.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A crawler polling less often than every 30 minutes leaves the competitor branch unreachable at most evaluation times, so the scrape schedule decides whether rivals influence price at all.
- decision Whoever moves unit cost moves the floor. Past $293.75 the $329 constant is inert, so a supplier increase quietly raises the minimum sale price without anyone editing the pricing config.
- capability A blocked run is still written out as a decision naming the guardrail that stopped it. That makes the question of why a price did not move answerable from a table.
- cost The two-hour cooldown buys stability at a fixed price: twelve moves a day per SKU, and a rival's flash sale can go unmatched for almost two hours by design.
Read `decidePrice` in the order it executes. The first thing it does is look at `lastPriceChangeAt`, and if the last change was less than 120 minutes ago it returns the current price with reason `no_change` and `blocked: "price_changed_too_recently"` [7]. Nothing downstream runs.
Then the floor is computed. `marginFloor` is `unitCost * (1 + MIN_MARGIN)` with `MIN_MARGIN` at 0.12, and `effectiveFloor` is the larger of that and the 329 constant [8][6]. So $329 binds only while unit cost sits at or below $293.75, which is 329 divided by 1.12 [17]. Above that cost the minimum tracks procurement, and `marginFloor` is the number that sets it. The file also uses 1.12 as a price multiplier in the demand branch [9]; that multiplier and the 0.12 margin are unrelated quantities that happen to share a digit [21].
The rule list and the code disagree about stock. The list says increase 12% above 50 units sold in 24 hours and cut 7% below 15 [4]. The code gates the increase on `stock > 20` and the cut on `stock > 50` [9]. With 20 units left and 60 sold, the price holds [20], and the post's own opening argument is that nearly empty inventory is when you should be raising price instead of chasing a competitor down [2].
The competitor adjustment runs after the demand branch and can only push the candidate lower. It needs an observation within 30 minutes and a competitor price below the candidate, then sets the candidate to `competitorPrice - 1` with reason `fresh_competitor_undercut` [10]. The last transformation is `clamp(candidate, effectiveFloor, PRICE_CEILING)` [11]. Because the clamp runs last, no rule can breach the floor.
Two constants set the system's clock. The 120-minute interval caps a SKU at 12 changes a day [18]. The 30-minute window means competitor data is usable only when a scrape landed in the previous half hour. The crawler needs 48 runs a day, 30 minutes into 24 hours, before that branch can fire at an arbitrary evaluation time [19]. Of those two rules the post says: "Without them, prices can oscillate all day or react to bad data." [5]
The audit trail is two tables. `price_observations` carries source, price, `observed_at` and a metadata jsonb; `pricing_decisions` carries old price, new price, reason and the inputs jsonb [12]. The post's stated reason for the split is that "Amazon is cheaper" and "our scraper last saw Amazon cheaper 4 hours ago" are different facts [13]. Blocked requests that are not modelled as failures look like missing products [14], and the post recommends a tool called Wire for collecting competitor prices as inspectable observations [15].
The constants belong to one console. For 12% up at 50 units and 7% down at 15 to transfer, your demand would have to respond on a two-hour granularity at a comparable elasticity. Your own static floor would have to sit above cost plus margin to matter at all [4][17]. The ordering is what carries over: inputs, then rules, then guardrails, then the record of why [3].
What to watch
- Whether a follow-up reports what the guardrails actually blocked in production: counts of price_changed_too_recently against floor clamps would show which constant fires most.
- Any measured elasticity behind the 12% and 7% steps, which the post presents as example values for one console.
- Whether the rule list is reconciled with the stock gates in the code, since the two disagree on what happens when inventory runs low.