{
  "name": "Successive discounts compounded: what each reduction leaves, and the single rate a whole chain works out at",
  "page": "https://otherworldlabs.com/stacked-discounts-and-the-single-rate/",
  "endpoint": "https://otherworldlabs.com/stacked-discounts-and-the-single-rate/api.json",
  "question": "For a list price and a sequence of reductions applied one after another, what does each reduction leave, what does it take off, what is the net price at the end, and what single rate is the whole sequence equal to? And the other way round: what list price arrives at a net price I type under that same sequence, and what single rate sits between a list price and a net price I both type?",
  "answers": "The price each reduction leaves and the amount it takes off, the single rate the chain works out at after each reduction and at the end, the net price, the whole amount taken off, the rates added together beside the rate the chain works out at, the list price that arrives at a typed net under the typed chain, the rate from a typed list price to a typed net, the extended total at a typed quantity, and the net price moved onto a typed rounding increment.",
  "version": "2026-10-25",
  "licence": "Figures and formulas free to use. Attribution to otherworldlabs.com appreciated and not required.",
  "method": "A reduction applied to a price leaves that price multiplied by what the reduction does not take: one less the rate over a hundred.",
  "currency": "None is named. Every money figure is in whatever currency the caller's own list price is in, and the page prints a dollar sign as a display convention only. Nothing here converts a currency, holds an exchange rate, or asks which currency a figure is in. A caller passing euros gets euros back, with a dollar sign in front of them on the page and no sign at all in this endpoint's figures.",
  "supplies_no_figure": "Every input is the caller's. This engine holds no list price, no rate, no target net, no quantity and no rounding increment of its own, and it has no table of typical, standard, industry or seasonal rates to fall back on. A reduction is read off the caller's quote, price list or invoice or it is not read at all. There is no default for any field: a blank list price withholds every reading, a blank target net withholds the two readings that start from a net, a blank quantity withholds the extended total, and a blank rounding increment means no increment is applied.",
  "no_pricing_advice": "This engine gives no pricing advice of any kind. It does not recommend a discount, does not say what one should be offered, holds no typical, standard, industry or seasonal figure, says nothing about what a reduction does to demand, to margin or to profit, and offers no view on when to discount or by how much. It works out no cost, no margin, no markup and no profit, and there is no input for a cost anywhere in it. It says nothing about whether a price is fair, competitive, legal, advertisable or compliant, and nothing about reference prices, was-prices, sale-pricing rules or advertising law. It supplies no fee, platform, marketplace, card or payment-processing figure.",
  "no_tax": "This engine computes no tax of any kind. There is no sales tax, VAT, GST, income tax, withholding or deduction in it, no rate for any of them, and nothing here says what is taxable, at what rate, or in what place. A price that needs a tax line needs a figure this engine does not hold, and a caller applying a tax rate to one of these outputs is doing arithmetic this endpoint has not done and does not describe.",
  "rounding": {
    "inputs": "Every numeric input is read at its own field's fineness and then held inside that field's declared range: the list price, the target net, every rate and the rounding increment to two decimals by round2 below, and the quantity to a whole figure by Math.round, which sends a half toward positive infinity. A typed 12.004 in a rate field is read as 12.00; a typed 1200000 in the list-price field, whose range ends at 1000000, is read as 1000000; a typed 250 in a rate field, whose range ends at 200, is read as 200. NO NOTICE IS PRINTED FOR EITHER, and that silence is stated here rather than left to be found: every numeric field on the page carries its own min, max and step, so a browser holds a reader's typed figure inside them and the reader never meets the clamp, while a caller of this endpoint can pass anything and will get the read-and-clamped answer with nothing said about it. A caller that works the formulas below from the figures as passed rather than from the figures as read will part from this engine on any input finer than its field's step or outside its field's range.",
    "list_price": "ONE PLACE THE CLAMP IS NOT A CLAMP, named here rather than found later: a list price of nought, or a minus list price, or anything that rounds to nought at two decimals, is read as NO LIST PRICE AT ALL and not as a list price of nought. The page's own field starts at 0.01 so a reader cannot reach it; a caller of this endpoint can, and gets the waiting state - incomplete true, and every money and rate output null - because a reduction has nothing to work on and a net price of nought would be an answer this engine has no grounds for.",
    "money": "two decimals for every money figure, by round2(x) = Math.round(x * 100) / 100 on the double, which sends a half toward positive infinity rather than away from zero. At the positive half: 0.125 comes out as 0.13. AT THE NEGATIVE HALF: -0.125 comes out as -0.12 and NOT as -0.13, and -0.005 comes out as -0.00 and not as -0.01, because Math.round(-12.5) is -12. That matters here rather than in general, because three outputs of this engine are legitimately negative - a price in the chain when a rate past a hundred takes off the whole price and keeps going, the single rate the chain works out at when the chain puts the price up, and the rate from a typed list price to a typed net when the net is the larger - so a reimplementation that rounds half away from zero will part from this one by a cent on exactly those.",
    "rates": "two decimals, by the same round2 on the double and so with the same behaviour at a half. A rate is a percentage, printed as a figure out of a hundred and never as a fraction.",
    "the_chain": "THE CHAIN IS COMPOUNDED FROM THE LIST PRICE AND THE RATES AND NEVER FROM A FIGURE ALREADY ROUNDED. The price after reduction i is round2 of the list price multiplied, in order, by each of the first i rates' own remainders in percentage form, and then divided by a hundred i times - the same sequence of operations in the same order, so a reimplementation doing it in another order may part from this one in the last bit of a double. It is NOT the printed price after reduction i-1 multiplied again: that would work a figure from a rounded one, and over six reductions it drifts.",
    "what_a_reduction_takes_off": "the amount reduction i takes off is round2 of the PRINTED price before it less the PRINTED price after it, which is why the column of amounts taken off adds exactly to the whole amount taken off, and the whole amount taken off is round2 of the list price less the net price. This is a deliberate choice between two defensible definitions: the other one, the unrounded drop, would not foot.",
    "the_single_rate": "the single rate a chain works out at, and the running rate after each reduction, are read off the printed price and the printed list price: round2(100 - price x 100 / list). They are therefore the compounded chain's own rate and are NEVER the typed rates added together. The sum of the typed rates is returned separately, as sumOfTypedRates, and exists only so that a caller can print the figure this engine exists to contradict. ONE CONSEQUENCE OF READING IT OFF THE PRINTED PRICE, named here rather than left to be found, because it is the one that caught a reimplementation of this endpoint: a chain of ONE is not quite the rate typed at it once the list price is small. The half-cent the price is kept to is worth 0.5 / list points of rate, so 19.99 less 12.50% returns 12.51, 0.05 less 25.00% returns 20.00 and 0.01 less 50.00% returns 0.00. Measured over all 30,001 rates the form offers, at ten list prices: 0 disagreements at a list price of 100.00 or more, and 99.99 is the largest list price at which any offered rate parts from the typed one. A reimplementation that reads the single rate off an UNROUNDED price will agree with this engine from 100.00 up and part from it below.",
    "the_increment": "a rounding increment is the caller's own and this engine holds none. Where one is passed above nought, the net price is moved onto the nearest multiple of it, with a half going toward positive infinity, and the result is returned as netAtIncrement - a figure of its own, beside netPrice, never in place of it. No other output is moved by it: the chain, the single rate, the inverse readings and the extended total are all worked from netPrice."
  },
  "inputs": [
    {
      "name": "listPrice",
      "type": "number",
      "unit": "money, in the caller's own currency",
      "required": true,
      "range": "0.01 to 1000000.00",
      "note": "The figure the reductions come off. Nought, a minus figure, a blank or anything that rounds to nought at two decimals is read as no list price at all and withholds every reading."
    },
    {
      "name": "stepCount",
      "type": "integer",
      "unit": "reductions",
      "required": false,
      "range": "0 to 6",
      "note": "How many of the steps array are read, in order. Entries past it are ignored. Blank falls back to how many leading entries carry a rate, which is what an array passed with no count means by it. Nought is a real answer: an empty chain, which returns the list price as the net and no single rate."
    },
    {
      "name": "steps[].rate",
      "type": "number",
      "unit": "per cent",
      "required": false,
      "range": "-100.00 to 200.00",
      "note": "The rate at that point in the sequence, as the quote writes it. The range runs past a hundred and below nought on purpose: a rate past a hundred takes off the whole price and keeps going, and a minus rate is an increase, applied with its sign kept. A blank rate inside the count is a step that takes nothing off, and it is not counted among stepsCounted."
    },
    {
      "name": "targetNet",
      "type": "number",
      "unit": "money, in the caller's own currency",
      "required": false,
      "range": "0 to 1000000.00, or blank",
      "note": "The net price being checked against. Blank leaves listForTarget and achievedRate null. Nought is a real answer and is not blank."
    },
    {
      "name": "quantity",
      "type": "integer",
      "unit": "units",
      "required": false,
      "range": "0 to 100000, or blank",
      "note": "How many the net price is extended across. Blank leaves extendedTotal null. Nought is a real answer and extends the net price to nothing."
    },
    {
      "name": "roundTo",
      "type": "number",
      "unit": "money, in the caller's own currency",
      "required": false,
      "range": "0 to 100.00",
      "note": "The caller's own rounding increment. Nought, which is the default, means none is applied and netAtIncrement comes back null. This engine holds no increment of its own and proposes none."
    }
  ],
  "outputs": [
    {
      "name": "listPriceRead",
      "type": "number",
      "unit": "money",
      "note": "The list price as read and held inside its range, or null when there is none. Every other money figure is worked from this one."
    },
    {
      "name": "stepsCounted",
      "type": "integer",
      "unit": "reductions",
      "note": "How many of the read steps carry a rate. A blank rate inside the count is not one of them."
    },
    {
      "name": "netPrice",
      "type": "number",
      "unit": "money",
      "note": "What is left at the end of the chain. With no reduction counted it is the list price itself. Null while there is no list price."
    },
    {
      "name": "equivalentRate",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the whole chain works out at, read off the net price and the list price. Null when no reduction is counted, because a chain with nothing in it has no rate. It can come out below nought when the chain puts the price up."
    },
    {
      "name": "totalRemoved",
      "type": "number",
      "unit": "money",
      "note": "The list price less the net price, which is also the sum of the amounts the reductions took off."
    },
    {
      "name": "sumOfTypedRates",
      "type": "number",
      "unit": "per cent",
      "note": "The counted rates added together. This is NOT the rate the chain works out at and is returned only so that a caller can print the figure the compounding contradicts. Null when no reduction is counted."
    },
    {
      "name": "listForTarget",
      "type": "number",
      "unit": "money",
      "note": "The list price that arrives at targetNet under the counted chain. Null for three different reasons, and the third is the opposite of the second: (1) no target is passed, or no list price is typed; (2) no list price in the range 0.01 to 1000000.00 arrives at the target, in which case targetUnreachable is true; (3) a counted rate is exactly a hundred AND the target is nought, where the chain leaves nothing of any list price so EVERY list price arrives at the target and there is no single one to name - in that state listForTarget is null and targetUnreachable is FALSE. The page says which of the three it is in words beside the reading and invents no figure for any of them."
    },
    {
      "name": "targetUnreachable",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when a target net is passed, a list price is typed, and no list price the page offers arrives at that target under the counted chain. The four states it is true in, each with the reason the page prints for it: a counted rate of exactly a hundred with a target above nought, which leaves nothing from every list price; a needed list price that is not a figure at all; one that is nought or a minus figure, which is not a price - a chain that puts the price up needs a minus list price to reach a net the list has already passed; and one past the ceiling this page offers. FALSE in three states that are NOT reachability, and this is where a caller reading the sentence above and stopping would part from the engine: when no target is passed; while incomplete holds, because with no list price nothing has been worked out to be out of reach and the waiting answer for a boolean is false, not null; and when a counted rate is exactly a hundred and the target IS nought, where every list price arrives at it rather than none."
    },
    {
      "name": "achievedRate",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate from the typed list price to the typed net: round2(100 - targetNet x 100 / listPrice). Null when either is missing. Below nought when the typed net is the larger of the two."
    },
    {
      "name": "extendedTotal",
      "type": "number",
      "unit": "money",
      "note": "The net price times the quantity. Null when no quantity is passed."
    },
    {
      "name": "netAtIncrement",
      "type": "number",
      "unit": "money",
      "note": "The net price moved onto the caller's own rounding increment. Null when no increment above nought is passed. It never replaces netPrice and no other output is worked from it."
    },
    {
      "name": "roundingApplied",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when an increment above nought was passed and a net price exists to move, which is the one condition under which netAtIncrement is a figure."
    },
    {
      "name": "incomplete",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when there is no usable list price. Every money and rate output is null while it holds, and the page says so at the top rather than printing a nought."
    },
    {
      "name": "emptyChain",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when a list price is read and no reduction is counted: the net price is the list price and equivalentRate is null."
    },
    {
      "name": "anyRateBelowZero",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when a counted rate is below nought, which is an increase applied with its sign kept."
    },
    {
      "name": "anyPriceBelowZero",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when a price in the chain comes out below nought, which a rate past a hundred does."
    },
    {
      "name": "netAboveList",
      "type": "boolean",
      "unit": "true or false",
      "note": "True when achievedRate is below nought, which is to say the typed net exceeds the typed list price."
    },
    {
      "name": "priceAfter1",
      "type": "number",
      "unit": "money",
      "note": "The price reduction 1 leaves: the list price multiplied by what the first 1 reduction leaves of it, kept to the cent. Null when fewer than 1 reductions are counted."
    },
    {
      "name": "priceAfter2",
      "type": "number",
      "unit": "money",
      "note": "The price reduction 2 leaves: the list price multiplied by what the first 2 reductions leave of it, kept to the cent. Null when fewer than 2 reductions are counted."
    },
    {
      "name": "priceAfter3",
      "type": "number",
      "unit": "money",
      "note": "The price reduction 3 leaves: the list price multiplied by what the first 3 reductions leave of it, kept to the cent. Null when fewer than 3 reductions are counted."
    },
    {
      "name": "priceAfter4",
      "type": "number",
      "unit": "money",
      "note": "The price reduction 4 leaves: the list price multiplied by what the first 4 reductions leave of it, kept to the cent. Null when fewer than 4 reductions are counted."
    },
    {
      "name": "priceAfter5",
      "type": "number",
      "unit": "money",
      "note": "The price reduction 5 leaves: the list price multiplied by what the first 5 reductions leave of it, kept to the cent. Null when fewer than 5 reductions are counted."
    },
    {
      "name": "priceAfter6",
      "type": "number",
      "unit": "money",
      "note": "The price reduction 6 leaves: the list price multiplied by what the first 6 reductions leave of it, kept to the cent. Null when fewer than 6 reductions are counted."
    },
    {
      "name": "removedAt1",
      "type": "number",
      "unit": "money",
      "note": "The amount reduction 1 took off: the printed price before it less the printed price after it, which is why the column of these adds exactly to totalRemoved. Null when fewer than 1 reductions are counted."
    },
    {
      "name": "removedAt2",
      "type": "number",
      "unit": "money",
      "note": "The amount reduction 2 took off: the printed price before it less the printed price after it, which is why the column of these adds exactly to totalRemoved. Null when fewer than 2 reductions are counted."
    },
    {
      "name": "removedAt3",
      "type": "number",
      "unit": "money",
      "note": "The amount reduction 3 took off: the printed price before it less the printed price after it, which is why the column of these adds exactly to totalRemoved. Null when fewer than 3 reductions are counted."
    },
    {
      "name": "removedAt4",
      "type": "number",
      "unit": "money",
      "note": "The amount reduction 4 took off: the printed price before it less the printed price after it, which is why the column of these adds exactly to totalRemoved. Null when fewer than 4 reductions are counted."
    },
    {
      "name": "removedAt5",
      "type": "number",
      "unit": "money",
      "note": "The amount reduction 5 took off: the printed price before it less the printed price after it, which is why the column of these adds exactly to totalRemoved. Null when fewer than 5 reductions are counted."
    },
    {
      "name": "removedAt6",
      "type": "number",
      "unit": "money",
      "note": "The amount reduction 6 took off: the printed price before it less the printed price after it, which is why the column of these adds exactly to totalRemoved. Null when fewer than 6 reductions are counted."
    },
    {
      "name": "rateAfter1",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the chain works out at by reduction 1, read off priceAfter1 and listPriceRead. Null when fewer than 1 reductions are counted. At the last counted reduction it equals equivalentRate."
    },
    {
      "name": "rateAfter2",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the chain works out at by reduction 2, read off priceAfter2 and listPriceRead. Null when fewer than 2 reductions are counted. At the last counted reduction it equals equivalentRate."
    },
    {
      "name": "rateAfter3",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the chain works out at by reduction 3, read off priceAfter3 and listPriceRead. Null when fewer than 3 reductions are counted. At the last counted reduction it equals equivalentRate."
    },
    {
      "name": "rateAfter4",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the chain works out at by reduction 4, read off priceAfter4 and listPriceRead. Null when fewer than 4 reductions are counted. At the last counted reduction it equals equivalentRate."
    },
    {
      "name": "rateAfter5",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the chain works out at by reduction 5, read off priceAfter5 and listPriceRead. Null when fewer than 5 reductions are counted. At the last counted reduction it equals equivalentRate."
    },
    {
      "name": "rateAfter6",
      "type": "number",
      "unit": "per cent",
      "note": "The single rate the chain works out at by reduction 6, read off priceAfter6 and listPriceRead. Null when fewer than 6 reductions are counted. At the last counted reduction it equals equivalentRate."
    }
  ],
  "formula": [
    "keep(i) = 100 - rate(i)",
    "priceAfter(i) = round2( ((listPrice x keep(1) x ... x keep(i)) / 100) / 100 ... i times )",
    "priceAfter(0) = listPriceRead",
    "removedAt(i) = round2( priceAfter(i-1) - priceAfter(i) )",
    "rateAfter(i) = round2( 100 - priceAfter(i) x 100 / listPriceRead )",
    "netPrice = priceAfter(stepsCounted), or listPriceRead when stepsCounted is 0",
    "equivalentRate = round2( 100 - netPrice x 100 / listPriceRead ), null when stepsCounted is 0",
    "totalRemoved = round2( listPriceRead - netPrice )",
    "sumOfTypedRates = round2( rate(1) + ... + rate(stepsCounted) ), null when stepsCounted is 0",
    "listForTarget = round2( ((targetNet x 100 ... stepsCounted times) / keep(1)) / ... / keep(stepsCounted) ), null when any keep(i) is 0 or the figure falls outside 0.01 to 1000000.00",
    "achievedRate = round2( 100 - targetNet x 100 / listPriceRead )",
    "targetUnreachable = false whenever incomplete is true or targetNet is blank, WHATEVER is passed: with no list price nothing has been worked out to be out of reach, and the waiting answer for a boolean is false rather than null. Otherwise, with a list price typed and a target passed: true when some keep(i) is 0 and targetNet is not 0; true when the listForTarget figure above is not finite, or is under 0.01, or is over 1000000.00; and FALSE when some keep(i) is 0 and targetNet IS 0, where every list price arrives at the target and listForTarget is null for that reason instead",
    "extendedTotal = round2( netPrice x quantity )",
    "netAtIncrement = round2( Math.round(netPrice / roundTo) x roundTo ), null when roundTo is 0"
  ],
  "identities": [
    "removedAt(1) + ... + removedAt(stepsCounted) = totalRemoved, exactly, because each amount is the difference of two printed prices and the sum telescopes to listPriceRead - netPrice",
    "listPriceRead - totalRemoved = netPrice",
    "rateAfter(stepsCounted) = equivalentRate",
    "equivalentRate = 0 when every counted rate is 0. And with exactly one reduction counted, equivalentRate = rate(1) at every list price of 100.00 or more - but NOT below that, which is the correction this line carries: the single rate is read off the price the reduction leaves AFTER that price is kept to the cent, so the half-cent in it is worth 0.5 / listPriceRead points of rate, which is under half a hundredth only from 100.00 up. 19.99 less 12.50% returns 12.51, 0.05 less 25.00% returns 20.00, and 0.01 less 50.00% returns 0.00. Measured over all 30,001 rates the form offers at ten list prices: 0 disagreements at 100.00 and above, and the largest list price at which any offered rate parts from rate(1) is 99.99",
    "equivalentRate is NOT sumOfTypedRates, which is the whole reason this engine returns both: for 20 and 10 it is 28.00 against 30.00. The true gap between them is the product of the counted rates divided by a hundred, and the two RETURNED figures coincide wherever that gap rounds away at two decimals - two counted rates of 0.01 each return 0.02 for both - so this line is a statement about the arithmetic and not a promise that the two figures always differ. The page prints the ordering it reads at the state it is in rather than asserting one",
    "achievedRate = equivalentRate whenever the typed net equals the net the chain leaves. The converse does NOT hold, for the same reason as the line above: both are kept to two decimals, so two different typed nets can read as one rate - on a list of 104.09 the chain 20 then 10 leaves 74.94, and a typed net of 74.95 returns 28.00 for both readings",
    "extendedTotal = netPrice x quantity to the cent",
    "incomplete true implies netPrice, equivalentRate, totalRemoved, sumOfTypedRates, listForTarget, achievedRate, extendedTotal and netAtIncrement are all null - and implies targetUnreachable FALSE, whatever target is passed. targetUnreachable is the one output that is not null while incomplete holds, because it is a boolean and the waiting answer for a boolean is false: with no list price nothing has been worked out to be out of reach. The same is true of roundingApplied, emptyChain, anyRateBelowZero, anyPriceBelowZero and netAboveList, which are all false while incomplete holds",
    "targetUnreachable true implies listForTarget is null, and listForTarget a figure implies targetUnreachable false"
  ],
  "example": {
    "why": "The chain this page exists for: a quote reading less twenty and ten on a list of a thousand, which everybody adds up to thirty and which works out at twenty-eight.",
    "inputs": {
      "listPrice": 1000,
      "stepCount": 2,
      "steps": [
        {
          "rate": 20
        },
        {
          "rate": 10
        }
      ],
      "targetNet": 650,
      "quantity": 12,
      "roundTo": 0
    },
    "outputs": {
      "listPriceRead": 1000,
      "stepsCounted": 2,
      "netPrice": 720,
      "equivalentRate": 28,
      "totalRemoved": 280,
      "sumOfTypedRates": 30,
      "listForTarget": 902.78,
      "targetUnreachable": false,
      "achievedRate": 35,
      "extendedTotal": 8640,
      "netAtIncrement": null,
      "roundingApplied": false,
      "incomplete": false,
      "emptyChain": false,
      "anyRateBelowZero": false,
      "anyPriceBelowZero": false,
      "netAboveList": false,
      "priceAfter1": 800,
      "removedAt1": 200,
      "rateAfter1": 20,
      "priceAfter2": 720,
      "removedAt2": 80,
      "rateAfter2": 28
    },
    "reading": "The first reduction takes 200.00 off 1000.00 and leaves 800.00, which is a chain of 20.00 so far. The second takes 10.00 of what is left, which is 80.00, and leaves 720.00. The whole chain has taken 280.00 off and works out at 28.00, not at the 30.00 the two rates add to. A net of 650.00 would need a list price of 902.78 under this same chain, the rate from 1000.00 to 650.00 is 35.00, and twelve of them at the net come to 8640.00."
  },
  "worked_increase": {
    "why": "Three of the five states this engine prints rather than smooths over, in one chain: a rate past a hundred, which takes off the whole price and keeps going; a minus rate, which is an increase applied with its sign kept; and a typed net above the typed list price, which gives a rate below nought. The target is out of reach because the chain ends up putting the price up, so no list price inside the range arrives at it.",
    "inputs": {
      "listPrice": 1000,
      "stepCount": 2,
      "steps": [
        {
          "rate": 120
        },
        {
          "rate": -20
        }
      ],
      "targetNet": 1400,
      "quantity": 5,
      "roundTo": 0.05
    },
    "outputs": {
      "listPriceRead": 1000,
      "stepsCounted": 2,
      "netPrice": -240,
      "equivalentRate": 124,
      "totalRemoved": 1240,
      "sumOfTypedRates": 100,
      "listForTarget": null,
      "targetUnreachable": true,
      "achievedRate": -40,
      "extendedTotal": -1200,
      "netAtIncrement": -240,
      "roundingApplied": true,
      "incomplete": false,
      "emptyChain": false,
      "anyRateBelowZero": true,
      "anyPriceBelowZero": true,
      "netAboveList": true,
      "priceAfter1": -200,
      "removedAt1": 1200,
      "rateAfter1": 120,
      "priceAfter2": -240,
      "removedAt2": 40,
      "rateAfter2": 124
    },
    "reading": "A rate of 120.00 leaves -20.00 of every hundred, so 1000.00 becomes -200.00 and 1200.00 has come off. The second rate is -20.00, an increase, which leaves 120.00 of every hundred: -200.00 becomes -240.00, and the drop between the two printed prices is 40.00. The chain has taken 1240.00 off in all and works out at 124.00, which is not the 100.00 the two rates add to. The typed net of 1400.00 is above the typed list of 1000.00, so the rate between them is -40.00, an increase. No list price arrives at 1400.00 under this chain, so listForTarget is null and targetUnreachable is true. The increment of 0.05 is in force and moves nothing, because -240.00 already sits on a multiple of it."
  },
  "worked_unreachable": {
    "why": "The fifth state: a rate of exactly a hundred leaves nothing from every list price, so no list price arrives at a net above nought, and the engine returns null with targetUnreachable true rather than inventing a figure. It also shows a reduction after the price has already reached nought, which takes nothing off.",
    "inputs": {
      "listPrice": 500,
      "stepCount": 2,
      "steps": [
        {
          "rate": 100
        },
        {
          "rate": 10
        }
      ],
      "targetNet": 250,
      "quantity": 3,
      "roundTo": 0
    },
    "outputs": {
      "listPriceRead": 500,
      "stepsCounted": 2,
      "netPrice": 0,
      "equivalentRate": 100,
      "totalRemoved": 500,
      "sumOfTypedRates": 110,
      "listForTarget": null,
      "targetUnreachable": true,
      "achievedRate": 50,
      "extendedTotal": 0,
      "netAtIncrement": null,
      "roundingApplied": false,
      "incomplete": false,
      "emptyChain": false,
      "anyRateBelowZero": false,
      "anyPriceBelowZero": false,
      "netAboveList": false,
      "priceAfter1": 0,
      "removedAt1": 500,
      "rateAfter1": 100,
      "priceAfter2": 0,
      "removedAt2": 0,
      "rateAfter2": 100
    },
    "reading": "A rate of 100.00 leaves nothing of 500.00, so 500.00 has come off and the price stands at 0. The second rate of 10.00 takes 10.00 of nothing, which is nothing, so the price stays at 0 and the chain still works out at 100.00, not at the 110.00 the two rates add to. The typed net of 250.00 cannot be reached by any list price under this chain, so listForTarget is null and targetUnreachable is true. The rate from 500.00 to 250.00 is 50.00, which is a reading about two typed figures and says nothing about the chain."
  },
  "not_covered": [
    "No cost, no margin, no markup and no profit. There is no input for a cost and no output derived from one. A caller wanting a margin is asking a different question of a different page.",
    "No tax of any kind, no rate for one, and nothing about what is taxable.",
    "No fee, platform, marketplace, card or payment-processing figure.",
    "No currency conversion and no exchange rate. The currency is whatever the caller's own figures are in and this engine never asks.",
    "No recommended, typical, standard, industry or seasonal rate, and no view on when to discount or by how much.",
    "No statement about whether a price is fair, competitive, legal, advertisable or compliant, and nothing about reference prices, was-prices, sale-pricing rules or advertising law.",
    "No rounding rule of its own. An increment is applied only where the caller passes one, to the net price only.",
    "No allocation of one reduction across several lines, and no order of application chosen for the caller: the order is the order the steps array is in, because which reduction applies first is a fact about the quote and not an arithmetic question.",
    "No clock. Nothing here changes between one day and the next, and no output is dated.",
    "No seventh reduction. A quote with more than six rates on it needs the chain split, and this engine says so by reading six and ignoring the rest rather than by failing."
  ],
  "no_advice": "This is arithmetic on figures the caller supplies. It is not pricing advice, not legal advice, not tax advice and not accounting advice, and nothing in it says what any price ought to be."
}
