{
 "endpoint": "https://otherworldlabs.com/grocery-list-footed-by-aisle-and-store/api.json",
 "page": "https://otherworldlabs.com/grocery-list-footed-by-aisle-and-store/",
 "what_it_answers": "For a list of items a reader types, with how many of each they need and the price they hold for one of them, what each line comes to, what each aisle label they typed comes to, what each stop label they typed comes to and how many rows stand at that stop, what the whole list comes to, and whether the aisle column and the stop column foot to one figure. One list of lines, grouped two independent ways, both groupings cross-footed.",
 "shape": "A static document, not a service. There is no request to make and nothing is posted anywhere: this file is the engine written out so that an agent re-doing the arithmetic gets the same answer the page does, and so that the workbook sold beside the page can be read against both. The page's own flat answer is the function apiOutputs inside <script id=\"core\">, and the names below are its keys.",
 "units": "A count of things for a quantity, a count of rows for a stop's rows, and United States dollars for money. No other unit: this engine computes no weight, no volume, no time and no percentage.",
 "the_page_performs_no_division": "There is no division anywhere in this engine. No share, no percentage, no ratio, no cost per unit, no price per pound or per ounce and no unit-price comparison is computed or published, and a figure of any of those kinds appearing on the page would be a fault rather than a feature. The page starts from lines and builds a total up out of them.",
 "the_page_supplies_no_figure": "Every figure in this arithmetic is the reader's own. The page supplies no item, no starter list, no aisle, no category and no price: nothing typical, average, regional, seasonal, current, fetched or cached. It names no shop, chain, market, brand or product and compares none of them. It makes no nutrition, calorie, allergen, dietary, portion, health, food-safety or shelf-life claim, no claim about what a household needs, and no claim about saving money.",
 "state": {
  "shape": "{ rowCount, rows: [ { item, qty, price, aisle, store }, ... ] }",
  "rows": "An array of up to thirty rows. Only the first rowCount of them are read; a row object the array does not carry is read as a row with nothing typed on it.",
  "rowCount_blank": "With rowCount blank or unreadable, the list is as long as the rows the state carries, up to thirty, and one row long when it carries none.",
  "blank_is_not_nothing": "A blank cell and a typed 0 are two different states. A blank quantity or a blank price is read as not typed, and the list waits; a typed 0 is a figure the reader typed, prints as the zero it is and is footed.",
  "every_field_is_a_string_or_a_number": "Every field may arrive as either. A number is used as it is; a string has $, commas and spaces taken out before it is read, so \"$1.95\" and 1.95 are one figure.",
  "the_label_fields": "aisle and store are free text. They are read with the spaces at each end trimmed and nothing else changed - the runs of space inside a label are kept - and they are cut to 24 characters. The item name is cut to 40 characters and its runs of space are collapsed to one, because it is a name and not a key.",
  "reading_at_a_limit": "A figure past the top of the range its own control offers is read AT the top: a quantity of 1200 is read as 999 and a price of 12000 as 9999.99. A figure that does not reach the bottom of its range is read as not typed at all, which is the state the list waits in rather than a figure the page mends: a quantity of -4 is read as no quantity."
 },
 "inputs": [
  {
   "name": "rowCount",
   "label": "How many rows are on this list",
   "type": "integer",
   "control": "rowCount",
   "offered": {
    "min": 1,
    "max": 30,
    "step": 1
   },
   "blank_means": "as many rows as the state carries, up to thirty, and one if it carries none",
   "note": "A select, one to thirty. The rows past the count keep what was typed in them and are not read."
  },
  {
   "name": "item",
   "label": "The item, in your own words",
   "type": "text",
   "control": "gItem0 to gItem29",
   "offered": {
    "maxlength": 40
   },
   "blank_means": "no item name; the row is still on the list if it carries a quantity, and the list prints it as the Nth row with no item typed",
   "note": "The page supplies no item and no starter list of any kind."
  },
  {
   "name": "qty",
   "label": "How many",
   "type": "number",
   "control": "gQty0 to gQty29",
   "offered": {
    "min": 0,
    "max": 999,
    "step": 1
   },
   "blank_means": "no quantity, which the list waits on",
   "note": "A typed fraction such as 2.5 is read and multiplied exactly as typed. The step is the control's, not a rounding: nothing is rounded on the way in."
  },
  {
   "name": "price",
   "label": "The price of one of them",
   "type": "money",
   "control": "gPrice0 to gPrice29",
   "offered": {
    "min": 0,
    "max": 9999.99,
    "step": 0.01
   },
   "blank_means": "no price, which the list waits on",
   "note": "The price of ONE of the things on that line, held by the reader. The page supplies no price of any kind."
  },
  {
   "name": "aisle",
   "label": "The aisle, in your own words",
   "type": "text",
   "control": "gAisle0 to gAisle29",
   "offered": {
    "maxlength": 24
   },
   "blank_means": "no aisle label; the row is footed into the named bucket No aisle named",
   "note": "The page supplies no aisle, category or grouping of its own."
  },
  {
   "name": "store",
   "label": "The stop, in your own words",
   "type": "text",
   "control": "gStore0 to gStore29",
   "offered": {
    "maxlength": 24
   },
   "blank_means": "no stop label; the row is footed into the named bucket No store named",
   "note": "The page names no shop, chain, market or brand anywhere."
  }
 ],
 "rounding": {
  "function": "rN(v, dp) = sign(v) * Math.round(Math.abs(v) * 10^dp) / 10^dp, on the double, with no epsilon and no decimal library.",
  "at_the_half": "Half away from zero in BOTH directions, because the absolute value is rounded and the sign is put back afterwards. At two decimals a value of -0.005 therefore comes out -0.01 and not -0.00: Math.round(-0.005 * 100) / 100 is -0 on the double. No figure this engine computes can be negative - a line is a quantity read from zero upwards multiplied by a price read from zero upwards - so the negative half-cent is a property of the function rather than a state the page can reach, and it is written down because this file has to say what the function does.",
  "what_is_rounded": "Nothing the engine computes. A line is its quantity multiplied by its price and is carried unrounded into its aisle subtotal, into its stop subtotal and into the list total; every figure in the outputs below is unrounded. rN is used where a figure is PRINTED and nowhere else: the page prints money to the cent and a quantity to two decimals where it is not whole.",
  "where_the_cent_is": "In the printing. A reader who re-adds the printed column of lines by hand can land a cent away from the printed total, because the printed lines are each rounded and the total is not. Section G of the page prints the unrounded figures the arithmetic carried, which is where that cent can be seen.",
  "the_two_column_feet": "aisleColumnFoot and storeColumnFoot are the same unrounded lines added in two different orders, so they are equal in arithmetic and can differ in the last bits of a double. Measured bound for the widest list the form offers - thirty rows, each at the top of both ranges - is a ten-thousandth of a dollar, a hundredth of the cent either of them is printed to, and the claim the page records on its cross-foot holds them to that ten-thousandth at every state its gates sweep.",
  "why_it_is_said_here": "Because a workbook that rounded each line to the cent and footed the rounded lines would disagree with this page by cents on a long list, and the kit sold beside the page foots the unrounded lines for that reason. check_kit compares the two at half a cent."
 },
 "formulas": {
  "rowNLine": "how many on row N multiplied by what one of them costs. Nothing is rounded. 0 where the row carries no quantity, no price, or neither an item nor a quantity.",
  "aisle_subtotal": "for each distinct aisle label typed, the lines of the rows carrying it, added in the order the rows were typed. A row with no aisle label is in the named bucket No aisle named, which is a subtotal like any other.",
  "store_subtotal": "for each distinct stop label typed, the lines of the rows carrying it, added in the order the rows were typed. A row with no stop label is in the named bucket No store named.",
  "store_rowCount": "a count of the rows carrying that stop label. A count of ROWS, never a sum of quantities, and never a figure divided by anything.",
  "listTotal": "every line on the list added, in the order the rows were typed.",
  "aisleColumnFoot": "every aisle subtotal added, in the order the aisles were first named.",
  "storeColumnFoot": "every stop subtotal added, in the order the stops were first named.",
  "crossFootDifference": "aisleColumnFoot minus storeColumnFoot.",
  "no_division": "There is no other formula. Nothing is divided by anything anywhere in this engine, so there is no share, no percentage, no ratio and no cost per unit to publish."
 },
 "grouping": {
  "the_key": "A label is grouped by the characters the reader typed, after the spaces at each end are trimmed and the case is folded. So \"Dairy\" and \"dairy \" are one aisle.",
  "what_is_not_folded": "Everything else. \"Dairy\" and \"Diary\" are two aisles, and so are \"corner shop\" and \"corner  shop\", because the runs of space inside a label are kept: a mistyped label stands as its own named row where a reader can see it rather than being merged into a label they did not type.",
  "the_name_printed": "The first spelling typed on the list. The group is keyed on the folded characters and named by the row that first reached it.",
  "the_two_buckets": "A label of spaces alone is read as no label. A row with no aisle label is footed into No aisle named and a row with no stop label into No store named, so every row on the list reaches both margins and the two columns foot to one figure. A row that is not on the list - no item and no quantity on it - is on neither margin."
 },
 "completeness": {
  "a_row_is_priced": "when it carries a quantity AND a price, each of them read as a figure rather than as not typed. A typed 0 is a figure.",
  "the_list_is_incomplete": "while no row is on the list, or any row on it carries no price, or any row on it carries no quantity. waiting is true in exactly that state.",
  "what_that_withholds": "every money figure the page prints: each line, every aisle subtotal, every stop subtotal, the list total, both column feet and the cross-foot. listTotal, aisleColumnFoot, storeColumnFoot, crossFootDifference, noAisleTotal and noStoreTotal are null while it holds, and the sentence saying which state the list is in prints in the glance, above where those figures would stand.",
  "what_it_does_not_withhold": "the list itself. The rows, the count of rows, the aisle labels and the stop labels with the rows standing at each one all print while the list waits, because the query this page answers asks for a list and the list is usable as a list before a price is typed at all.",
  "a_blank_row_does_not_wait": "A row the count asks for with NOTHING typed on it does not hold the list back: a blank line on a shopping list is a blank line and not a missing figure. It is on no margin and in no count, and the glance says how many such rows the list asks for.",
  "the_rowNLine_exception": "rowNLine is the row's own arithmetic and is published whenever that row carries both figures, whatever the rest of the list is doing. It is the one place where this endpoint answers something the page withholds, and it is here because an agent asking what a row comes to should get the row's answer; the page withholds the printed line because one line printed beside a withheld total is how a half-priced list comes to look confident."
 },
 "states": [
  {
   "state": "the empty list",
   "reached_when": "no row carries an item name or a quantity",
   "printed": "no figure of any kind; the glance says the list is empty",
   "outputs": "waiting true, every money figure null, rowsTyped 0"
  },
  {
   "state": "the list as a plain list, with no price typed",
   "reached_when": "rows are on the list and no row carries a price",
   "printed": "the rows, the row count, both margins with their rows counted, and no money figure anywhere",
   "outputs": "waiting true, rowsAwaitingPrice equal to rowsTyped"
  },
  {
   "state": "the part-priced list",
   "reached_when": "a row on the list carries a quantity and no price, or a price and no quantity",
   "printed": "no money figure at all, and the glance says how many rows are without a price and how many without a quantity",
   "outputs": "waiting true, rowsAwaitingPrice or rowsAwaitingQuantity above zero and below rowsTyped"
  },
  {
   "state": "the fully priced list with an aisle and a stop on every row",
   "reached_when": "every row on the list carries a quantity and a price, and both labels",
   "printed": "every figure: each line, every subtotal, the rows at each stop, the list total and the two column feet",
   "outputs": "waiting false, rowsWithNoAisle 0, rowsWithNoStore 0"
  },
  {
   "state": "the fully priced list with a label left blank",
   "reached_when": "a priced row carries no aisle label, or no stop label, or no label of either kind",
   "printed": "every figure, with that row in the named bucket on each margin it is without a label for",
   "outputs": "waiting false, rowsWithNoAisle or rowsWithNoStore above zero, and the two column feet still one figure"
  },
  {
   "state": "the fully priced list carrying a typed zero",
   "reached_when": "a row carries a typed zero quantity or a typed zero price",
   "printed": "that line as $0.00, footed into every subtotal it belongs to and counted as a row",
   "outputs": "waiting false, rowsWithTypedZero above zero"
  }
 ],
 "outputs": [
  {
   "name": "rowCount",
   "type": "integer",
   "of": "how many rows this list reads, 1 to 30. The count the control offers, read inside that range; with the count blank it is the number of row objects the state carries, up to thirty, and one if it carries none. This count is the ONE figure state.reading_at_a_limit's bottom-of-range sentence does not describe: a count past the top is read at the top, as that sentence says, and a count below the bottom - a typed 0 or a negative - is clamped UP to one row rather than read as not typed, so the page always reads at least one row. No reader can reach that state, because the control is a select of 1 to 30, and no generated state produces it; it is written down here because a module built from the general sentence alone would read a 0 count as a blank count and fall back to the rows the state carries."
  },
  {
   "name": "rowsTyped",
   "type": "integer",
   "of": "how many of those rows are ON the list: a row carrying an item name or a quantity. A row carrying a price alone is not an item and is not counted, because there is nothing on it to foot."
  },
  {
   "name": "rowsPriced",
   "type": "integer",
   "of": "how many rows on the list carry a quantity AND a price, so that a line can be worked for them."
  },
  {
   "name": "rowsAwaitingPrice",
   "type": "integer",
   "of": "how many rows on the list carry no typed price. A typed 0 is a price."
  },
  {
   "name": "rowsAwaitingQuantity",
   "type": "integer",
   "of": "how many rows on the list carry no typed quantity. A typed 0 is a quantity."
  },
  {
   "name": "rowsWithTypedZero",
   "type": "integer",
   "of": "how many rows on the list carry a typed zero quantity or a typed zero price. They are printed as the zero they are, footed, and counted."
  },
  {
   "name": "rowsWithNoAisle",
   "type": "integer",
   "of": "how many rows on the list carry no aisle label, after the spaces at each end are trimmed. Each of them is footed into the named bucket No aisle named."
  },
  {
   "name": "rowsWithNoStore",
   "type": "integer",
   "of": "how many rows on the list carry no stop label, after the spaces at each end are trimmed. Each of them is footed into the named bucket No store named."
  },
  {
   "name": "aisleGroupsNamed",
   "type": "integer",
   "of": "how many distinct aisle labels were typed, grouped by the characters typed after trimming and folding case. The named bucket is not one of them."
  },
  {
   "name": "storeGroupsNamed",
   "type": "integer",
   "of": "how many distinct stop labels were typed, grouped the same way. The named bucket is not one of them."
  },
  {
   "name": "noAisleTotal",
   "type": "number or null",
   "of": "the money on the rows with no aisle label: 0 where there are none, and null while the list is incomplete."
  },
  {
   "name": "noStoreTotal",
   "type": "number or null",
   "of": "the money on the rows with no stop label: 0 where there are none, and null while the list is incomplete."
  },
  {
   "name": "listTotal",
   "type": "number or null",
   "of": "the one list total in US dollars: every line on the list added, unrounded, in the order the rows were typed. Null while the list is incomplete."
  },
  {
   "name": "aisleColumnFoot",
   "type": "number or null",
   "of": "the aisle column's foot: every aisle subtotal added, in the order the aisles were first named, the named bucket included. Null while the list is incomplete."
  },
  {
   "name": "storeColumnFoot",
   "type": "number or null",
   "of": "the stop column's foot: every stop subtotal added, in the order the stops were first named, the named bucket included. Null while the list is incomplete."
  },
  {
   "name": "crossFootDifference",
   "type": "number or null",
   "of": "aisleColumnFoot minus storeColumnFoot. It is the two orders of one addition, so it is zero to within the last bits of a double and never a figure a cent can show. Null while the list is incomplete."
  },
  {
   "name": "waiting",
   "type": "boolean",
   "of": "true while the list is incomplete: no row is typed, or a typed row carries no price, or a typed row carries no quantity. While it is true the page prints no money figure anywhere and says in the glance which state the list is in."
  },
  {
   "name": "row1Line",
   "type": "number",
   "of": "the line on row 1: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row2Line",
   "type": "number",
   "of": "the line on row 2: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row3Line",
   "type": "number",
   "of": "the line on row 3: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row4Line",
   "type": "number",
   "of": "the line on row 4: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row5Line",
   "type": "number",
   "of": "the line on row 5: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row6Line",
   "type": "number",
   "of": "the line on row 6: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row7Line",
   "type": "number",
   "of": "the line on row 7: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row8Line",
   "type": "number",
   "of": "the line on row 8: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row9Line",
   "type": "number",
   "of": "the line on row 9: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row10Line",
   "type": "number",
   "of": "the line on row 10: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row11Line",
   "type": "number",
   "of": "the line on row 11: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row12Line",
   "type": "number",
   "of": "the line on row 12: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row13Line",
   "type": "number",
   "of": "the line on row 13: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row14Line",
   "type": "number",
   "of": "the line on row 14: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row15Line",
   "type": "number",
   "of": "the line on row 15: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row16Line",
   "type": "number",
   "of": "the line on row 16: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row17Line",
   "type": "number",
   "of": "the line on row 17: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row18Line",
   "type": "number",
   "of": "the line on row 18: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row19Line",
   "type": "number",
   "of": "the line on row 19: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row20Line",
   "type": "number",
   "of": "the line on row 20: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row21Line",
   "type": "number",
   "of": "the line on row 21: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row22Line",
   "type": "number",
   "of": "the line on row 22: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row23Line",
   "type": "number",
   "of": "the line on row 23: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row24Line",
   "type": "number",
   "of": "the line on row 24: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row25Line",
   "type": "number",
   "of": "the line on row 25: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row26Line",
   "type": "number",
   "of": "the line on row 26: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row27Line",
   "type": "number",
   "of": "the line on row 27: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row28Line",
   "type": "number",
   "of": "the line on row 28: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row29Line",
   "type": "number",
   "of": "the line on row 29: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  },
  {
   "name": "row30Line",
   "type": "number",
   "of": "the line on row 30: how many of the thing on it multiplied by what one of them costs, unrounded. 0 where that row carries no quantity or no price, or carries neither an item nor a quantity, or is past the row count. It is the row's own arithmetic and it does not wait on the rest of the list: the PAGE withholds the printed line while any typed row is without a figure, and listTotal is null in exactly that state, which is the difference between a row's own answer and the list's."
  }
 ],
 "limits": [
  "This is multiplication and addition on figures the reader typed, and nothing else. It is not advice of any kind and it recommends nothing: not an item, not a quantity, not an aisle, not a shop and not a price.",
  "Nothing here is nutritional, dietary or medical. There is no calorie, macro, protein, sugar, allergen, intolerance, portion or health figure in this engine, and no claim that any food is good, bad, healthy, unhealthy, fresh or suitable for anybody.",
  "Nothing here is about food safety. There is no storage, refrigeration, shelf-life, use-by, best-before or spoilage figure or claim.",
  "No price is supplied, fetched or cached, and nothing here says what anything costs, should cost or used to cost. No shop, chain, market, brand or product is named or compared, and there is no coupon, deal, loyalty, bulk or discount figure.",
  "No division is performed. There is no per-person share, no second payer, no cost per unit, no price per pound or per ounce, no unit-price comparison, no percentage and no ratio in this engine or in the workbook sold beside the page.",
  "No tax, delivery charge or service fee is added anywhere. The list total is the lines and nothing else.",
  "There is no budget rule, target, tolerance or verdict on a total here, and nothing in the page's words reads as approval or alarm about what a shop came to.",
  "Nothing is stored and nothing is sent. The page asks for no name of any kind, keeps nothing in the browser, and the figures are gone when the tab closes."
 ],
 "worked_example": {
  "why": "a list of six rows worked end to end: two rows in one aisle whose labels differ only by case and a trailing space, two stops, one row with no stop label, one row with no label of either kind, a fractional quantity and a typed zero price. Every figure below is this engine's own answer for the state above it.",
  "state": {
   "rowCount": 6,
   "rows": [
    {
     "item": "milk",
     "qty": "2",
     "price": "1.95",
     "aisle": "Dairy",
     "store": "Market"
    },
    {
     "item": "butter",
     "qty": "1",
     "price": "3.25",
     "aisle": "dairy ",
     "store": "market "
    },
    {
     "item": "mince",
     "qty": "2.5",
     "price": "4.40",
     "aisle": "chilled",
     "store": "Market"
    },
    {
     "item": "apples",
     "qty": "6",
     "price": "0.45",
     "aisle": "produce",
     "store": ""
    },
    {
     "item": "bags",
     "qty": "3",
     "price": "0",
     "aisle": "",
     "store": ""
    },
    {
     "item": "rice",
     "qty": "2",
     "price": "2.50",
     "aisle": "dry",
     "store": "corner shop"
    }
   ]
  },
  "outputs": {
   "rowCount": 6,
   "rowsTyped": 6,
   "rowsPriced": 6,
   "rowsAwaitingPrice": 0,
   "rowsAwaitingQuantity": 0,
   "rowsWithTypedZero": 1,
   "rowsWithNoAisle": 1,
   "rowsWithNoStore": 2,
   "aisleGroupsNamed": 4,
   "storeGroupsNamed": 2,
   "noAisleTotal": 0,
   "noStoreTotal": 2.7,
   "listTotal": 25.849999999999998,
   "aisleColumnFoot": 25.849999999999998,
   "storeColumnFoot": 25.849999999999998,
   "crossFootDifference": 0,
   "waiting": false,
   "row1Line": 3.9,
   "row2Line": 3.25,
   "row3Line": 11,
   "row4Line": 2.7,
   "row5Line": 0,
   "row6Line": 5,
   "row7Line": 0,
   "row8Line": 0,
   "row9Line": 0,
   "row10Line": 0,
   "row11Line": 0,
   "row12Line": 0,
   "row13Line": 0,
   "row14Line": 0,
   "row15Line": 0,
   "row16Line": 0,
   "row17Line": 0,
   "row18Line": 0,
   "row19Line": 0,
   "row20Line": 0,
   "row21Line": 0,
   "row22Line": 0,
   "row23Line": 0,
   "row24Line": 0,
   "row25Line": 0,
   "row26Line": 0,
   "row27Line": 0,
   "row28Line": 0,
   "row29Line": 0,
   "row30Line": 0
  },
  "read_it_back": "The two aisle labels Dairy and \"dairy \" are one aisle: milk at 2 x 1.95 and butter at 1 x 3.25 foot to 7.15 on it. The two stop labels Market and \"market \" are one stop. The row with no stop label is in No store named and the row with no label of either kind is in both buckets, so the aisle column and the stop column each hold all six rows and foot to the list total, and crossFootDifference is zero."
 },
 "second_worked_example": {
  "why": "the six states this engine must not smooth over, the state the page ships in, and the two readings at a limit, one state each",
  "empty": {
   "why": "the state the page ships in",
   "state": {
    "rowCount": 1,
    "rows": [
     {
      "item": "",
      "qty": "",
      "price": "",
      "aisle": "",
      "store": ""
     }
    ]
   },
   "outputs": {
    "rowCount": 1,
    "rowsTyped": 0,
    "rowsPriced": 0,
    "rowsAwaitingPrice": 0,
    "rowsAwaitingQuantity": 0,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 0,
    "rowsWithNoStore": 0,
    "aisleGroupsNamed": 0,
    "storeGroupsNamed": 0,
    "noAisleTotal": null,
    "noStoreTotal": null,
    "listTotal": null,
    "aisleColumnFoot": null,
    "storeColumnFoot": null,
    "crossFootDifference": null,
    "waiting": true,
    "row1Line": 0,
    "row2Line": 0,
    "row3Line": 0,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "plain_list": {
   "why": "the list as a plain list: item names typed, no price typed anywhere",
   "state": {
    "rowCount": 3,
    "rows": [
     {
      "item": "rice",
      "qty": "2",
      "price": "",
      "aisle": "",
      "store": ""
     },
     {
      "item": "eggs",
      "qty": "12",
      "price": "",
      "aisle": "",
      "store": ""
     },
     {
      "item": "oats",
      "qty": "1",
      "price": "",
      "aisle": "",
      "store": ""
     }
    ]
   },
   "outputs": {
    "rowCount": 3,
    "rowsTyped": 3,
    "rowsPriced": 0,
    "rowsAwaitingPrice": 3,
    "rowsAwaitingQuantity": 0,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 3,
    "rowsWithNoStore": 3,
    "aisleGroupsNamed": 0,
    "storeGroupsNamed": 0,
    "noAisleTotal": null,
    "noStoreTotal": null,
    "listTotal": null,
    "aisleColumnFoot": null,
    "storeColumnFoot": null,
    "crossFootDifference": null,
    "waiting": true,
    "row1Line": 0,
    "row2Line": 0,
    "row3Line": 0,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "part_priced": {
   "why": "a part-priced list: one row priced, one row with a quantity and no price",
   "state": {
    "rowCount": 2,
    "rows": [
     {
      "item": "rice",
      "qty": "2",
      "price": "2.50",
      "aisle": "dry",
      "store": "market"
     },
     {
      "item": "eggs",
      "qty": "12",
      "price": "",
      "aisle": "chilled",
      "store": "corner shop"
     }
    ]
   },
   "outputs": {
    "rowCount": 2,
    "rowsTyped": 2,
    "rowsPriced": 1,
    "rowsAwaitingPrice": 1,
    "rowsAwaitingQuantity": 0,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 0,
    "rowsWithNoStore": 0,
    "aisleGroupsNamed": 2,
    "storeGroupsNamed": 2,
    "noAisleTotal": null,
    "noStoreTotal": null,
    "listTotal": null,
    "aisleColumnFoot": null,
    "storeColumnFoot": null,
    "crossFootDifference": null,
    "waiting": true,
    "row1Line": 5,
    "row2Line": 0,
    "row3Line": 0,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "price_without_quantity": {
   "why": "a typed row carrying a price and no quantity: there is nothing on it to multiply the price by",
   "state": {
    "rowCount": 2,
    "rows": [
     {
      "item": "rice",
      "qty": "2",
      "price": "2.50",
      "aisle": "dry",
      "store": "market"
     },
     {
      "item": "eggs",
      "qty": "",
      "price": "3.60",
      "aisle": "chilled",
      "store": "market"
     }
    ]
   },
   "outputs": {
    "rowCount": 2,
    "rowsTyped": 2,
    "rowsPriced": 1,
    "rowsAwaitingPrice": 0,
    "rowsAwaitingQuantity": 1,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 0,
    "rowsWithNoStore": 0,
    "aisleGroupsNamed": 2,
    "storeGroupsNamed": 1,
    "noAisleTotal": null,
    "noStoreTotal": null,
    "listTotal": null,
    "aisleColumnFoot": null,
    "storeColumnFoot": null,
    "crossFootDifference": null,
    "waiting": true,
    "row1Line": 5,
    "row2Line": 0,
    "row3Line": 0,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "typed_zeros": {
   "why": "a zero quantity and a zero price, both printed as the zero they are and both still counted",
   "state": {
    "rowCount": 2,
    "rows": [
     {
      "item": "salt",
      "qty": "0",
      "price": "1.20",
      "aisle": "baking",
      "store": "market"
     },
     {
      "item": "bags",
      "qty": "3",
      "price": "0",
      "aisle": "baking",
      "store": "market"
     }
    ]
   },
   "outputs": {
    "rowCount": 2,
    "rowsTyped": 2,
    "rowsPriced": 2,
    "rowsAwaitingPrice": 0,
    "rowsAwaitingQuantity": 0,
    "rowsWithTypedZero": 2,
    "rowsWithNoAisle": 0,
    "rowsWithNoStore": 0,
    "aisleGroupsNamed": 1,
    "storeGroupsNamed": 1,
    "noAisleTotal": 0,
    "noStoreTotal": 0,
    "listTotal": 0,
    "aisleColumnFoot": 0,
    "storeColumnFoot": 0,
    "crossFootDifference": 0,
    "waiting": false,
    "row1Line": 0,
    "row2Line": 0,
    "row3Line": 0,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "labels_left_blank": {
   "why": "a row with an aisle and no stop, a row with a stop and no aisle, and a row with no label of either kind",
   "state": {
    "rowCount": 3,
    "rows": [
     {
      "item": "apples",
      "qty": "6",
      "price": "0.45",
      "aisle": "produce",
      "store": ""
     },
     {
      "item": "oats",
      "qty": "1",
      "price": "3.10",
      "aisle": "",
      "store": "market"
     },
     {
      "item": "tape",
      "qty": "1",
      "price": "2.00",
      "aisle": "",
      "store": ""
     }
    ]
   },
   "outputs": {
    "rowCount": 3,
    "rowsTyped": 3,
    "rowsPriced": 3,
    "rowsAwaitingPrice": 0,
    "rowsAwaitingQuantity": 0,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 2,
    "rowsWithNoStore": 2,
    "aisleGroupsNamed": 1,
    "storeGroupsNamed": 1,
    "noAisleTotal": 5.1,
    "noStoreTotal": 4.7,
    "listTotal": 7.800000000000001,
    "aisleColumnFoot": 7.8,
    "storeColumnFoot": 7.800000000000001,
    "crossFootDifference": -8.881784197001252e-16,
    "waiting": false,
    "row1Line": 2.7,
    "row2Line": 3.1,
    "row3Line": 2,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "blank_row_between": {
   "why": "a blank row standing between two typed and priced rows: the list is complete and the blank row is on no margin",
   "state": {
    "rowCount": 3,
    "rows": [
     {
      "item": "rice",
      "qty": "2",
      "price": "2.50",
      "aisle": "dry",
      "store": "market"
     },
     {
      "item": "",
      "qty": "",
      "price": "",
      "aisle": "",
      "store": ""
     },
     {
      "item": "oats",
      "qty": "1",
      "price": "3.10",
      "aisle": "dry",
      "store": "market"
     }
    ]
   },
   "outputs": {
    "rowCount": 3,
    "rowsTyped": 2,
    "rowsPriced": 2,
    "rowsAwaitingPrice": 0,
    "rowsAwaitingQuantity": 0,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 0,
    "rowsWithNoStore": 0,
    "aisleGroupsNamed": 1,
    "storeGroupsNamed": 1,
    "noAisleTotal": 0,
    "noStoreTotal": 0,
    "listTotal": 8.1,
    "aisleColumnFoot": 8.1,
    "storeColumnFoot": 8.1,
    "crossFootDifference": 0,
    "waiting": false,
    "row1Line": 5,
    "row2Line": 0,
    "row3Line": 3.1,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  },
  "out_of_range": {
   "why": "a quantity past the top of its range, a price past the top of its range, and a quantity typed below the bottom of its own range",
   "state": {
    "rowCount": 2,
    "rows": [
     {
      "item": "flour",
      "qty": "1200",
      "price": "12000",
      "aisle": "dry",
      "store": "market"
     },
     {
      "item": "salt",
      "qty": "-4",
      "price": "1.20",
      "aisle": "dry",
      "store": "market"
     }
    ]
   },
   "outputs": {
    "rowCount": 2,
    "rowsTyped": 2,
    "rowsPriced": 1,
    "rowsAwaitingPrice": 0,
    "rowsAwaitingQuantity": 1,
    "rowsWithTypedZero": 0,
    "rowsWithNoAisle": 0,
    "rowsWithNoStore": 0,
    "aisleGroupsNamed": 1,
    "storeGroupsNamed": 1,
    "noAisleTotal": null,
    "noStoreTotal": null,
    "listTotal": null,
    "aisleColumnFoot": null,
    "storeColumnFoot": null,
    "crossFootDifference": null,
    "waiting": true,
    "row1Line": 9989990.01,
    "row2Line": 0,
    "row3Line": 0,
    "row4Line": 0,
    "row5Line": 0,
    "row6Line": 0,
    "row7Line": 0,
    "row8Line": 0,
    "row9Line": 0,
    "row10Line": 0,
    "row11Line": 0,
    "row12Line": 0,
    "row13Line": 0,
    "row14Line": 0,
    "row15Line": 0,
    "row16Line": 0,
    "row17Line": 0,
    "row18Line": 0,
    "row19Line": 0,
    "row20Line": 0,
    "row21Line": 0,
    "row22Line": 0,
    "row23Line": 0,
    "row24Line": 0,
    "row25Line": 0,
    "row26Line": 0,
    "row27Line": 0,
    "row28Line": 0,
    "row29Line": 0,
    "row30Line": 0
   }
  }
 }
}
