{
  "endpoint": "https://otherworldlabs.com/monthly-budget-what-is-left/api.json",
  "page": "https://otherworldlabs.com/monthly-budget-what-is-left/",
  "what_it_answers": "For the income lines and the category lines a reader types for one month - each line a name they typed and an amount in dollars - what does each side come to, how many lines were footed on each side, and what is the difference between the two feet: what is left over, or what the month is short by.",
  "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 an agent can answer the question without the page and get the same figures the page prints. The engine itself is the script with the id core in the page above, and its flat outputs function is apiOutputs(state).",
  "units": "US dollars and whole counts of lines, and nothing else. No percentage is computed or returned anywhere, deliberately: a percentage of somebody's income is the shape of a spending rule, and this engine states no rule.",
  "the_engine_supplies_nothing": "Every line, every amount and every name in this arithmetic is typed by the reader. This engine holds no category list, no default split, no suggested line, no percentage, no typical or household figure and no limit of any kind; it recommends nothing, it orders nothing anybody owes, it says nothing about whether a month is healthy or about whether anything on it is worth its amount, and it returns no credit, loan, mortgage, insurance, investment, retirement or tax figure. It totals two sides a reader typed and subtracts one from the other. That is the whole of it.",
  "state": {
    "two_sides": "The spelling the page's own form fills: { incomeLineCount, categoryLineCount, incomeLines: [{name, amount}], categoryLines: [{name, amount}] }. The count says how many lines of that side the form is showing, and the engine foots that many, padding a missing member with a blank one. A count above that side's offered maximum is read as the maximum, because the maximum is the number of rows the form draws: the engine foots at most 8 income lines and at most 20 category lines, so an agent that hands it 10 income lines and a count of 10 gets the first 8 of them footed. A count below one foots nothing on that side.",
    "one_list": "The equivalent spelling, for a record that carries one row per line: { lines: [{side, name, amount}] }, where side is \"income\" or \"category\" in any case. The two spellings are read into the same two sides and return the same outputs, which the page's own test suite asserts at every swept state. A line whose side is neither word is footed on neither side, because a line whose side was never chosen belongs to neither.",
    "blank": "A blank amount is not a zero: the line is not footed, it adds nothing to its side and it is not counted. A blank name is not a blank line: an amount typed with no name beside it is footed like every other and is labelled by its position."
  },
  "inputs": [
    {
      "name": "incomeLineCount",
      "label": "How many lines bring money in this month?",
      "type": "count",
      "offered": { "min": 1, "max": 8, "step": 1 },
      "blank_means": "the form always shows at least one line, so this is never blank in the page; an engine given no count foots every member of the array it was handed",
      "note": "The count of lines is the only figure the form itself chooses a bound for. It is not a limit on anybody's month: it is the number of rows the page draws. The engine reads a count above the maximum as the maximum, so 8 is also the most income lines it will ever foot; the form's own select offers 1 to 8 and cannot ask for more."
    },
    {
      "name": "incomeName",
      "label": "Your name for an income line",
      "type": "text",
      "offered": { "max_characters": 40 },
      "blank_means": "the line is labelled by its position instead, and its amount is still footed",
      "note": "Free text. No name is supplied, suggested, prefilled or completed by this engine, on either side."
    },
    {
      "name": "incomeAmount",
      "label": "The amount for that income line, for this one month",
      "type": "number",
      "offered": { "min": 0, "max": 100000, "step": 0.01 },
      "blank_means": "nothing typed on that line, so it is not footed and not counted",
      "note": "Dollars for one month, read at the cent. The figure is the reader's own, off their pay stub, deposit or statement line; no field here asks for an institution, an account, a balance or a credential."
    },
    {
      "name": "categoryLineCount",
      "label": "How many lines take money out this month?",
      "type": "count",
      "offered": { "min": 1, "max": 20, "step": 1 },
      "blank_means": "as for the income side: the form always shows at least one line",
      "note": "The two sides lengthen independently, because a month with one income line and fourteen category lines is an ordinary month. The engine reads a count above the maximum as the maximum here too, so 20 is the most category lines it will ever foot; the form's own select offers 1 to 20 and cannot ask for more."
    },
    {
      "name": "categoryName",
      "label": "Your name for a category line",
      "type": "text",
      "offered": { "max_characters": 40 },
      "blank_means": "the line is labelled by its position instead, and its amount is still footed",
      "note": "Free text, and the fence the page is built on: this engine supplies no category list and no suggested line. A document that named the categories would be making a recommendation; one that carries the reader's own names is carrying their month."
    },
    {
      "name": "categoryAmount",
      "label": "The amount for that category line, for this one month",
      "type": "number",
      "offered": { "min": 0, "max": 100000, "step": 0.01 },
      "blank_means": "nothing typed on that line, so it is not footed and not counted",
      "note": "Dollars for one month, read at the cent."
    }
  ],
  "rounding": {
    "function": "rN(v, n) = Math.round(v * 10^n) / 10^n, on the double, with no epsilon and no decimal library.",
    "at_the_half": "JavaScript Math.round takes the half toward positive infinity. On the positive side that is half away from zero: 0.125 at two decimals is 0.13. On the negative side it is half toward zero: -0.125 at two decimals is -0.12, not -0.13, and -0.005 at two decimals is -0.0 rather than -0.01. The negative half is reachable here, because the difference is negative in every month whose category side is the larger, and it is stated rather than left to a reader to discover.",
    "what_is_rounded": "Each amount is rounded to the cent as it is read, because the control offers a step of a cent and the figures added have to be the figures printed. Each side's total is then rounded to the cent again, which only clears the float error of adding cents. The difference is the two rounded totals subtracted, rounded to the cent: it is not a third rounding of anything, because the two operands are already whole cents.",
    "money_convention": "A negative figure is printed with the minus before the currency sign: -$41.73, never $-41.73 and never ($41.73). The workbook sold beside the page prints it the same way."
  },
  "the_two_sides": "This engine performs no division. It foots two independently lengthened sides whose every row label was typed by the reader, and its one computed row is the subtraction of one side's foot from the other's. There is no total to allocate, nothing is divided between anybody, and no line is ever a part of a whole this engine supplied.",
  "formulas": {
    "incomeTotal": "rN(sum of rN(amount, 2) over the income lines whose amount is a figure, 2)",
    "categoryTotal": "rN(sum of rN(amount, 2) over the category lines whose amount is a figure, 2)",
    "incomeLinesFooted": "the count of income lines whose amount is a figure, blank-named or not",
    "categoryLinesFooted": "the count of category lines whose amount is a figure, blank-named or not",
    "linesFooted": "incomeLinesFooted + categoryLinesFooted",
    "complete": "incomeTotal > 0 and categoryTotal > 0",
    "difference": "null while complete is false; otherwise rN(incomeTotal - categoryTotal, 2)",
    "surplus": "null while complete is false; otherwise the difference where it is above zero, and 0 where it is not",
    "shortfall": "null while complete is false; otherwise rN(-difference, 2) where the difference is below zero, and 0 where it is not"
  },
  "states": [
    {
      "name": "bothSidesEmpty",
      "when": "no amount above zero has been typed on either side",
      "complete": false,
      "prints": "The page prints no figure it computed, anywhere: the five headline rows (both feet, the difference and both counts) read \"not yet\", the three feet of the computed table read \"not yet\", the working block prints nothing at all, and the sentence saying that neither side foots to a positive amount prints in the glance, above where the figures would be. The only figures printed anywhere on the page in this state are the amounts the reader typed, printed back in their own table rows at the cent - so a side of zeroes prints $0.00 in its rows and no total."
    },
    {
      "name": "categorySideEmpty",
      "when": "the income side carries an amount above zero and the category side does not",
      "complete": false,
      "prints": "As above, and the sentence names the category side. This is the state a stranger who typed one income line and nothing else is in, and the reason the page prints no figure it computed in it: a confident surplus off a half-entered month would tell somebody they have money they do not have. The income side's own foot is returned in `incomeTotal` here and is printed NOWHERE on the page - not in the glance, not in the table's foot and not in the working, which waits with them (the working block printed `What comes in, added up` here until 2026-10-24, which is the display condition REVIEW-round1 B2 and B3 closed; audit.js check 8b now fails if it comes back)."
    },
    {
      "name": "incomeSideEmpty",
      "when": "the category side carries an amount above zero and the income side does not",
      "complete": false,
      "prints": "As above, and the sentence names the income side."
    },
    {
      "name": "leftOver",
      "when": "both sides carry an amount above zero and the difference is above zero",
      "complete": true,
      "prints": "The two feet, the difference as its own positive figure, the two counts, and one sentence: the income side foots to one figure, the category side to another, and the stated amount is left over."
    },
    {
      "name": "sidesEqual",
      "when": "both sides carry an amount above zero and the two feet are the same figure",
      "complete": true,
      "prints": "The two feet and a difference of $0.00, which is the only zero the page ever COMPUTES. It is not the only zero the page prints: a line with 0 typed on it prints $0.00 in its own row of the table, and the same 0.00 is one of the addends in that side's printed sum once the month is complete - both of them the reader's own figure printed back, neither of them a figure this engine computed."
    },
    {
      "name": "shortBy",
      "when": "both sides carry an amount above zero and the difference is below zero",
      "complete": true,
      "prints": "The two feet and the difference as -$ its own figure, with the sentence saying what the month is short by. A negative difference is never shown as zero, never hidden, never rounded towards zero and never softened: it is printed as plainly as a surplus, and the page says nothing about it beyond the subtraction."
    }
  ],
  "outputs": [
    { "name": "complete", "type": "boolean", "of": "whether both sides carry an amount above zero" },
    { "name": "state", "type": "string", "of": "one of the six names above, spelled exactly: bothSidesEmpty, categorySideEmpty, incomeSideEmpty, leftOver, sidesEqual, shortBy" },
    { "name": "incomeTotal", "type": "dollars, 2 decimals", "of": "the income side's foot for the month. Returned at every state, including the three in which the page prints no figure it computed and this foot is printed nowhere on it: it is the sum of what has been typed on that side, which is true whether or not the other side has been entered." },
    { "name": "categoryTotal", "type": "dollars, 2 decimals", "of": "the category side's foot for the month, on the same terms" },
    { "name": "incomeLinesFooted", "type": "whole count", "of": "how many income lines carried an amount, so a reader can see that every amount they typed was footed" },
    { "name": "categoryLinesFooted", "type": "whole count", "of": "how many category lines carried an amount" },
    { "name": "linesFooted", "type": "whole count", "of": "the two counts added, which is every line footed in the month" },
    { "name": "difference", "type": "dollars, 2 decimals, or null", "of": "the income foot less the category foot, positive when something is left over and negative when the month is short. NULL while complete is false, because the difference between a month's two sides is not defined while one of them has not been entered. An agent that prints a difference where this is null has printed a figure about an unfinished month." },
    { "name": "surplus", "type": "dollars, 2 decimals, or null", "of": "the difference where it is above zero, 0 where it is not, null while complete is false" },
    { "name": "shortfall", "type": "dollars, 2 decimals, or null", "of": "what the month is short by as a positive figure, 0 where the month is not short, null while complete is false" }
  ],
  "worked_example": {
    "why": "one income line and two category lines, a month with something left over",
    "state": {
      "incomeLineCount": 1,
      "categoryLineCount": 2,
      "incomeLines": [{ "name": "pay", "amount": 2400 }],
      "categoryLines": [{ "name": "rent", "amount": 1200 }, { "name": "the car", "amount": 310.25 }]
    },
    "working": [
      "incomeTotal: 2400.00",
      "categoryTotal: 1200.00 + 310.25 = 1510.25",
      "difference: 2400.00 - 1510.25 = 889.75",
      "incomeLinesFooted: 1, categoryLinesFooted: 2, linesFooted: 3"
    ],
    "outputs": {
      "complete": true,
      "state": "leftOver",
      "incomeTotal": 2400,
      "categoryTotal": 1510.25,
      "incomeLinesFooted": 1,
      "categoryLinesFooted": 2,
      "linesFooted": 3,
      "difference": 889.75,
      "surplus": 889.75,
      "shortfall": 0
    }
  },
  "second_worked_example": {
    "why": "the same month a cent short, in the one-list spelling, with an amount typed against no name",
    "state": {
      "lines": [
        { "side": "income", "name": "pay", "amount": 1500 },
        { "side": "category", "name": "", "amount": 1200 },
        { "side": "category", "name": "the car", "amount": 300.01 }
      ]
    },
    "working": [
      "incomeTotal: 1500.00",
      "categoryTotal: 1200.00 + 300.01 = 1500.01",
      "difference: 1500.00 - 1500.01 = -0.01",
      "the unnamed category line is footed and labelled by its position, because no typed amount is ever excluded from its side"
    ],
    "outputs": {
      "complete": true,
      "state": "shortBy",
      "incomeTotal": 1500,
      "categoryTotal": 1500.01,
      "incomeLinesFooted": 1,
      "categoryLinesFooted": 2,
      "linesFooted": 3,
      "difference": -0.01,
      "surplus": 0,
      "shortfall": 0.01
    }
  },
  "limits": [
    "It answers one month. It keeps nothing between visits, carries nothing forward and compares nothing with any other month. The workbook sold beside the page is the thing that carries a month per column.",
    "It knows nothing about what a line is. A line called rent and a line called a loan are two names and two amounts to this engine, and it will not treat either differently.",
    "It supplies no category list, no default split, no percentage, no rule and no typical figure, and it makes no judgement of any kind about the month it foots. Nothing it returns is advice, and a figure it returns is not a statement that anything is affordable, adequate or wise.",
    "A difference is returned only once both sides carry an amount. There is no way to ask it what is left of a month that has only one side entered, because there is no answer to that question.",
    "The engine stores nothing and sends nothing: it is this arithmetic and it holds no state between calls. The page that carries it keeps nothing between visits either, and no name, amount or total a reader types ever leaves their machine; what the page itself sends is the funnel beacon - that the page was opened, that the workbook block was seen and that the buy link was clicked, with the page's address, a one-visit identifier and the referring site, and with no figure of anybody's month attached. There is no field for an institution, an account, a balance or a credential, and this document is the whole of the interface."
  ]
}
