{
  "name": "A nail day, footed twice: the clients a day holds against the hands and against the clock",
  "page": "https://otherworldlabs.com/nail-tech-appointment-stock-log/",
  "endpoint": "https://otherworldlabs.com/nail-tech-appointment-stock-log/api.json",
  "question": "For one day a solo nail tech is planning, how many clients does the day hold once each service's cure window is counted as well as its hands-on time, how many minutes does the mix put on the clock, what does the day gross at the tech's own prices, and when does it finish?",
  "answers": "one day at a time; the page keeps no history, this endpoint stores nothing, and neither is an appointment book: there is no state, no reminder and no client record anywhere in either of them",
  "version": "2026-10-11",
  "licence": "The arithmetic is not ours to license. Reuse the formulas; the page is free and needs no attribution.",
  "method": "static description plus a worked example; the same formulas run in the page in the reader's browser, with nothing sent anywhere",
  "money": "every money figure is printed with a dollar sign and nothing is converted and no rate is attached: the figures are the caller's own prices in the caller's own currency, and the symbol is a label.",
  "rounding": {
    "money": "round2(x) is Math.round(x * 100) / 100 on the double, so a half cent goes toward positive infinity: 0.125 becomes 0.13 and -0.125 becomes -0.12, which is not 'half away from zero' and is reachable wherever a cent of a price is. Prices are fixed at the cent as they arrive and a row's gross is round2(clients x price); the day's gross is the running sum of those row figures, rounded at each step, so it is the sum of the figures the page prints rather than of longer hidden ones.",
    "minutes": "every minute figure is a whole minute. A minute input that arrives with a fraction is rounded to the nearest whole minute (Math.round, so a half goes toward positive infinity) and the endpoint says both numbers in a notice. Nothing on this page is scheduled in fractions of a minute, so no minute output ever carries a decimal.",
    "counts": "whole. clientsAsked, clientsThatFitByHands, clientsThatFitOnClock, stationsAtPeak and serviceCount are counts of things and are whole by construction. extraClientsFromReuse is the FLOOR of reusableHandsFreeMinutes / oneMoreClientMinutes and is NOT rounded: a part of a client is not a client, so 205 / 45 = 4.56 gives four, not five, and the page prints the quotient TO TWO DECIMALS on the line above the count so both are visible. That line is NOT the exact quotient and the page no longer calls it one: 205 / 45 is 4.5555... and 4.56 is it rounded (REVIEW-round4 m4-1). clientsSpareHandsHold is floored the same way, over a numerator floored at zero.",
    "percent": "handsOnSharePct is printed at the fewest decimals, one at the least, that make the printed percentage times the usable day reach the printed hands-on minutes at the whole minute. It has no upper bound: a mix that asks more of the hands than the day has really does come to more than 100%, and that is the figure the page exists to show, so it is never clipped or withheld on account of being large. It is null, and the page prints no figure at all, when the usable day is nothing. The engine carries a second, narrower discard - a share is thrown away if no precision from one decimal to six makes its product reach the printed hands-on minutes - and that one is a GUARD rather than a reachable state: one decimal already reaches its own whole minute at every usable day this page offers, because the day is at most 840 minutes and a tenth of a per cent of it moves the product by at most 0.42 of a minute, inside the half minute whole-minute rounding tolerates. test.js walks every denominator from 1 to 840 and asserts it, and the page's own sweep is asserted to produce a null share only where the usable day is nothing, so a caller may treat an empty day as the only null and should still handle the guard rather than assume the search cannot fail (REVIEW-round3 m3-2)."
  },
  "inputs": [
    {
      "name": "dayStart",
      "type": "string",
      "unit": "clock time",
      "required": true,
      "range": "05:00 to 14:00, step 15 minutes",
      "note": "The time the tech unlocks, as hh:mm on the twenty-four hour clock. The page's own control is a time field offering 05:00 to 14:00 in quarter hours and nothing else. Any string this endpoint cannot read as hh:mm - a blank, whitespace, '9am', '24:00' - is accepted and returns every minute and money figure unchanged, with finishTime null and no day sheet, and the page says so in a notice. A start time that cannot be read makes the entry incomplete."
    },
    {
      "name": "dayMinutes",
      "type": "integer",
      "unit": "minutes",
      "required": true,
      "range": "60 to 840, step 15",
      "note": "How long the tech is in the building, held as minutes so that it and breakMinutes share one unit and either can be set above the other. The page's own control is a select of the 53 quarter hours from 1 h 00 m to 14 h 00 m, whose values are these minute figures. Clamped at zero from below; a day of nothing makes the entry incomplete."
    },
    {
      "name": "breakMinutes",
      "type": "integer",
      "unit": "minutes",
      "required": true,
      "range": "0 to 180, step 5",
      "note": "The unpaid break in the day. Taken out of the day before the work is laid out, so where in the day it falls changes no figure: usableMinutes is max(0, dayMinutes - breakMinutes) and the finish time moves by exactly these minutes however they are placed. A break at or above the day leaves no usable minutes, which the endpoint returns as zero and the page prints as a notice naming both figures rather than dividing by it. THAT NOTICE IS ABOUT A DAY THAT EXISTS: where dayMinutes is itself nothing the comparison is satisfied by two zeros, so the page says that no day length was entered and says nothing about the break, because a break cannot have taken a day nobody entered (REVIEW-round7 B7-1). usableMinutes is zero in both states and the entry is incomplete in both."
    },
    {
      "name": "turnaroundMin",
      "type": "integer",
      "unit": "minutes",
      "required": true,
      "range": "0 to 60, step 5",
      "note": "Wipe-down and set-up between one client and the next, the same for every service. These are hands-on minutes: they are never hidden under a cure window, and every client in the mix costs one of them."
    },
    {
      "name": "stations",
      "type": "integer",
      "unit": "count",
      "required": true,
      "range": "1 to 6, step 1",
      "note": "Places a client can sit. A client holds a station from the moment the hands go on until her cure is done, so with one station nothing can cure while the next client is being started. A station count below one makes the entry incomplete and no schedule is run."
    },
    {
      "name": "serviceCount",
      "type": "integer",
      "unit": "count",
      "required": true,
      "range": "1 to 6, step 1",
      "note": "How many rows of the services array are live. Clamped into 1 to 6; rows past it are ignored entirely, which is why rowGross beyond it is null rather than zero."
    },
    {
      "name": "services[].serviceName",
      "type": "string",
      "unit": "none",
      "required": false,
      "range": "free text, up to 40 characters",
      "note": "The tech's own name for the service. A blank or whitespace-only name is a complete row and is labelled 'unnamed' in every table; a name is never arithmetic, so it never makes an entry incomplete."
    },
    {
      "name": "services[].clients",
      "type": "integer",
      "unit": "count",
      "required": true,
      "range": "0 to 20, step 1",
      "note": "Clients of this service in the day being planned. Clamped at zero from below. A row with no clients contributes nothing and needs no other figure."
    },
    {
      "name": "services[].handsOnMin",
      "type": "integer",
      "unit": "minutes",
      "required": true,
      "range": "0 to 240, step 5",
      "note": "The minutes the tech's hands are on one client of this service, with no gap in them. A row with clients in it and no hands-on minutes makes the entry incomplete: that is the state that filling a form from the top reaches, and no headline figure is returned for it."
    },
    {
      "name": "services[].handsFreeMin",
      "type": "integer",
      "unit": "minutes",
      "required": true,
      "range": "0 to 120, step 5",
      "note": "The cure, dry or soak window after the hands-on work on one client: the tech's hands are free and the client still holds her station. Nothing here is a true entry and not a missing one - it is what a tech who holds only her totals enters - and it gives the one-clock day, with the overlap gain at zero. It may legitimately exceed handsOnMin: a pedicure soak longer than the hands-on time is the state this calculation exists for."
    },
    {
      "name": "services[].price",
      "type": "number",
      "unit": "money",
      "required": true,
      "range": "0 to 500, step 0.01",
      "note": "What the tech charges for one client of this service, off her own list. This endpoint supplies, suggests, averages and benchmarks no price: a price of 0.00 is a complete entry, it is the value every field on the page ships with, and it returns a gross of 0.00 rather than a blank. Clamped at zero from below and fixed at the cent."
    },
    {
      "name": "serviceName",
      "type": "string",
      "unit": "none",
      "required": false,
      "range": "free text, up to 40 characters",
      "note": "ONE SERVICE, SPELLED FLAT. This field and the four below it are read ONLY when no services array is given, and they then produce exactly the state the page's own services select reaches on 1. They exist because a caller holding one line of a record - an agent, or the kit checker feeding this engine one day of a weekly book - holds five figures rather than a list of objects, and a record column cannot be mapped onto an array of objects. Nothing on the page itself writes them. serviceCount is not forced by them: with the flat spelling and no serviceCount the engine reads one row, and with a serviceCount above one the other rows are empty."
    },
    {
      "name": "clients",
      "type": "integer",
      "unit": "count",
      "required": false,
      "range": "0 to 20, step 1",
      "note": "The flat spelling of services[0].clients. Read only when no services array is given."
    },
    {
      "name": "handsOnMin",
      "type": "integer",
      "unit": "minutes",
      "required": false,
      "range": "0 to 240, step 5",
      "note": "The flat spelling of services[0].handsOnMin. Read only when no services array is given."
    },
    {
      "name": "handsFreeMin",
      "type": "integer",
      "unit": "minutes",
      "required": false,
      "range": "0 to 120, step 5",
      "note": "The flat spelling of services[0].handsFreeMin. Read only when no services array is given."
    },
    {
      "name": "price",
      "type": "number",
      "unit": "money",
      "required": false,
      "range": "0 to 500, step 0.01",
      "note": "The flat spelling of services[0].price. Read only when no services array is given, and this endpoint supplies no price in either spelling."
    }
  ],
  "outputs": [
    {
      "name": "usableMinutes",
      "unit": "minutes",
      "formula": "max(0, dayMinutes - breakMinutes); the minutes of the day the work is laid out in"
    },
    {
      "name": "handsOnMinutes",
      "unit": "minutes",
      "formula": "the sum over live rows of clients x handsOnMin"
    },
    {
      "name": "turnaroundMinutes",
      "unit": "minutes",
      "formula": "the sum over live rows of clients x turnaroundMin, which is clientsAsked x turnaroundMin"
    },
    {
      "name": "handsMinutes",
      "unit": "minutes",
      "formula": "handsOnMinutes + turnaroundMinutes; what the mix asks of the hands, and the only minutes that can never be hidden"
    },
    {
      "name": "handsFreeMinutes",
      "unit": "minutes",
      "formula": "the sum over live rows of clients x handsFreeMin; every cure minute in the mix, hidden or not"
    },
    {
      "name": "minutesSpareAgainstHands",
      "unit": "minutes",
      "formula": "usableMinutes - handsMinutes; SIGNED, and legitimately negative: a mix that asks more of the hands than the day has returns a negative figure here and the page prints it as minutes over"
    },
    {
      "name": "clientsAsked",
      "unit": "count",
      "formula": "the sum over live rows of clients"
    },
    {
      "name": "clientsThatFitByHands",
      "unit": "count",
      "formula": "clients taken in the order the rows are entered, each costing handsOnMin + turnaroundMin, until the next one would pass usableMinutes; the walk stops at the first client that does not fit and never resumes, so it is a count of a contiguous day and not a packing. Never negative, never above clientsAsked"
    },
    {
      "name": "clientsThatFitOnClock",
      "unit": "count",
      "formula": "the same walk through the schedule below, where a client fits only if her hands come off inside usableMinutes AND her cure finishes inside it. Never above clientsThatFitByHands, because a client's clock cost is her hands cost plus any wait for a station. With no station at all no schedule is run and this is zero"
    },
    {
      "name": "waitedForStationMinutes",
      "unit": "minutes",
      "formula": "the sum over the schedule of the minutes the tech waited for a station to free before starting a client, which happens whenever every station is held by a client still curing"
    },
    {
      "name": "trailingCureMinutes",
      "unit": "minutes",
      "formula": "max(0, the last cure to finish - the moment the hands came off the last client); the cure minutes at the end of the day with no later client to hide inside. 'The moment the hands came off' a client means the END OF HER TURNAROUND, the moment the station is wiped and the hands are free for the next client, which is the convention everywhere in this endpoint and the moment the page's day sheet prints in its own column. With no station at all no schedule is run and this is zero"
    },
    {
      "name": "unhideableHandsFreeMinutes",
      "unit": "minutes",
      "formula": "clockMinutes - handsMinutes, which is identically waitedForStationMinutes + trailingCureMinutes; the cure minutes the day had to find room for. Never negative and never above handsFreeMinutes, because every idle minute falls inside at least one client's cure window"
    },
    {
      "name": "reusableHandsFreeMinutes",
      "unit": "minutes",
      "formula": "handsFreeMinutes - unhideableHandsFreeMinutes: the cure minutes of the mix with the cure minutes the day had to find room for taken out. Never negative. IT IS A NETTING OF TWO DAY FIGURES AND NOT A PER-MINUTE COUNT, and this is the third round to correct the gloss on it: two clients can be curing in one and the same minute, so a mix can carry a cure total the day has no wall-clock room to hold minute for minute, and no per-minute account of this figure balances. Measured, on the five-pedicure day of REVIEW-round3 B3-1 (2 stations, turnaround 10, one row of 5 clients at 20 hands-on minutes and a 120-minute soak, an 8-hour day with a 30-minute break, 450 usable minutes): the cure minutes that fall inside some pair of hands or some turnaround come to 170 and this figure is 330. The 160 between them are minutes in which the tech waited for a station while two clients cured at once - the day pays for such a minute once and the mix counts it twice. So the gloss 'the minutes that passed while the hands were on another client or on a turnaround' is wrong as a definition (REVIEW-round3 B3-1 found it wrong in 179 of the 1,789 swept states whose day sheet is not truncated, by 670 minutes at the widest) and before it a gloss reading 'on somebody else' was wrong the same way, because on one station with a cure shorter than the wipe-down every cure minute hides inside that client's own turnaround (REVIEW-round2 B2-1). The per-minute sentence holds only where the day waited for nothing, which is the reuseBound 'none' state the page prints it in"
    },
    {
      "name": "reuseBound",
      "unit": "label",
      "formula": "which of the two bounds put cure minutes on the clock. THE FOUR LABELS ARE TESTED IN THIS ORDER, and the order is part of the answer because two of the cases overlap (REVIEW-round3 m3-1): first 'neither', wherever the mix asks for one client or none, so there is no other client to hide a cure inside at all; then 'none', wherever unhideableHandsFreeMinutes is zero, so the day found room for every cure minute it had; then 'stations', wherever the minutes waited for a station exceed the cure left trailing at the end (there was no free place to put a client); otherwise 'hands', where the trailing cure is at least the minutes waited and there was no later client to hide it inside. A one-client mix whose single cure trails past the hands satisfies the 'hands' description too and still answers 'neither', because 'neither' is tested first; a one-client mix with no cure window satisfies both 'neither' and 'none' and answers 'neither' for the same reason"
    },
    {
      "name": "clockMinutes",
      "unit": "minutes",
      "formula": "the schedule's own span: max(the moment the hands came off the last client, the last cure to finish). Identically handsMinutes + unhideableHandsFreeMinutes. With no station at all no schedule is run and this is handsMinutes"
    },
    {
      "name": "stationsAtPeak",
      "unit": "count",
      "formula": "the largest number of stations held at once over the schedule, counting the client under the hands and every client still curing. THE COUNT IS TAKEN ONCE PER CLIENT, AS SHE IS PLACED: it is her, plus every client placed before her whose cure has not finished by the minute her own hands go on, and the figure is the largest of those per-client counts. A client who begins in the very same minute as the client before her is therefore NOT inside that earlier client's count, which is the clause REVIEW-round6 B6-1 found missing: an agent that read the first-minute rule below as a count over every client at every minute of the day returned three on a two-station day, at 1 of its 4,536 states, where the schedule's own per-client count is two. A client counts as holding a station FROM THE MOMENT HER HANDS GO ON, so a client whose hands-on minutes and cure window are both nothing still holds one at that moment: the figure is at least one wherever the mix has a client in it and the day has a station. Every other part of the schedule reads a station's hold as half-open - the next client may start at the exact minute the client before her finishes curing, which is why no two clients hold one station at the same moment - and this is the one place the starting client is counted at her own first minute. Never above stations, and the once-per-client count is why that and the first-minute rule do not pull against each other: a mix of clients who each hold a station for no measurable time, all of them starting in one and the same minute, counts past the stations that exist only under a reading that counts them all at that minute, and this calculation does not take the count that way. Such a mix is in any case an INCOMPLETE entry - a row with clients in it and no hands-on minutes is missing a field this calculation needs - and the page prints no figure of any kind for an incomplete entry, so the one state in which the two readings differ is a state the page answers with a notice and not with this count. With no station at all no schedule is run and this is zero"
    },
    {
      "name": "oneMoreClientMinutes",
      "unit": "minutes",
      "formula": "the commonest service's handsOnMin + turnaroundMin, where the commonest service is the live row with the most clients and the first such row where two rows tie. Zero when no row has a client in it"
    },
    {
      "name": "extraClientsFromReuse",
      "unit": "count",
      "formula": "floor(reusableHandsFreeMinutes / oneMoreClientMinutes), or zero when oneMoreClientMinutes is zero; the hidden cure minutes expressed in whole clients of the commonest service. Never negative. READ THIS FIGURE BESIDE clientsSpareHandsHold AND NOT ON ITS OWN: it is a cure total measured in client-sized units and is NOT a count of clients that can be added. The minutes it divides are hands-FREE minutes, hidden precisely because the hands were busy through them, while one more client costs hands-ON minutes plus a turnaround, and nothing in this calculation performs that exchange. On the worked example below it is 4 while minutesSpareAgainstHands is 10 and one more client costs 45, so the four clients it names would need 180 minutes of hands the day does not have; over the page's own 4,534-state sweep 4,170 of the 4,246 states that print a positive figure here print one larger than the spare hands minutes can hold, and 3,878 of those are days whose hands are already over the usable day. A caller that renders this figure must render clientsSpareHandsHold with it or say the same thing in its own words (REVIEW-round4 B4-1)"
    },
    {
      "name": "clientsSpareHandsHold",
      "unit": "count",
      "formula": "floor(max(0, minutesSpareAgainstHands) / oneMoreClientMinutes), or zero when oneMoreClientMinutes is zero; the clients the minutes left over against the hands would actually take, at the cost of one more client of the commonest service. Floored at zero because a day already over its hands has no room at all. Neither this figure nor extraClientsFromReuse bounds the other: either can be the larger, and the page prints a sentence carrying the ordering wherever extraClientsFromReuse is the larger of the two (REVIEW-round4 B4-1)"
    },
    {
      "name": "dayGross",
      "unit": "money",
      "formula": "the running sum, rounded at each step, of round2(clients x price) over the live rows; the caller's own prices times the caller's own counts and nothing else. It is the gross of the MIX AS ENTERED: it counts every client in the mix, including the clients clientsThatFitOnClock excludes, so wherever clientsThatFitOnClock is below clientsAsked this figure is larger than the gross of the day that fits. The page names it for the mix rather than for the day for that reason, and prints a sentence saying so in the state it happens in (REVIEW-round2 B2-3)"
    },
    {
      "name": "finishTime",
      "unit": "clock time",
      "formula": "hh:mm of dayStart + breakMinutes + clockMinutes, taken modulo twenty-four hours; null when dayStart could not be read"
    },
    {
      "name": "finishCrossesMidnight",
      "unit": "boolean",
      "formula": "true when dayStart + breakMinutes + clockMinutes reaches 1440 minutes or more, so the finish time printed belongs to the next day; false when dayStart could not be read. The break is counted in this flag and in no clock time on the day sheet, so a day pushed past midnight by the break alone flags here while no sheet line has wrapped; the implication runs one way, from a wrapped sheet line to this flag, and never back (REVIEW-round4 B4-4)"
    },
    {
      "name": "handsOnSharePct",
      "unit": "percent",
      "formula": "handsOnMinutes / usableMinutes x 100, printed at the fewest decimals (one at the least) whose product with usableMinutes reaches handsOnMinutes at the whole minute. No upper bound. null when usableMinutes is zero"
    },
    {
      "name": "rowGross1",
      "unit": "money",
      "formula": "round2(clients x price) for the first service row, or null when the row is past serviceCount"
    },
    {
      "name": "rowGross2",
      "unit": "money",
      "formula": "round2(clients x price) for the second service row, or null when the row is past serviceCount"
    },
    {
      "name": "rowGross3",
      "unit": "money",
      "formula": "round2(clients x price) for the third service row, or null when the row is past serviceCount"
    },
    {
      "name": "rowGross4",
      "unit": "money",
      "formula": "round2(clients x price) for the fourth service row, or null when the row is past serviceCount"
    },
    {
      "name": "rowGross5",
      "unit": "money",
      "formula": "round2(clients x price) for the fifth service row, or null when the row is past serviceCount"
    },
    {
      "name": "rowGross6",
      "unit": "money",
      "formula": "round2(clients x price) for the sixth service row, or null when the row is past serviceCount"
    }
  ],
  "formula": [
    "an entry is COMPLETE when dayStart reads as hh:mm AND dayMinutes is above zero AND stations is at least one AND every live row with clients above zero has handsOnMin above zero. A price is never a condition: a price of 0.00 is a complete entry and is what the page ships with. Until the entry is complete the page prints no number in any headline row, prints no table and prints no day sheet, and says in the glance which of those four things is missing; this endpoint still returns every figure, because a caller that supplied the state knows what it supplied.",
    "every numeric input is clamped at zero from below, then every minute and count input is rounded to the nearest whole number and every price to the nearest cent, and only those normalised figures are used below, so no line is worked from a longer number than the one the page prints.",
    "serviceCount is clamped into 1 to 6. A row past it is read by nothing.",
    "the flat one-service spelling: when no services array is given and any of serviceName, clients, handsOnMin, handsFreeMin or price is, those five are read as services[0] and nothing else is. It is the same state as a one-row mix and returns identical figures; test.js asserts that over 600 swept states.",
    "per live row: handsOnTotal = clients x handsOnMin, turnaroundTotal = clients x turnaroundMin, handsFreeTotal = clients x handsFreeMin, rowGross = round2(clients x price).",
    "the day's hands: handsOnMinutes and turnaroundMinutes are those columns footed, handsMinutes is their sum, and handsFreeMinutes is the cure column footed.",
    "the usable day: usableMinutes = max(0, dayMinutes - breakMinutes), and minutesSpareAgainstHands = usableMinutes - handsMinutes, signed.",
    "the walk against the hands: clients in the order the rows are entered, each costing handsOnMin + turnaroundMin against a budget of usableMinutes, stopping for good at the first client that does not fit. The count is clientsThatFitByHands.",
    "THE SCHEDULE, which is this calculation's distinct part. Clients are taken in the order the rows are entered. A client holds a station from the moment the hands go on until her cure is done. The tech starts a client at the earliest moment, at or after the hands came off the client before her, at which fewer than `stations` clients are still holding a station; the minutes between those two moments are a wait and are added to waitedForStationMinutes. The hands then run for handsOnMin, the turnaround for turnaroundMin after that, and the cure runs for handsFreeMin from the END OF HER OWN HANDS-ON TIME, which is before her turnaround and not after it - the lamp goes on while the tech is still wiping the station down. That anchor is the one place in this endpoint where 'the hands coming off' is NOT the end of the turnaround, and the step below that defines the phrase exempts it by name. The station given is the lowest-numbered one whose last client's cure has finished.",
    "a client fits on the clock only if her turnaround ends inside usableMinutes and her cure finishes inside it; the count stops for good at the first client that does not, and is clientsThatFitOnClock. The count is a PREFIX of the schedule, so a client whose own window lands inside the day is still outside the count when she sits after one that did not: the clients the count excludes are the schedule's lines from clientsThatFitOnClock onward, which is the test the page's day sheet marks its own lines with (REVIEW-round2 B2-2). An agent reproducing the sheet marks the same lines by that index and not by the per-client fit.",
    "clockMinutes is the span of that schedule: max(the moment the hands came off the last client, the last cure to finish). trailingCureMinutes is the part of it after the hands came off for the last time. 'The moment the hands came off' a client means the end of her turnaround throughout this endpoint - the wipe-down and set-up are inside it - with the single exception of the cure anchor in the schedule step above, which is the end of hands-on alone. The convention is why the worked example's trailingCureMinutes is ten rather than the twenty a reading that left the turnaround out would give, and why the page's day sheet names that column for the wipe-down rather than for the hands alone.",
    "unhideableHandsFreeMinutes = clockMinutes - handsMinutes = waitedForStationMinutes + trailingCureMinutes. reusableHandsFreeMinutes = handsFreeMinutes - unhideableHandsFreeMinutes. The two foot to handsFreeMinutes by that subtraction, and THAT IS ALL THE SPLIT IS: it is not a partition of the mix's cure minutes into minutes spent one way and minutes spent another. Two clients can cure in one and the same minute, so the mix's cure total can be larger than the wall-clock minutes the day has to put cure in, and a reader who tries to tick off each cure minute as hidden or on the clock will not balance the figure (REVIEW-round3 B3-1, which measured a gap of 160 minutes on a five-pedicure day). An agent reproducing the page computes the two by this subtraction and describes them as a netting.",
    "the per-row hidden figure the page prints in its table is the day's reusableHandsFreeMinutes handed out to the rows in the order they are entered, each row taking the smaller of its own cure total and what is left. It is an allocation of one day figure, not a second calculation, and it foots to that figure exactly; the page says so where it prints it.",
    "the day sheet the page prints beside these figures is the schedule above, line by line, and THIS ENDPOINT RETURNS THE SCHEDULE ENTIRE. The page's own sheet prints the first twenty-four lines of it and its caption names how many clients it left off the end; that cap is a limit of a printed page and not of the calculation, and the caption's two figures - the lines printed and the ones left off - are page figures carried by no output here. A caller that wants them takes the count of the schedule it is given, caps it at twenty-four and subtracts (REVIEW-round6 m6-2). ONE CONSEQUENCE OF THAT CAP, AND THE PAGE SAYS IT IN THE SAME CAPTION: clientsThatFitOnClock IS NOT READABLE OFF THE PRINTED SHEET WHENEVER IT REACHES THE CAP. The page marks every schedule line past clientsThatFitOnClock, so where that count is smaller than the lines printed the marked lines locate it; but a complete mix can hold more clients on the clock than the sheet prints - six rows of twenty clients at five hands-on minutes on a fourteen-hour day holds 120 and prints 24, with not one line marked - and there the count is only in the figure itself. A caller reading the printed sheet rather than this endpoint must take the count from clientsThatFitOnClock and not from where the markers stop (P-014 closing round, F13).",
    "stationsAtPeak is the largest count of stations held at once over the schedule, which is at most stations because the tech waits rather than oversubscribing them and because THE COUNT IS TAKEN ONCE PER CLIENT AS SHE IS PLACED - her, plus every client placed before her whose cure has not finished by the minute her hands go on - so a client beginning in the very same minute as another is not inside that other client's count. The client being started counts as holding a station at her own first minute, so a client with no hands-on minutes and no cure still holds one and the figure is at least one wherever the mix has a client and the day has a station; everywhere else a hold is half-open, so the next client may begin at the exact minute the client before her finishes curing. A mix carrying a client who holds a station for no measurable time is an incomplete entry, because her row has clients in it and no hands-on minutes, and the page prints no figure for an incomplete entry (REVIEW-round6 B6-1).",
    "the extra clients: oneMoreClientMinutes is the commonest row's handsOnMin + turnaroundMin, and extraClientsFromReuse is floor(reusableHandsFreeMinutes / oneMoreClientMinutes). It is the hidden cure total measured in client-sized units and NOT a count of clients that can be added, because its numerator is hands-free minutes and its denominator is a cost to the hands. clientsSpareHandsHold = floor(max(0, minutesSpareAgainstHands) / oneMoreClientMinutes) is the count the hands can actually take, and the two are printed together with a sentence between them wherever the first is the larger (REVIEW-round4 B4-1).",
    "the gross: dayGross is the running sum, rounded at each step, of the row grosses.",
    "the finish: finishTime is hh:mm of dayStart + breakMinutes + clockMinutes modulo twenty-four hours, and finishCrossesMidnight says whether that sum reached a day. THE BREAK IS IN THE FINISH AND IS IN NO OTHER CLOCK TIME THIS CALCULATION RETURNS: the schedule above lays the clients out from dayStart with no break in it at all, so every time on the day sheet is dayStart plus an offset of the schedule's own, and the gap between the last time on the sheet and the finish is exactly breakMinutes. Two consequences a caller must carry: the sheet's times are the sheet for a break taken once the last client is done, and any other placement moves every line from there on later by breakMinutes; and a day pushed past midnight BY THE BREAK sets finishCrossesMidnight while no line of the sheet has wrapped (38 of the page's own swept states). The page says both in the sheet's caption and names the break in the finish row (REVIEW-round4 B4-3, B4-4).",
    "the share: handsOnSharePct is handsOnMinutes / usableMinutes x 100 at the fewest decimals that reach handsOnMinutes, with no ceiling."
  ],
  "identities": [
    "handsMinutes equals handsOnMinutes + turnaroundMinutes",
    "clockMinutes equals handsMinutes + unhideableHandsFreeMinutes",
    "unhideableHandsFreeMinutes equals waitedForStationMinutes + trailingCureMinutes",
    "reusableHandsFreeMinutes + unhideableHandsFreeMinutes equals handsFreeMinutes, and both are at or above zero",
    "clientsThatFitOnClock is at or below clientsThatFitByHands, which is at or below clientsAsked",
    "stationsAtPeak is at or below stations",
    "minutesSpareAgainstHands equals usableMinutes - handsMinutes",
    "the row grosses foot to dayGross, and the per-row hidden figures foot to reusableHandsFreeMinutes",
    "extraClientsFromReuse x oneMoreClientMinutes is at or below reusableHandsFreeMinutes, and one more of them would pass it, wherever oneMoreClientMinutes is above zero",
    "clientsSpareHandsHold x oneMoreClientMinutes is at or below max(0, minutesSpareAgainstHands), and one more of them would pass it, wherever oneMoreClientMinutes is above zero. Nothing orders the two client figures: extraClientsFromReuse is above clientsSpareHandsHold in most reachable states, which is the whole of REVIEW-round4 B4-1",
    "round(usableMinutes x handsOnSharePct / 100) equals handsOnMinutes, wherever handsOnSharePct is not null",
    "no two clients on the day sheet hold one station at the same moment, and no two clients are under the hands at the same moment"
  ],
  "example": {
    "note": "the day the page ships filled in, so this endpoint and the page answer with the same figures. Every price is 0.00 because no price on that page is ever one the page chose.",
    "inputs": {
      "dayStart": "09:00",
      "dayMinutes": 480,
      "breakMinutes": 30,
      "turnaroundMin": 10,
      "stations": 2,
      "serviceCount": 3,
      "services": [
        {
          "serviceName": "Gel manicure",
          "clients": 4,
          "handsOnMin": 35,
          "handsFreeMin": 10,
          "price": 0
        },
        {
          "serviceName": "Pedicure",
          "clients": 3,
          "handsOnMin": 30,
          "handsFreeMin": 45,
          "price": 0
        },
        {
          "serviceName": "Full set",
          "clients": 2,
          "handsOnMin": 60,
          "handsFreeMin": 20,
          "price": 0
        }
      ]
    },
    "outputs": {
      "usableMinutes": 450,
      "handsOnMinutes": 350,
      "turnaroundMinutes": 90,
      "handsMinutes": 440,
      "handsFreeMinutes": 215,
      "minutesSpareAgainstHands": 10,
      "clientsAsked": 9,
      "clientsThatFitByHands": 9,
      "clientsThatFitOnClock": 9,
      "waitedForStationMinutes": 0,
      "trailingCureMinutes": 10,
      "unhideableHandsFreeMinutes": 10,
      "reusableHandsFreeMinutes": 205,
      "reuseBound": "hands",
      "clockMinutes": 450,
      "stationsAtPeak": 2,
      "oneMoreClientMinutes": 45,
      "extraClientsFromReuse": 4,
      "clientsSpareHandsHold": 0,
      "dayGross": 0,
      "finishTime": "17:00",
      "finishCrossesMidnight": false,
      "handsOnSharePct": 77.8,
      "rowGross1": 0,
      "rowGross2": 0,
      "rowGross3": 0,
      "rowGross4": null,
      "rowGross5": null,
      "rowGross6": null
    }
  },
  "not_covered": [
    "anything an appointment book does. There is no state here, no reminder, no deposit, no client record and no history: one day in, one day's arithmetic out, nothing kept. A caller that wants a booking system wants a booking system.",
    "stock of any kind: no on-hand count, no reorder point, no reorder quantity. That arithmetic belongs to a different calculation and is deliberately not duplicated here.",
    "what product a service uses or what one set costs in product. That is a unit-cost calculation and this endpoint does not do it.",
    "sanitation, disinfection, licensing or any state-board record. None of it is here and none of it is implied.",
    "any client's name, health, allergy, condition or medication. There is no field for it, by design, because a record kept on a desk is the wrong place for it.",
    "whether a day is good, full, profitable or improvable; what any service should cost; what any tech earns or should earn; and any comparison with another tech, shop, book or application.",
    "whether anybody turns up, how long a set actually takes on the day, whether a cure runs long, and where in the day the break is taken. The schedule is the mix the caller supplied laid out end to end, not a prediction.",
    "overtime, pay, tax, tips, card fees and anything else that is not minutes, clients or the caller's own price."
  ],
  "no_advice": "This endpoint returns arithmetic on figures supplied to it. It recommends no price, no wage, no service mix, no day length and no decision of any kind, it benchmarks nothing against anybody, and it returns no forecast of what a day will take."
}
