{
  "name": "Hunt club season dues, by membership class",
  "page": "https://otherworldlabs.com/split-cost-of-hunting-lease/",
  "endpoint": "https://otherworldlabs.com/split-cost-of-hunting-lease/api.json",
  "question": "For one hunt club season, what dues does each membership class have to pay for the season to fund itself, what does that collect against what the club plans to spend, and what did one day afield cost?",
  "answers": "one season at a time; the page keeps no history and this endpoint stores nothing",
  "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",
  "currency": "whatever the figures are entered in; the page does no conversion and attaches no currency to a figure",
  "direction": "The calculation runs forward, not backward: it does not apportion a cost already paid among known payers. It takes a season budget of seven lines, reduces it by one variable income line (the guest-day fee for the guest days expected), divides what is left across weighted membership classes, and only then holds what that collects beside what the club plans to spend. The second direction, the cost of a day afield, divides the same budget by the days the classes logged.",
  "rounding": {
    "counts": "whole; a figure outside the range the control offers is brought to the nearer end of the range first, and a fraction is then rounded to the nearest whole by the same rule as money below",
    "money": "two decimals by round2(x) = sign(x) x (Math.round(Math.abs(x) * 100) / 100) on the double, which is half AWAY FROM ZERO in both directions: -0.005 comes out as -0.01, where Math.round(-0.005 * 100) / 100 would give -0 and so -0.00. Negative money is reachable here, because shortOrOver and every difference in the last-season column are signed, which is why the sign is taken out before the rounding and put back after. round2 is applied where each figure is defined and again to every sum, difference and quotient built out of figures already rounded, so no line is worked from a longer number than the one printed",
    "percentages": "collectionPct is the only percentage returned. It is the quotient at one decimal by the same half-away-from-zero rule, and the page prints the figure it returns rather than the exact quotient, so a reader who recomputes from the figure shown gets the figure shown",
    "shares": "a class's shares and its weight are rounded to two decimals, and the shares on the roll are the rounded per-class figures added and rounded again"
  },
  "form": "This endpoint takes the figures it is given. The page in front of a reader has one further rule, which is not arithmetic and is not described by the fields below: it ships as a worked example, and the moment the reader types a figure of their own anywhere in the form, every box they have not typed in is cleared and read as blank, so an example figure in a section they never scrolled to cannot reach their answer. An API caller sends what it means to send and this rule does not apply to it (S11, AU-1 and AU-2).",
  "clamping": "Every figure is brought inside the range the page's own control offers before anything is computed, and the ranges in `inputs` below are those controls' own min, max and step. There is no state this endpoint describes that the page's controls cannot reach.",
  "inputs": [
    {
      "name": "acres",
      "type": "number",
      "unit": "acres",
      "required": false,
      "range": "0 to 20000, whole acres",
      "note": "The acreage the club has the run of, as a bare figure. It is divided by the members on the roll and nothing else is done with it and nothing is said about it."
    },
    {
      "name": "rentPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: what the club pays the landowner for the season, as the club's own paperwork states it. Blank reads as nothing."
    },
    {
      "name": "coverPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: the premium as invoiced. It is a typed number and this endpoint says nothing else about it. Blank reads as nothing."
    },
    {
      "name": "plotsPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: seed, fertiliser, fuel and any contract work. Blank reads as nothing."
    },
    {
      "name": "standsPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: what the club plans to buy or replace. Blank reads as nothing."
    },
    {
      "name": "gatesPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: gates, locks, gravel, culverts, mowing. Blank reads as nothing."
    },
    {
      "name": "campPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: power, propane, water, repairs. Blank reads as nothing."
    },
    {
      "name": "otherPlanned",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "Planned this season: whatever else the season carries, and the one box a club holding only a season total types that total into; it is summed like every other line. Blank reads as nothing."
    },
    {
      "name": "rentActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "coverActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "plotsActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "standsActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "gatesActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "campActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "otherActual",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 100000, to the cent",
      "note": "What this line actually cost last season. Blank or zero means the club does not hold it, and the difference for that line is returned as null rather than as a figure equal to the plan."
    },
    {
      "name": "guestFee",
      "type": "number",
      "unit": "dollars",
      "required": false,
      "range": "0 to 500, to the cent",
      "note": "What the club charges for one guest day. The endpoint chooses no fee."
    },
    {
      "name": "guestDaysExpected",
      "type": "integer",
      "unit": "guest days",
      "required": false,
      "range": "0 to 500, whole days",
      "note": "The guest days the club expects over the season. Multiplied by guestFee this is the income that reduces the budget before the dues are set, which is the one variable income line in the calculation."
    },
    {
      "name": "guestDaysLogged",
      "type": "integer",
      "unit": "guest days",
      "required": false,
      "range": "0 to 500, whole days",
      "note": "The guest days in the guest book so far. Multiplied by guestFee this is income the club has, and it joins what the club collects. It may legitimately run above guestDaysExpected; that is the surplus case."
    },
    {
      "name": "classes",
      "type": "array of 4 objects",
      "unit": "membership classes",
      "required": true,
      "range": "exactly 4 entries; a club with fewer types a member count of 0 in the rest",
      "note": "The four membership classes, as classes and counts. No member is named: the array carries no field for a person, the youth class is a count, and nothing identifying anybody is read or returned."
    },
    {
      "name": "classes[].name",
      "type": "string",
      "unit": "text",
      "required": false,
      "range": "up to 40 characters; blank and whitespace-only are both allowed",
      "note": "What the club calls the class. A blank or whitespace-only name is labelled Class 1 to Class 4 by position. The name is never parsed for a figure."
    },
    {
      "name": "classes[].count",
      "type": "integer",
      "unit": "members",
      "required": false,
      "range": "0 to 200, whole members",
      "note": "Members on the roll in that class. A fraction is rounded to the nearest whole before anything is computed."
    },
    {
      "name": "classes[].weight",
      "type": "number",
      "unit": "shares a member",
      "required": false,
      "range": "0 to 3, in steps of 0.05, rounded to two decimals",
      "note": "The share weight the club's own bylaws or vote give that class. The endpoint suggests none. While exactly one class carries members, the dues a member comes out at the same figure to within the cent the two-stage rounding leaves, whatever non-zero weight sits in the row: duesPerShare is rounded to the cent before it is multiplied by the weight, so at some weights the product lands a cent or two either side of netToRaise / that class's member count (review round 3, R3-M4)."
    },
    {
      "name": "classes[].duesVoted",
      "type": "number",
      "unit": "dollars a member",
      "required": false,
      "range": "0 to 5000, to the cent",
      "note": "A dues figure the club has already voted for that class. Blank or zero means none is voted, and what the class pays is then the figure the budget requires."
    },
    {
      "name": "classes[].daysAfield",
      "type": "integer",
      "unit": "hunter-days",
      "required": false,
      "range": "0 to 5000, whole days",
      "note": "Days afield logged by that class, off the club's sign-in sheet. A class that logs none contributes nothing to the hunter-day denominator, and nobody is labelled a hunter or a non-hunter anywhere."
    },
    {
      "name": "lines",
      "type": "array of objects, optional",
      "unit": "ledger entries",
      "required": false,
      "range": "any length; omitted by the page itself",
      "note": "The ledger the workbook sold beside this page keeps. The page's own form types the seven lines and the standing figures directly and sends no lines[]; a workbook fill arrives as lines[] and is folded into exactly the same fields before anything is computed, which is how the workbook and this endpoint are checked against each other at the cent. Each entry is {line, basis, amount, days, classLabel}: a line naming one of the seven budget keys or labels adds its amount to that line's planned column, or to the last-season column when basis names last season; line 'Guest day' adds its days to the expected or the logged count; lines 'Guest-day fee' and 'Acres on the ground' set those standing figures; line 'Day afield' adds days to the class its classLabel names; lines 'Members on the roll', 'Share weight' and 'Dues voted' set that class's figure. Every total reachable through lines[] is reachable by typing the same total into the controls the page offers."
    }
  ],
  "outputs": [
    {
      "name": "plannedSpend",
      "unit": "dollars",
      "formula": "sum over the seven planned lines, each rounded to the cent first, and the sum rounded again"
    },
    {
      "name": "actualSpend",
      "unit": "dollars",
      "formula": "sum over the seven last-season lines, the same way"
    },
    {
      "name": "varianceTotal",
      "unit": "dollars, signed, or null",
      "formula": "sum of the per-line differences that are not null; null while no line carries a last-season figure, because a difference against a column nobody filled is not a difference"
    },
    {
      "name": "linesWithActual",
      "unit": "lines",
      "formula": "how many of the seven lines carry a last-season figure above nothing"
    },
    {
      "name": "guestIncomeExpected",
      "unit": "dollars",
      "formula": "guestFee x guestDaysExpected",
      "note": "The figure is the product above whatever the two factors are. The page prints it as a figure only while the fee is typed, and dashes it in the glance, the season lines beneath the dues table and the working together while the fee box is empty, naming the empty box: a price that is not on the page cannot make an income out of a count of days (review round 3, R3-B1). With a fee typed and a count of nothing the product is the nothing it really is, and that state is an incomplete entry under waiting rather than a printed 0.00."
    },
    {
      "name": "guestIncomeLogged",
      "unit": "dollars",
      "formula": "guestFee x guestDaysLogged",
      "note": "The figure is the product above whatever the two factors are. The page prints it as a figure only while the fee is typed, and dashes it in the glance, the season lines beneath the dues table and the working together while the fee box is empty, naming the empty box: a price that is not on the page cannot make an income out of a count of days (review round 3, R3-B1). With a fee typed and a count of nothing the product is the nothing it really is, and that state is an incomplete entry under waiting rather than a printed 0.00."
    },
    {
      "name": "guestOverPlan",
      "unit": "dollars",
      "formula": "max(0, guestIncomeExpected - plannedSpend): the amount by which the guest days expected would cover the whole plan, which is why netToRaise never goes below nothing"
    },
    {
      "name": "netToRaise",
      "unit": "dollars",
      "formula": "max(0, plannedSpend - guestIncomeExpected)"
    },
    {
      "name": "membersTotal",
      "unit": "members",
      "formula": "sum over the four classes of count"
    },
    {
      "name": "totalShares",
      "unit": "shares",
      "formula": "sum over the four classes of round2(count x effectiveWeight), where effectiveWeight is the weight supplied for that class except in the one state oneShareAMember names below, in which the single class on the roll takes an effective weight of 1 and the shares on the roll are that class's members"
    },
    {
      "name": "oneShareAMember",
      "unit": "true or false",
      "formula": "true when exactly one of the four classes carries a member count above nothing AND the shares as supplied, sum over the four classes of round2(count x weight), come to nothing - that is, when the one class on the roll has no weight. A weight is a ratio between classes, so with one class on the roll there is no second class for it to be a ratio to: the effective weight of that class is 1, one share is one member, and duesPerShare is netToRaise / that class's members, which is the figure the weight cancels to at any non-zero weight, to within the cent the two-stage rounding leaves (R3-M4: duesPerShare is rounded to the cent before the weight multiplies it). With two or more classes on the roll and no weight anywhere it is false and no dues figure is computed, because the ratio between those classes is the club's to vote and this arithmetic will not assume one (review round 2, R2-B1)"
    },
    {
      "name": "duesPerShare",
      "unit": "dollars a share, or null",
      "formula": "netToRaise / totalShares, to the cent; null when totalShares is nothing, which is any state where no class carries both a member count and a non-zero weight and oneShareAMember is false"
    },
    {
      "name": "duesRequiredTotal",
      "unit": "dollars, or null",
      "formula": "sum over the four classes of classTotal where the figure the budget requires is used; null when duesPerShare is null"
    },
    {
      "name": "duesCollected",
      "unit": "dollars, or null",
      "formula": "sum over the four classes of classTotal, where a class with a voted figure pays the voted figure and a class without pays the figure the budget requires; null when any class carrying a member count has no dues figure of either kind - no voted figure and no duesRequired, which is duesPerShare being null - because the collection cannot be added out of a figure that does not exist. The page's dues table prints a dash in that class's row, and a column that foots has to foot out of what is printed in it (review round 2, R2-B2)"
    },
    {
      "name": "collects",
      "unit": "dollars, or null",
      "formula": "duesCollected + guestIncomeLogged; null when duesCollected is null"
    },
    {
      "name": "shortOrOver",
      "unit": "dollars, signed, or null",
      "formula": "collects - plannedSpend. Negative is short of plan and positive is over it; this is the one figure here that is legitimately below nothing. null when collects is null"
    },
    {
      "name": "collectionPct",
      "unit": "percent, or null",
      "formula": "collects / plannedSpend x 100 at one decimal; bounded below at nothing and unbounded above, because a club may collect more than it plans to spend. null while nothing is planned to be spent, and null while collects is null"
    },
    {
      "name": "hunterDays",
      "unit": "hunter-days",
      "formula": "sum over the four classes of daysAfield"
    },
    {
      "name": "costPerHunterDay",
      "unit": "dollars a hunter-day, or null",
      "formula": "plannedSpend / hunterDays, to the cent; null while no day afield is logged"
    },
    {
      "name": "acresPerMember",
      "unit": "acres a member, or null",
      "formula": "acres / membersTotal at two decimals; null while no member is on the roll AND null while no acreage is typed, because a club that has not typed its acreage does not hold 0.00 acres a member (review round 1, R1-B3: the sentence this endpoint carried before described a rule that printed a figure out of an empty box, and the page was changed to match this one rather than the other way about)"
    },
    {
      "name": "waiting",
      "unit": "true or false",
      "formula": "true while the entry is incomplete: no member count anywhere, or no budget line anywhere, or a guest-day fee typed with no guest day expected or logged beside it, or a guest day expected or logged with no guest-day fee typed beside it. The last two are the two halves of one product and are read the same way round (review round 3, R3-B1): a guest-day income is a price for a count of days, the count can honestly be nothing while the price cannot be nothing with a guest day in the book, so a count on its own does not make an income figure and the entry is incomplete until the fee is typed or the counts are cleared. Until that round the condition was an OR in the other direction and the page printed a guest-day income of 0.00, and a whole season computed from it, for a club that had typed its guest book and not its fee. While it is true the page prints no headline figure at all, says in the glance what it is waiting on, and withholds the same figures everywhere else they would print: the dues a member and what the class pays in the dues table, the money cell of its footing, and all four of the season lines beneath it (review round 4, R4-B1: the guest-day income logged, what the club collects, what it plans to spend and what it is short or over left the dues table for a two-column block of their own, because the table's column header 'What the class pays' was false of every one of them), and the working rows for the guest-day income, the net to raise, the dues a share, the dues each class pays, what the club collects, short or over, the collection percentage, the cost of a day afield and the acres a member. What still prints while it is true is the arithmetic of what was typed and nothing about the season: the budget lines added, last season's lines added, the difference, the members, the shares and the hunter-days (review round 1, R1-B1)"
    },
    {
      "name": "planned_rent",
      "unit": "dollars",
      "formula": "the planned figure for the Rent for the season line, to the cent, after the range clamp"
    },
    {
      "name": "actual_rent",
      "unit": "dollars",
      "formula": "the last-season figure for the Rent for the season line, to the cent, after the range clamp"
    },
    {
      "name": "variance_rent",
      "unit": "dollars, signed, or null",
      "formula": "planned_rent - actual_rent when actual_rent is above nothing; null otherwise"
    },
    {
      "name": "planned_cover",
      "unit": "dollars",
      "formula": "the planned figure for the Liability cover premium line, to the cent, after the range clamp"
    },
    {
      "name": "actual_cover",
      "unit": "dollars",
      "formula": "the last-season figure for the Liability cover premium line, to the cent, after the range clamp"
    },
    {
      "name": "variance_cover",
      "unit": "dollars, signed, or null",
      "formula": "planned_cover - actual_cover when actual_cover is above nothing; null otherwise"
    },
    {
      "name": "planned_plots",
      "unit": "dollars",
      "formula": "the planned figure for the Food plots and seed line, to the cent, after the range clamp"
    },
    {
      "name": "actual_plots",
      "unit": "dollars",
      "formula": "the last-season figure for the Food plots and seed line, to the cent, after the range clamp"
    },
    {
      "name": "variance_plots",
      "unit": "dollars, signed, or null",
      "formula": "planned_plots - actual_plots when actual_plots is above nothing; null otherwise"
    },
    {
      "name": "planned_stands",
      "unit": "dollars",
      "formula": "the planned figure for the Stands and feeders line, to the cent, after the range clamp"
    },
    {
      "name": "actual_stands",
      "unit": "dollars",
      "formula": "the last-season figure for the Stands and feeders line, to the cent, after the range clamp"
    },
    {
      "name": "variance_stands",
      "unit": "dollars, signed, or null",
      "formula": "planned_stands - actual_stands when actual_stands is above nothing; null otherwise"
    },
    {
      "name": "planned_gates",
      "unit": "dollars",
      "formula": "the planned figure for the Gates and road work line, to the cent, after the range clamp"
    },
    {
      "name": "actual_gates",
      "unit": "dollars",
      "formula": "the last-season figure for the Gates and road work line, to the cent, after the range clamp"
    },
    {
      "name": "variance_gates",
      "unit": "dollars, signed, or null",
      "formula": "planned_gates - actual_gates when actual_gates is above nothing; null otherwise"
    },
    {
      "name": "planned_camp",
      "unit": "dollars",
      "formula": "the planned figure for the The camp line, to the cent, after the range clamp"
    },
    {
      "name": "actual_camp",
      "unit": "dollars",
      "formula": "the last-season figure for the The camp line, to the cent, after the range clamp"
    },
    {
      "name": "variance_camp",
      "unit": "dollars, signed, or null",
      "formula": "planned_camp - actual_camp when actual_camp is above nothing; null otherwise"
    },
    {
      "name": "planned_other",
      "unit": "dollars",
      "formula": "the planned figure for the Other, or the whole season if you have not itemised it line, to the cent, after the range clamp"
    },
    {
      "name": "actual_other",
      "unit": "dollars",
      "formula": "the last-season figure for the Other, or the whole season if you have not itemised it line, to the cent, after the range clamp"
    },
    {
      "name": "variance_other",
      "unit": "dollars, signed, or null",
      "formula": "planned_other - actual_other when actual_other is above nothing; null otherwise"
    },
    {
      "name": "shares1",
      "unit": "shares",
      "formula": "round2(classes[0].count x effectiveWeight(classes[0])), where effectiveWeight is that class's own weight unless oneShareAMember is true and that class is the one on the roll, in which case it is 1"
    },
    {
      "name": "duesRequired1",
      "unit": "dollars a member, or null",
      "formula": "round2(duesPerShare x effectiveWeight(classes[0])); null when duesPerShare is null. With oneShareAMember true this is netToRaise / that class's member count, the figure the weight cancels to, to within the cent the two-stage rounding leaves at other weights (R3-M4)"
    },
    {
      "name": "duesPaidPerMember1",
      "unit": "dollars a member, or null",
      "formula": "classes[0].duesVoted when that is above nothing, otherwise duesRequired1; null when both are absent"
    },
    {
      "name": "classTotal1",
      "unit": "dollars",
      "formula": "round2(duesPaidPerMember1 x classes[0].count), or 0 when there is no figure a member of that class pays; the field is 0 and not null, and the dues table prints a dash for it (R1-M8)"
    },
    {
      "name": "daysAfield1",
      "unit": "hunter-days",
      "formula": "classes[0].daysAfield after the range clamp"
    },
    {
      "name": "shares2",
      "unit": "shares",
      "formula": "round2(classes[1].count x effectiveWeight(classes[1])), where effectiveWeight is that class's own weight unless oneShareAMember is true and that class is the one on the roll, in which case it is 1"
    },
    {
      "name": "duesRequired2",
      "unit": "dollars a member, or null",
      "formula": "round2(duesPerShare x effectiveWeight(classes[1])); null when duesPerShare is null. With oneShareAMember true this is netToRaise / that class's member count, the figure the weight cancels to"
    },
    {
      "name": "duesPaidPerMember2",
      "unit": "dollars a member, or null",
      "formula": "classes[1].duesVoted when that is above nothing, otherwise duesRequired2; null when both are absent"
    },
    {
      "name": "classTotal2",
      "unit": "dollars",
      "formula": "round2(duesPaidPerMember2 x classes[1].count), or 0 when there is no figure a member of that class pays; the field is 0 and not null, and the dues table prints a dash for it (R1-M8)"
    },
    {
      "name": "daysAfield2",
      "unit": "hunter-days",
      "formula": "classes[1].daysAfield after the range clamp"
    },
    {
      "name": "shares3",
      "unit": "shares",
      "formula": "round2(classes[2].count x effectiveWeight(classes[2])), where effectiveWeight is that class's own weight unless oneShareAMember is true and that class is the one on the roll, in which case it is 1"
    },
    {
      "name": "duesRequired3",
      "unit": "dollars a member, or null",
      "formula": "round2(duesPerShare x effectiveWeight(classes[2])); null when duesPerShare is null. With oneShareAMember true this is netToRaise / that class's member count, the figure the weight cancels to"
    },
    {
      "name": "duesPaidPerMember3",
      "unit": "dollars a member, or null",
      "formula": "classes[2].duesVoted when that is above nothing, otherwise duesRequired3; null when both are absent"
    },
    {
      "name": "classTotal3",
      "unit": "dollars",
      "formula": "round2(duesPaidPerMember3 x classes[2].count), or 0 when there is no figure a member of that class pays; the field is 0 and not null, and the dues table prints a dash for it (R1-M8)"
    },
    {
      "name": "daysAfield3",
      "unit": "hunter-days",
      "formula": "classes[2].daysAfield after the range clamp"
    },
    {
      "name": "shares4",
      "unit": "shares",
      "formula": "round2(classes[3].count x effectiveWeight(classes[3])), where effectiveWeight is that class's own weight unless oneShareAMember is true and that class is the one on the roll, in which case it is 1"
    },
    {
      "name": "duesRequired4",
      "unit": "dollars a member, or null",
      "formula": "round2(duesPerShare x effectiveWeight(classes[3])); null when duesPerShare is null. With oneShareAMember true this is netToRaise / that class's member count, the figure the weight cancels to"
    },
    {
      "name": "duesPaidPerMember4",
      "unit": "dollars a member, or null",
      "formula": "classes[3].duesVoted when that is above nothing, otherwise duesRequired4; null when both are absent"
    },
    {
      "name": "classTotal4",
      "unit": "dollars",
      "formula": "round2(duesPaidPerMember4 x classes[3].count), or 0 when there is no figure a member of that class pays; the field is 0 and not null, and the dues table prints a dash for it (R1-M8)"
    },
    {
      "name": "daysAfield4",
      "unit": "hunter-days",
      "formula": "classes[3].daysAfield after the range clamp"
    }
  ],
  "formula": [
    "for each of the seven lines L: planned_L = clamp(L planned, 0, 100000) to the cent; actual_L = clamp(L actual, 0, 100000) to the cent",
    "for each line L: variance_L = planned_L - actual_L when actual_L > 0, else null",
    "plannedSpend = sum over L of planned_L; actualSpend = sum over L of actual_L",
    "linesWithActual = count of L where actual_L > 0",
    "varianceTotal = sum over L of variance_L where it is not null, or null when linesWithActual is 0",
    "guestIncomeExpected = guestFee x guestDaysExpected; guestIncomeLogged = guestFee x guestDaysLogged",
    "netToRaise = max(0, plannedSpend - guestIncomeExpected); guestOverPlan = max(0, guestIncomeExpected - plannedSpend)",
    "membersTotal = sum of count_i; hunterDays = sum of daysAfield_i",
    "typedShares = sum over the four classes of round2(count_i x weight_i); oneShareAMember = (exactly one class has count_i > 0) and typedShares is 0",
    "for each class i: eff_i = 1 when oneShareAMember and count_i > 0, else weight_i",
    "for each class i: shares_i = count_i x eff_i; totalShares = sum of shares_i",
    "duesPerShare = netToRaise / totalShares when totalShares > 0, else null",
    "for each class i: duesRequired_i = duesPerShare x eff_i when duesPerShare is not null, else null",
    "for each class i: duesPaidPerMember_i = duesVoted_i when duesVoted_i > 0, else duesRequired_i",
    "for each class i: classTotal_i = duesPaidPerMember_i x count_i, or 0 when duesPaidPerMember_i is null",
    "duesUnset = some class has count_i > 0 and duesPaidPerMember_i is null",
    "duesCollected = null when duesUnset, else sum of classTotal_i; duesRequiredTotal = sum of (duesRequired_i x count_i) when duesPerShare is not null, else null",
    "collects = null when duesCollected is null, else duesCollected + guestIncomeLogged",
    "shortOrOver = null when collects is null, else collects - plannedSpend",
    "collectionPct = collects / plannedSpend x 100 when plannedSpend > 0 and collects is not null, else null",
    "costPerHunterDay = plannedSpend / hunterDays when hunterDays > 0, else null",
    "acresPerMember = acres / membersTotal when membersTotal > 0 and acres > 0, else null",
    "waiting = membersTotal is 0, or plannedSpend is 0, or (guestFee > 0 and guestDaysExpected is 0 and guestDaysLogged is 0), or (guestFee is 0 and (guestDaysExpected > 0 or guestDaysLogged > 0))"
  ],
  "identities": [
    "duesCollected equals the sum of the four classTotal figures, each of them the figure printed in that class's row of the dues table; the column foots and the page's test harness asserts it. Where duesCollected is null the dues table's footing prints a dash in that column, so a footing is printed only where every class on the roll prints a figure above it (R2-B2)",
    "collects equals duesCollected + guestIncomeLogged, and shortOrOver equals collects - plannedSpend, so what the page calls short or over is the subtraction of two figures it also prints. All three are null together, and the collection percentage with them",
    "varianceTotal equals the sum of the per-line differences the page prints, not plannedSpend - actualSpend, which differ whenever a line carries no last-season figure. The page says in words how many lines carry one",
    "while exactly one class carries members, duesPerShare x that class's weight is the same figure whatever non-zero weight is typed, because the weight cancels between the shares and the per-member figure. With no weight typed at all in that one row the same figure is returned by the oneShareAMember rule above, so netToRaise / that class's members is the per-member figure at every weight the row can hold, the empty box included (R2-B1)",
    "the three guest-day figures and their income: guestIncomeExpected and guestIncomeLogged are each a product of the fee and a count, so with neither the fee nor a count supplied there is no such income, and the page dashes the figure in the glance, in the season lines beneath the dues table and in the working together rather than printing 0.00 in one block and a dash in another (R2-B4). This endpoint returns 0 for them, which is the product of the two zeros it was given; the dash is a statement about an empty box, which only the page has"
  ],
  "example": {
    "note": "the season the page ships filled in, so this endpoint and the page answer with the same figures",
    "inputs": {
      "acres": 1100,
      "rentPlanned": 9600,
      "rentActual": 9200,
      "coverPlanned": 850,
      "coverActual": 810,
      "plotsPlanned": 1200,
      "plotsActual": 1340,
      "standsPlanned": 600,
      "standsActual": 520,
      "gatesPlanned": 450,
      "gatesActual": 615,
      "campPlanned": 700,
      "campActual": 680,
      "otherPlanned": 300,
      "otherActual": 300,
      "guestFee": 50,
      "guestDaysExpected": 30,
      "guestDaysLogged": 24,
      "classes": [
        {
          "name": "Full member",
          "count": 12,
          "weight": 1,
          "duesVoted": 0,
          "daysAfield": 210
        },
        {
          "name": "Associate",
          "count": 5,
          "weight": 0.5,
          "duesVoted": 0,
          "daysAfield": 48
        },
        {
          "name": "Youth",
          "count": 4,
          "weight": 0.25,
          "duesVoted": 0,
          "daysAfield": 26
        },
        {
          "name": "Non-hunting",
          "count": 3,
          "weight": 0.15,
          "duesVoted": 0,
          "daysAfield": 0
        }
      ]
    },
    "outputs": {
      "plannedSpend": 13700,
      "actualSpend": 13465,
      "varianceTotal": 235,
      "linesWithActual": 7,
      "guestIncomeExpected": 1500,
      "guestIncomeLogged": 1200,
      "guestOverPlan": 0,
      "netToRaise": 12200,
      "membersTotal": 24,
      "totalShares": 15.95,
      "duesPerShare": 764.89,
      "duesRequiredTotal": 12200,
      "duesCollected": 12200,
      "collects": 13400,
      "shortOrOver": -300,
      "collectionPct": 97.8,
      "hunterDays": 284,
      "costPerHunterDay": 48.24,
      "acresPerMember": 45.83,
      "waiting": false,
      "oneShareAMember": false,
      "planned_rent": 9600,
      "actual_rent": 9200,
      "variance_rent": 400,
      "planned_cover": 850,
      "actual_cover": 810,
      "variance_cover": 40,
      "planned_plots": 1200,
      "actual_plots": 1340,
      "variance_plots": -140,
      "planned_stands": 600,
      "actual_stands": 520,
      "variance_stands": 80,
      "planned_gates": 450,
      "actual_gates": 615,
      "variance_gates": -165,
      "planned_camp": 700,
      "actual_camp": 680,
      "variance_camp": 20,
      "planned_other": 300,
      "actual_other": 300,
      "variance_other": 0,
      "shares1": 12,
      "duesRequired1": 764.89,
      "duesPaidPerMember1": 764.89,
      "classTotal1": 9178.68,
      "daysAfield1": 210,
      "shares2": 2.5,
      "duesRequired2": 382.45,
      "duesPaidPerMember2": 382.45,
      "classTotal2": 1912.25,
      "daysAfield2": 48,
      "shares3": 1,
      "duesRequired3": 191.22,
      "duesPaidPerMember3": 191.22,
      "classTotal3": 764.88,
      "daysAfield3": 26,
      "shares4": 0.45,
      "duesRequired4": 114.73,
      "duesPaidPerMember4": 114.73,
      "classTotal4": 344.19,
      "daysAfield4": 0
    }
  },
  "not_covered": [
    "What the club's agreement with the landowner says, or should say. This endpoint reads a rent figure and nothing about the document it comes from.",
    "What ground is worth, what a season should cost, or what to charge an acre. Every figure here is one that was typed in.",
    "Whether a dues figure is the right one. The arithmetic says what the budget as typed requires; the club's own vote is a separate figure and is returned beside it rather than instead of it.",
    "Anything about who may hunt, where, when, or under what licence. No such figure is read and none is returned.",
    "Mid-season movement. One season at a time: a member joining or leaving part way through is a decision the arithmetic does not make, and the page says so rather than guessing a pro-rata rule."
  ],
  "no_advice": "This endpoint returns arithmetic on figures supplied to it. It recommends no dues figure, no share weight, no guest-day fee, no acreage and no budget."
}
