{
  "name": "What is still owed on one invoice after the payments received, and the date it falls due",
  "page": "https://otherworldlabs.com/invoice-balance-due-after-payments/",
  "endpoint": "https://otherworldlabs.com/invoice-balance-due-after-payments/api.json",
  "question": "For one invoice, from the lines the caller is billing with their quantities and unit rates, the flat charges and the credits the caller has set as amounts, and the payments already received: what does each line come to, what does the invoice come to, what has been received against it, what is still owed, and on what date does it fall due given a number of whole calendar days?",
  "answers": "one invoice at a time. The page keeps no history, this endpoint stores nothing, and neither of them holds a rate, a charge, a credit, an interval or a tax figure of any kind.",
  "version": "2026-10-25",
  "licence": "The arithmetic is not ours to license. Reuse the formulas; the page is free and needs no attribution.",
  "method": "static description plus the state the page ships in and two worked invoices; the same formulas run in the page in the reader's browser, with nothing sent anywhere",
  "currency": "none is named by this engine. Every money input and output is in whatever currency the caller's own figures are in, and the engine neither converts nor labels it. The page itself prints every money figure with a dollar sign, as a display convention only, the same mark for whatever currency the reader actually typed in; it is not a claim that the engine, this endpoint or the reader's figures are in US dollars. A negative figure prints with the minus before the sign, -$140.00, never $-140.00 and never in brackets.",
  "supplies_no_figure": "This engine holds no unit rate, no flat charge, no credit, no terms interval and no tax rate, and it will not derive one from another field. Every one of them is supplied by the caller or the figures that depend on it come back null. The page ships with every one of those fields empty for the same reason: a shipped figure in a rate box is the page supplying a rate. Three example blocks sit below. The first of them, example, is that shipped state itself and carries no figure at all: every money field in it is null, its invoice date is empty and its interval is null. The two worked invoices beside it, worked_part_paid and worked_overpaid, do carry figures, because a worked invoice with no figures in it tests nothing; they are arbitrary, they are nobody's measurements, and they are not a starting point for anybody's billing.",
  "no_tax": "There is no tax of any kind in this engine: no sales tax, no VAT, no GST, no income tax, no withholding and no deduction. No rate for any of them is computed, supplied or applied, no output is a tax figure, and nothing here says what is taxable, at what rate or in what place. An invoice that needs a tax line needs a figure this engine does not hold and will not work out. The same is true of every legal question an invoice raises: this engine says nothing about whether a document is valid, binding, enforceable, compliant or sufficient, supplies no payment terms, no late fee, no interest, no penalty and no finance charge, names no numbering scheme as required, and asks for no bank account, card or routing number.",
  "rounding": {
    "inputs": "Every numeric input is read at its own field's fineness and then held inside that field's declared range: a count to a whole figure by Math.round, which sends a half toward positive infinity, and every money and quantity field to two decimals by round2 below. A typed 12.004 in a rate field is read as 12.00; a typed 120000 in a rate field whose range ends at 100000 is read as 100000; a typed 0 in the line count, whose range starts at 1, is read as 1. 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.",
    "counts": "whole. lineCount, chargeCount, creditCount, paymentCount and termsDays are read with Math.round and then held inside the range named beside each of them. A blank count falls back to the length of its own array, held inside the same range, except termsDays, where blank is not nought: it is the seventh thing the page tells apart, and it leaves the due date null.",
    "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 two outputs of this engine are legitimately negative - balanceStillOwed when the payments received come to more than the invoice total, and invoiceTotal when the credits come to more than the lines and the charges together - so a reimplementation that rounds half away from zero would part from this one by a cent on any negative half it met on them. NO STATE OF THIS ENGINE PUTS EITHER OF THEM ON A HALF, and that is said here rather than left for a reader to assume otherwise: every figure they are built from - each line amount, the charges total, the credits total, the payments total - has already been through round2, so the argument of each outer round2 is a whole number of cents. The only half this engine can reach is a positive one, qty * rate on a line, where neither factor is ever below nought. The negative rule is stated because it is the rule round2 follows, not because a state of this engine exercises it. round2 is applied where each figure is defined and again to every sum built out of figures already rounded, so no printed line is worked from a longer number than the one printed: the line amounts add up to linesSubtotal exactly. ONE PLACE THE RULE IS NOT THE DECIMAL RULE, named here rather than found later: a decimal half with no exact double is rounded from the double nearest it, so a typed 1.005 at the cent is 1.00 and not 1.01, because the double nearest 1.005 is a shade under it. 0.125 and -0.125 are both exactly representable, so the rule can be stated exactly at either half, and a reimplementation working in decimal will part from this engine only on the halves that are not - and on the negative half not at all, since no state of this engine reaches one.",
    "quantities": "two decimals, by the same round2, so hours, days, units and metres all fit. A quantity is printed at the fineness it was typed to, with the trailing noughts dropped, and is never money.",
    "dates": "A date is read only as four digits, a two-digit month and a two-digit day joined by hyphens, and only when it is a real day of a real month: 2026-02-30 is not a date and is read as no date at all. The page's own date fields are date inputs, so a reader's value is always in that form or empty. Date arithmetic is whole days from a fixed origin: dueDate is the invoice date plus termsDays, counted in CALENDAR days straight through weekends and holidays. It is not a working-day count, the engine knows no holidays, and no output of this engine is worked out from today's date, so no output depends on today and no figure changes between one day and the next. ONE CLOCK IS READ ON THE PAGE AND IT REACHES NO FIGURE: the page that ships beside this endpoint stamps the date in its print header when a reader prints the sheet, read once from the reader's own device, and no input, output or printed figure here or there is worked out from it.",
    "percentages": "there are none. This engine returns no percentage and the page prints none: every output is money, a quantity, a count, a date, a state name or a flag. A credit is a flat amount the caller passes, never a rate, and no tax, discount, interest or penalty rate exists anywhere here."
  },
  "inputs": [
    {
      "name": "lineCount",
      "type": "integer",
      "unit": "lines",
      "required": true,
      "range": "1 to 20",
      "note": "How many of the lines array are read. Rows past it are ignored. Blank falls back to the length of the array, held inside the range."
    },
    {
      "name": "lines[].desc",
      "type": "text",
      "unit": "words",
      "required": false,
      "range": "up to 60 characters",
      "note": "What the line is for, in the caller's own words. Blank is allowed and the line is then named by its own number. It is never used in any arithmetic."
    },
    {
      "name": "lines[].qty",
      "type": "number",
      "unit": "whatever the caller is counting",
      "required": false,
      "range": "0 to 9999, two decimals",
      "note": "How many: hours, days, units, metres. Nought is a real answer and prices the line at nothing. Blank with a rate typed is a half-entered line, which withholds every total."
    },
    {
      "name": "lines[].rate",
      "type": "money",
      "unit": "money for one",
      "required": false,
      "range": "0 to 100000.00",
      "note": "The price for one of whatever the quantity counts. The caller's own; this engine has none and proposes none. Nought is a real answer. Blank with a quantity typed is a half-entered line."
    },
    {
      "name": "chargeCount",
      "type": "integer",
      "unit": "charges",
      "required": true,
      "range": "0 to 6",
      "note": "How many of the charges array are read. Nought is a real answer."
    },
    {
      "name": "charges[].chargeDesc",
      "type": "text",
      "unit": "words",
      "required": false,
      "range": "up to 60 characters",
      "note": "What the flat charge is for. Never used in arithmetic."
    },
    {
      "name": "charges[].chargeAmt",
      "type": "money",
      "unit": "money",
      "required": false,
      "range": "0 to 100000.00",
      "note": "A flat amount that is not priced per unit. Blank contributes nothing and the working says so; it does not withhold a total."
    },
    {
      "name": "creditCount",
      "type": "integer",
      "unit": "credits",
      "required": true,
      "range": "0 to 6",
      "note": "How many of the credits array are read. Nought is a real answer."
    },
    {
      "name": "credits[].creditDesc",
      "type": "text",
      "unit": "words",
      "required": false,
      "range": "up to 60 characters",
      "note": "What the credit is for. Never used in arithmetic."
    },
    {
      "name": "credits[].creditAmt",
      "type": "money",
      "unit": "money",
      "required": false,
      "range": "0 to 100000.00",
      "note": "A reduction the caller has set, as a flat amount. Never a percentage and never worked out by this engine."
    },
    {
      "name": "paymentCount",
      "type": "integer",
      "unit": "payments",
      "required": true,
      "range": "0 to 12",
      "note": "How many of the payments array are read. Nought is a real answer and is the state where nothing has been received yet."
    },
    {
      "name": "payments[].payDate",
      "type": "date",
      "unit": "a calendar date",
      "required": false,
      "range": "any real day, or blank",
      "note": "The day the payment arrived. BLANK IS ALLOWED and so is text this engine cannot read as a date: such a row is counted into paymentsReceivedTotal and into paymentsCounted exactly as a dated row is, is counted in undatedPayments, and is the only row left out of paymentDatedBeforeInvoiceDate, which needs two readable dates. A caller who holds one total paid to date and no dated record passes it as one row with a blank date."
    },
    {
      "name": "payments[].payAmt",
      "type": "money",
      "unit": "money",
      "required": false,
      "range": "0 to 1000000.00",
      "note": "The amount received. A row with an amount is counted; a row with none is not. Nought is a real answer and is counted."
    },
    {
      "name": "invoiceDate",
      "type": "date",
      "unit": "a calendar date",
      "required": false,
      "range": "any real day, or blank",
      "note": "The day the invoice was raised. Blank leaves dueDate null and leaves paymentDatedBeforeInvoiceDate false, because that report needs two readable dates."
    },
    {
      "name": "termsDays",
      "type": "integer",
      "unit": "whole calendar days",
      "required": false,
      "range": "0 to 365, or blank",
      "note": "The interval the caller set. BLANK IS NOT NOUGHT: blank leaves dueDate null, which is the seventh thing the page tells apart, while nought means the invoice falls due on the day it was raised. This engine proposes no interval and the page ships the field empty."
    }
  ],
  "outputs": [
    {
      "name": "lineCount",
      "type": "integer",
      "unit": "lines",
      "note": "How many lines were read."
    },
    {
      "name": "linesPriced",
      "type": "integer",
      "unit": "lines",
      "note": "How many of them carry both a quantity and a rate."
    },
    {
      "name": "linesHalfEntered",
      "type": "integer",
      "unit": "lines",
      "note": "How many carry one of the two and not the other. Any of these makes the entry incomplete."
    },
    {
      "name": "linesSubtotal",
      "type": "money or null",
      "unit": "money",
      "note": "The rounded line amounts added. Null while the entry is incomplete."
    },
    {
      "name": "chargesTotal",
      "type": "money",
      "unit": "money",
      "note": "The flat charges that carry an amount, added. Nought when none does."
    },
    {
      "name": "creditsTotal",
      "type": "money",
      "unit": "money",
      "note": "The credits that carry an amount, added. Nought when none does."
    },
    {
      "name": "invoiceTotal",
      "type": "money or null",
      "unit": "money",
      "note": "linesSubtotal plus chargesTotal less creditsTotal. Null while the entry is incomplete. It is negative when the credits come to more than the lines and the charges together, and is never floored at nought."
    },
    {
      "name": "paymentsReceivedTotal",
      "type": "money",
      "unit": "money",
      "note": "The payment rows that carry an amount, added. It does not wait on a complete entry."
    },
    {
      "name": "paymentsCounted",
      "type": "integer",
      "unit": "payments",
      "note": "How many payment rows carry an amount and are therefore counted into the balance. A row is counted on carrying an amount at all, so a row typed as 0.00 is counted here and adds nothing to paymentsReceivedTotal. COUNTING A ROW IS NOT RECEIVING MONEY: balanceState is decided by paymentsReceivedTotal and never by this count, so paymentsCounted above nought with paymentsReceivedTotal of nought is still 'nothing received yet'."
    },
    {
      "name": "undatedPayments",
      "type": "integer",
      "unit": "payments",
      "note": "How many of those carry no date this engine can read."
    },
    {
      "name": "balanceStillOwed",
      "type": "money or null",
      "unit": "money",
      "note": "invoiceTotal less paymentsReceivedTotal, with its own sign and never floored at nought. Null while the entry is incomplete."
    },
    {
      "name": "dueDate",
      "type": "date or null",
      "unit": "a calendar date",
      "note": "invoiceDate plus termsDays in whole calendar days. Null for any of three reasons: the entry is incomplete, no interval is typed, or the invoice date is not a date."
    },
    {
      "name": "balanceState",
      "type": "text",
      "unit": "a state name",
      "note": "Exactly one of five, and every test below is on an AMOUNT, never on a row count. In order: 'still being filled in' when withheld is true; otherwise 'paid above the total' when balanceStillOwed is below nought; otherwise 'nothing received yet' when paymentsReceivedTotal is exactly nought; otherwise 'settled to the penny' when balanceStillOwed is exactly nought; otherwise 'part paid'. The order is what makes exactly one of them apply, so an invoice for nothing with nothing paid against it is 'nothing received yet' rather than settled. The third test reads paymentsReceivedTotal and NOT paymentsCounted, which is the one thing a caller cannot guess: a payment row typed as 0.00 raises paymentsCounted to one and leaves paymentsReceivedTotal at nought, and the state is 'nothing received yet' because nothing has been received. 'paid above the total' is the name for every negative balance, including the one the typed credits cause with nothing received at all (creditsOverBilled true, paymentsReceivedTotal nought); the page's own sentence for that state names the credits rather than an overpayment."
    },
    {
      "name": "withheld",
      "type": "boolean",
      "unit": "a flag",
      "note": "True when no line is priced, or when any line carries one of its two figures and not the other. While it is true the four figures above that can be null are null."
    },
    {
      "name": "paymentDatedBeforeInvoiceDate",
      "type": "boolean",
      "unit": "a flag",
      "note": "True when a counted payment carries a readable date earlier than a readable invoice date. The payment is still counted into the balance; this is a report, not an exclusion."
    },
    {
      "name": "creditsOverBilled",
      "type": "boolean",
      "unit": "a flag",
      "note": "True when invoiceTotal is negative, which is when the credits come to more than the lines and the charges together."
    },
    {
      "name": "line1Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 1: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line2Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 2: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line3Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 3: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line4Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 4: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line5Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 5: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line6Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 6: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line7Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 7: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line8Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 8: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line9Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 9: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line10Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 10: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line11Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 11: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line12Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 12: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line13Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 13: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line14Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 14: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line15Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 15: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line16Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 16: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line17Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 17: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line18Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 18: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line19Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 19: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    },
    {
      "name": "line20Amount",
      "type": "money or null",
      "unit": "money",
      "note": "The amount of line 20: round2(qty * rate) for that line, or null where it carries one of the two figures and not the other, or where it is past lineCount. Every one of the twenty is named here rather than collapsed into a range, because a field list a reader has to expand is a field list a checker cannot read."
    }
  ],
  "formula": [
    "lineNAmount   = round2(lines[N].qty * lines[N].rate), or null unless both are present",
    "linesSubtotal = round2(sum of the non-null lineNAmount for N up to lineCount), or null while withheld",
    "chargesTotal  = round2(sum of charges[].chargeAmt that are present, up to chargeCount)",
    "creditsTotal  = round2(sum of credits[].creditAmt that are present, up to creditCount)",
    "invoiceTotal  = round2(round2(linesSubtotal + chargesTotal) - creditsTotal), or null while withheld",
    "paymentsReceivedTotal = round2(sum of payments[].payAmt that are present, up to paymentCount)",
    "balanceStillOwed = round2(invoiceTotal - paymentsReceivedTotal), or null while withheld",
    "dueDate = the day invoiceDate falls on, plus termsDays whole calendar days",
    "withheld = linesPriced is nought, or linesHalfEntered is above nought",
    "balanceState = 'still being filled in' if withheld; else 'paid above the total' if balanceStillOwed < 0; else 'nothing received yet' if paymentsReceivedTotal === 0; else 'settled to the penny' if balanceStillOwed === 0; else 'part paid'"
  ],
  "identities": [
    "The line amounts added come to linesSubtotal exactly, because each is rounded where it is defined and the subtotal is the sum of the rounded ones: the printed column adds up to the printed foot with no remainder anywhere.",
    "invoiceTotal + paymentsReceivedTotal - balanceStillOwed is nought, to the cent, whenever the entry is complete.",
    "linesSubtotal + chargesTotal - creditsTotal - invoiceTotal is nought, to the cent, whenever the entry is complete.",
    "balanceStillOwed is negative when and only when paymentsReceivedTotal comes to more than invoiceTotal, and creditsOverBilled is true when and only when invoiceTotal is negative.",
    "dueDate less invoiceDate, counted in whole days, is termsDays exactly, whenever dueDate is not null.",
    "paymentsCounted is the number of payment rows up to paymentCount that carry an amount, and undatedPayments is never above it.",
    "Exactly one of the five balanceState names applies to any state, and 'still being filled in' applies when and only when withheld is true.",
    "balanceState is a function of withheld, balanceStillOwed and paymentsReceivedTotal alone: paymentsCounted appears in no test of it. So 'nothing received yet' applies when and only when the entry is complete, balanceStillOwed is nought or more and paymentsReceivedTotal is exactly nought - whatever paymentsCounted reads - and neither 'part paid' nor 'settled to the penny' can apply while paymentsReceivedTotal is nought."
  ],
  "example": {
    "why": "The state the page ships in: six line blocks, one flat charge, one credit and two payment rows on screen, and not one figure typed. It is the first of the eight things the page tells apart.",
    "inputs": {
      "lineCount": 6,
      "lines": [
        {
          "desc": "",
          "qty": null,
          "rate": null
        },
        {
          "desc": "",
          "qty": null,
          "rate": null
        },
        {
          "desc": "",
          "qty": null,
          "rate": null
        },
        {
          "desc": "",
          "qty": null,
          "rate": null
        },
        {
          "desc": "",
          "qty": null,
          "rate": null
        },
        {
          "desc": "",
          "qty": null,
          "rate": null
        }
      ],
      "chargeCount": 1,
      "charges": [
        {
          "chargeDesc": "",
          "chargeAmt": null
        }
      ],
      "creditCount": 1,
      "credits": [
        {
          "creditDesc": "",
          "creditAmt": null
        }
      ],
      "paymentCount": 2,
      "payments": [
        {
          "payDate": "",
          "payAmt": null
        },
        {
          "payDate": "",
          "payAmt": null
        }
      ],
      "invoiceDate": "",
      "termsDays": null
    },
    "outputs": {
      "lineCount": 6,
      "linesPriced": 0,
      "linesHalfEntered": 0,
      "linesSubtotal": null,
      "chargesTotal": 0,
      "creditsTotal": 0,
      "invoiceTotal": null,
      "paymentsReceivedTotal": 0,
      "paymentsCounted": 0,
      "undatedPayments": 0,
      "balanceStillOwed": null,
      "dueDate": null,
      "balanceState": "still being filled in",
      "withheld": true,
      "paymentDatedBeforeInvoiceDate": false,
      "creditsOverBilled": false,
      "line1Amount": null,
      "line2Amount": null,
      "line3Amount": null,
      "line4Amount": null,
      "line5Amount": null,
      "line6Amount": null,
      "line7Amount": null,
      "line8Amount": null,
      "line9Amount": null,
      "line10Amount": null,
      "line11Amount": null,
      "line12Amount": null,
      "line13Amount": null,
      "line14Amount": null,
      "line15Amount": null,
      "line16Amount": null,
      "line17Amount": null,
      "line18Amount": null,
      "line19Amount": null,
      "line20Amount": null
    },
    "reading": "Nothing is typed, so no line is priced, the entry is incomplete and the four figures that wait on a complete entry are all null. The two tallies that do not wait on it answer: the charges and the credits come to nothing, because no charge and no credit carries an amount."
  },
  "worked_part_paid": {
    "why": "An invoice part paid: four priced lines, one flat charge, one credit, two payments received and an interval of thirty days. The third of the eight things the page tells apart.",
    "inputs": {
      "lineCount": 4,
      "lines": [
        {
          "desc": "Editing, episode one",
          "qty": 12.5,
          "rate": 75
        },
        {
          "desc": "Editing, episode two",
          "qty": 10,
          "rate": 75
        },
        {
          "desc": "Colour grading",
          "qty": 4,
          "rate": 90
        },
        {
          "desc": "Revisions, second round",
          "qty": 3.5,
          "rate": 75
        }
      ],
      "chargeCount": 1,
      "charges": [
        {
          "chargeDesc": "Rush delivery",
          "chargeAmt": 150
        }
      ],
      "creditCount": 1,
      "credits": [
        {
          "creditDesc": "Referral reduction",
          "creditAmt": 100
        }
      ],
      "paymentCount": 2,
      "payments": [
        {
          "payDate": "2026-09-15",
          "payAmt": 1000
        },
        {
          "payDate": "2026-10-01",
          "payAmt": 860
        }
      ],
      "invoiceDate": "2026-09-01",
      "termsDays": 30
    },
    "outputs": {
      "lineCount": 4,
      "linesPriced": 4,
      "linesHalfEntered": 0,
      "linesSubtotal": 2310,
      "chargesTotal": 150,
      "creditsTotal": 100,
      "invoiceTotal": 2360,
      "paymentsReceivedTotal": 1860,
      "paymentsCounted": 2,
      "undatedPayments": 0,
      "balanceStillOwed": 500,
      "dueDate": "2026-10-01",
      "balanceState": "part paid",
      "withheld": false,
      "paymentDatedBeforeInvoiceDate": false,
      "creditsOverBilled": false,
      "line1Amount": 937.5,
      "line2Amount": 750,
      "line3Amount": 360,
      "line4Amount": 262.5,
      "line5Amount": null,
      "line6Amount": null,
      "line7Amount": null,
      "line8Amount": null,
      "line9Amount": null,
      "line10Amount": null,
      "line11Amount": null,
      "line12Amount": null,
      "line13Amount": null,
      "line14Amount": null,
      "line15Amount": null,
      "line16Amount": null,
      "line17Amount": null,
      "line18Amount": null,
      "line19Amount": null,
      "line20Amount": null
    },
    "reading": "The four lines come to 937.50, 750.00, 360.00 and 262.50, which add to 2310.00; the flat charge of 150.00 goes on and the credit of 100.00 comes off, so the invoice comes to 2360.00. Two payments of 1000.00 and 860.00 are counted, which is 1860.00 received, and 2360.00 less 1860.00 leaves 500.00 still owed. Thirty days from 2026-09-01 is 2026-10-01."
  },
  "worked_overpaid": {
    "why": "An invoice paid above its total, with a payment dated earlier than the invoice and a payment with no date at all: the fifth thing the page tells apart, with the sixth beside it, and the state a reimplementation that floors the balance at nought gets wrong.",
    "inputs": {
      "lineCount": 2,
      "lines": [
        {
          "desc": "Site visit",
          "qty": 2,
          "rate": 100
        },
        {
          "desc": "Materials",
          "qty": 1,
          "rate": 50
        }
      ],
      "chargeCount": 0,
      "charges": [],
      "creditCount": 0,
      "credits": [],
      "paymentCount": 2,
      "payments": [
        {
          "payDate": "2026-08-01",
          "payAmt": 200
        },
        {
          "payDate": "",
          "payAmt": 150
        }
      ],
      "invoiceDate": "2026-09-15",
      "termsDays": 14
    },
    "outputs": {
      "lineCount": 2,
      "linesPriced": 2,
      "linesHalfEntered": 0,
      "linesSubtotal": 250,
      "chargesTotal": 0,
      "creditsTotal": 0,
      "invoiceTotal": 250,
      "paymentsReceivedTotal": 350,
      "paymentsCounted": 2,
      "undatedPayments": 1,
      "balanceStillOwed": -100,
      "dueDate": "2026-09-29",
      "balanceState": "paid above the total",
      "withheld": false,
      "paymentDatedBeforeInvoiceDate": true,
      "creditsOverBilled": false,
      "line1Amount": 200,
      "line2Amount": 50,
      "line3Amount": null,
      "line4Amount": null,
      "line5Amount": null,
      "line6Amount": null,
      "line7Amount": null,
      "line8Amount": null,
      "line9Amount": null,
      "line10Amount": null,
      "line11Amount": null,
      "line12Amount": null,
      "line13Amount": null,
      "line14Amount": null,
      "line15Amount": null,
      "line16Amount": null,
      "line17Amount": null,
      "line18Amount": null,
      "line19Amount": null,
      "line20Amount": null
    },
    "reading": "Two lines come to 200.00 and 50.00, which is 250.00, with no charge and no credit, so the invoice comes to 250.00. Two payments are counted, 200.00 and 150.00, which is 350.00 received, and 250.00 less 350.00 leaves -100.00: the balance is negative because more has come in than the invoice comes to, and it is printed there rather than floored. One of the two payments carries no date, which is counted and reported and changes no figure; the other is dated 2026-08-01, earlier than the invoice date of 2026-09-15, which is reported and is still counted. Fourteen days from 2026-09-15 is 2026-09-29."
  },
  "not_covered": [
    "Tax of any kind. No sales tax, VAT, GST, income tax, withholding or deduction is computed, supplied or applied, and an invoice that needs a tax line needs a figure this engine does not hold.",
    "Anything legal. Whether a document is a valid invoice, whether an amount is recoverable, what a payer owes by law, and what a seller may charge are all outside this engine.",
    "Late fees, interest, penalties, finance charges and collection steps. None of them is an input, an output or a sentence here.",
    "What to charge. No typical, standard, market or recommended rate, charge or fee is supplied, and no advice on pricing is given anywhere.",
    "Payment credentials. No bank account, card, routing number or IBAN is an input, and the workbook sold beside the page has no cell for one.",
    "Anything that depends on today. No days-remaining figure, no days-overdue figure, and no figure anywhere worked out from a clock - the page reads one only for the date it stamps in its print header, which no figure depends on.",
    "Currency. No currency is named and nothing is converted.",
    "Working days and holidays. The interval is counted in calendar days, straight through both.",
    "Partial allocation of a payment across several invoices. This engine answers one invoice at a time and every counted payment is counted in full against it.",
    "A percentage of anything. A credit is a flat amount the caller passes."
  ],
  "no_advice": "This endpoint is arithmetic on figures a caller passes. It recommends nothing, holds no rate, charge, credit, interval or tax figure of its own, and says nothing about what anybody owes beyond the arithmetic of the figures and dates it was given."
}
