{
 "endpoint": "https://otherworldlabs.com/weighted-grade-by-category-and-the-score-needed/api.json",
 "page": "https://otherworldlabs.com/weighted-grade-by-category-and-the-score-needed/",
 "what_it_answers": "For each piece of work a reader types, with its points earned, its points possible and the category it sits in, and for the weight the reader types against each category: what percentage each category is at over its own marked work, what the course is at once each category is weighted by the weight typed against it and divided by the weight actually present, how much of the typed weight is still outstanding, and what percentage that outstanding weight must carry for the course to finish on each target typed.",
 "shape": "A static document, not a service. There is no request to make and nothing is posted anywhere: this file is the arithmetic written down, with its inputs, its ranges, its formulas, the states it keeps apart and three worked examples. An agent that wants the answer computes it from what is here. The page beside it runs the same engine in the reader's own browser and sends nothing anywhere.",
 "units": "Points to two decimals, percentages to one decimal, and weight in percentage points to one decimal. There is no money figure anywhere in this engine: no price, no rate, no cost, no fee and no amount of any kind. The only price printed on the page is the price of the workbook sold beside it, which is not an output of this engine.",
 "the_page_supplies_no_figure": "Every input is the reader's own: the category labels, the weight against each category, the label, category, points possible and points earned of each piece of work, and each target. The engine supplies no category, no weight, no target, no scale and no letter grade, and it defaults none of them. It also supplies no grading scale of any kind: no letter, no plus or minus band, no pass mark, no fail mark, no curve and no rounding convention presented as anybody's standard, and no grade point, quality point, credit, credit hour or conversion.",
 "the_one_rule": "A piece of work is MARKED when and only when it carries a points possible figure above 0 and a points earned figure typed against it. A typed 0 earned is marked; a blank score is not. An unmarked piece of work enters no total anywhere on the sheet. A category is marked when it holds at least one marked piece of work. Every earned and possible total this engine reports is a total over marked work only, which is why a category can carry points possible and still have no percentage, and why its typed weight is then outstanding rather than present.",
 "state": {
  "shape": "{ categoryCount, categories: [ { label, weight }, ... up to 6 ], workCount, works: [ { label, categoryIndex, possible, earned }, ... up to 20 ], targetCount, targets: [ { value }, ... up to 3 ] }",
  "counts": "categoryCount is read inside 1 to 6, workCount inside 1 to 20 and targetCount inside 0 to 3. Each says how many members of its list are live; members beyond it are read by nothing. A count that cannot be read at all falls back to the length of the list handed in, which is how the second spelling below works, and the page's own controls cannot be blank.",
  "categories": "A label, up to 40 characters, blank and whitespace-only accepted and replaced by a positional label the page prints (Category 1 and so on), and a weight in percentage points read inside 0 to 100 in tenths. The weights are the reader's own and this engine never scales them, corrects them or assumes they come to 100.",
  "works": "A label up to 40 characters; categoryIndex, the 0-based index of the category the row sits in, read inside 0 to 5 and read as no category at all when it names a category beyond the live count; points possible read inside 0 to 10,000 in hundredths, where 0 or blank means the row counts for nothing yet; and points earned read the same way, where blank means not marked yet.",
  "a_row_in_no_category": "A row whose categoryIndex cannot be read, or which names a category past the live count, is in no category: it enters no block and no total, and the page prints it at the foot of the sheet rather than dropping it. The page's own select always has a category chosen, so this state is reachable from a caller and from the workbook's spelling rather than from the page's own control, and it is printed rather than guessed at.",
  "targets": "Each target is an object carrying `value`, a percentage read inside 0 to 100 in tenths. A bare number is accepted in its place. A target row with nothing typed in it is not a target: nothing is solved on it and the page says so in words.",
  "the_second_spelling": "A SECOND SPELLING of the same state, and the same function: { categories: [ \"Homework\", \"Quizzes\", ... ], entries: [ { kind, what, category, possible, earned, weight, target }, ... ] }. `kind` is one of the five kinds the workbook own choice column offers - \"A piece of work\", \"A category weight\", \"The first target\", \"The second target\" or \"The third target\" - and a row of any other kind, or of none, is read by nothing. A work row reads its category, its points possible and its points earned; a weight row reads its category and its weight; a target row reads its target. ROWS OF ONE KIND ADD: a category weight is the total of the weight rows that name that category, and a target is the total of the rows of that target own kind, which is the reading that keeps a figure stable when rows are sorted, inserted or deleted. A target is named by the kind of its row and not by where the row sits, so the second target is the second target whether or not the first is typed. `category` is the label the workbook own list hands back, so it is resolved by its leading number first (\"2 Quizzes\" is the second category) and by an exact match of the typed name second; a bare number is read as the 0-based index, which is what the page own control hands in; and anything else is no category at all. This is the spelling the workbook sold beside the page is read against, one row an entry, and the page own test suite asserts that the two spellings answer identically at every swept state."
 },
 "inputs": [
  {
   "name": "categoryCount",
   "label": "How many categories the course has",
   "type": "integer",
   "control": "select",
   "offered": {
    "min": 1,
    "max": 6,
    "step": 1
   },
   "blank_means": "the length of the list handed in",
   "note": "the page ships at 3 and the control cannot be blank"
  },
  {
   "name": "categories[i].label",
   "label": "What you call the category",
   "type": "text",
   "control": "text",
   "offered": {
    "max_characters": 40
   },
   "blank_means": "a positional label the page prints, Category 1 to Category 6",
   "note": "the reader's own word; never a course code and never a person"
  },
  {
   "name": "categories[i].weight",
   "label": "The weight you were given for it",
   "type": "number",
   "control": "number",
   "offered": {
    "min": 0,
    "max": 100,
    "step": 0.1
   },
   "blank_means": "a weight of nothing, so the category is in no total of weight",
   "note": "percentage points off the reader's own syllabus; this engine supplies none and scales none"
  },
  {
   "name": "workCount",
   "label": "How many pieces of work you are typing",
   "type": "integer",
   "control": "select",
   "offered": {
    "min": 1,
    "max": 20,
    "step": 1
   },
   "blank_means": "the length of the list handed in",
   "note": "the page ships at 4"
  },
  {
   "name": "works[j].label",
   "label": "What the piece of work is called",
   "type": "text",
   "control": "text",
   "offered": {
    "max_characters": 40
   },
   "blank_means": "a positional label the page prints, Piece of work 1 and so on",
   "note": "the reader's own word"
  },
  {
   "name": "works[j].categoryIndex",
   "label": "The category it sits in",
   "type": "integer",
   "control": "select",
   "offered": {
    "min": 0,
    "max": 5,
    "step": 1
   },
   "blank_means": "no category on this sheet, so the row enters no total",
   "note": "0-based, in the order the categories were typed"
  },
  {
   "name": "works[j].possible",
   "label": "Points possible",
   "type": "number",
   "control": "number",
   "offered": {
    "min": 0,
    "max": 10000,
    "step": 0.01
   },
   "blank_means": "the row counts for nothing yet and is marked by nothing",
   "note": "the reader's own figure"
  },
  {
   "name": "works[j].earned",
   "label": "Points earned",
   "type": "number",
   "control": "number",
   "offered": {
    "min": 0,
    "max": 10000,
    "step": 0.01
   },
   "blank_means": "not marked yet, so the row enters no total",
   "note": "a typed 0 is marked and is not the same as a blank"
  },
  {
   "name": "targetCount",
   "label": "How many targets you are typing",
   "type": "integer",
   "control": "select",
   "offered": {
    "min": 0,
    "max": 3,
    "step": 1
   },
   "blank_means": "the length of the list handed in",
   "note": "the page ships at 2, and 0 is a legitimate state"
  },
  {
   "name": "targets[k].value",
   "label": "The percentage you want to finish at",
   "type": "number",
   "control": "number",
   "offered": {
    "min": 0,
    "max": 100,
    "step": 0.1
   },
   "blank_means": "no target on that row, so nothing is solved on it",
   "note": "the reader's own figure; this engine proposes none"
  }
 ],
 "rounding": {
  "the_order": "A typed figure is read in this order and no other: parsed with parseFloat after whitespace and thousands commas are removed, then tested against its range, then rounded once to the places this engine keeps. Out of range in EITHER direction is read as NOT TYPED and is never pulled to the nearest figure the control offers, so a points figure typed 10000.01 is read as blank rather than as 10,000.",
  "the_places_it_keeps": "ONE RULE FOR EVERY TYPED FIGURE: it is rounded once, to two decimals. That is the step the two points controls offer; the weight and the target controls step in tenths, and a hundredth in one of those is reachable from a caller and from the workbook sold beside the page rather than from the control, so it is read rather than rounded away. The counts are rounded to a whole number. Nothing else is rounded before it is used: a category percentage, the weighted total, the course mark and the figure the outstanding weight must carry are computed from the typed figures as read and are rounded only where they are PRINTED, which is two decimals for points and one decimal for every percentage and every weight. TWO ROUNDINGS THIS ENGINE APPLIES ALL THE SAME, declared here because the sentence above used to exclude them and a reimplementation that followed it could not reproduce the flat answer exactly. First, every running sum is rounded to two decimals as it accumulates: each category's markedEarned, markedPossible and possibleTyped, the sheet's earnedMarkedTotal and possibleMarkedTotal, and weightTypedTotal, weightPresent and weightOutstanding. The figures being added are already read to two decimals, so this only clears floating-point dust off a sum and moves no figure a reader can see. Second, the flat answer rounds its four percentage families to SIX decimals before it returns them: weightedSumMarked, markSoFar, catPct<i> for every category and target<k>Needed for every target. That one is a real rounding of up to 5e-7 on a percentage, so markSoFar comes back from apiOutputs as 47.142857 where the arithmetic above gives 47.14285714285714. The PRINTED figures are rounded from the unrounded arithmetic as this sentence says; the six decimals are the flat answer's alone.",
  "the_rounding_function": "Math.round(x * 10^places) / 10^places, which is Math.round on the double and so half toward POSITIVE INFINITY: a points figure typed 0.005 is read as 0.01, and one typed 0.004 as 0.00. The negative half-way case is unreachable from this engine rather than handled: every range starts at 0, so a negative figure is out of range in the low direction and is read as not typed before any rounding of it can matter, and no figure this engine reports can be negative.",
  "no_tolerance_of_its_own": "The comparison between this engine, the workbook's three routes and any reimplementation is made at half a cent on a points figure and at 1e-9 on a percentage, which is the tolerance the workbook's Check sheet uses, and it is made on the figures AFTER the two roundings `the_places_it_keeps` declares. A reimplementation that rounds the flat answer's four percentage families to six decimals as that sentence says agrees with this engine inside 1e-9; one that returns them unrounded differs from it by up to 5e-7 on a percentage, which is that declared rounding and not a disagreement about the arithmetic. Nothing in this engine compares two floating-point figures for exact equality except the zero tests named in `completeness`.",
  "why_it_is_said_here": "An agent redoing this arithmetic rounds where this says and nowhere else. A category percentage rounded before it is weighted, or a weighted total rounded before it is divided, gives a different course mark, and the difference is reachable on ordinary figures rather than only at an edge."
 },
 "formulas": {
  "marked": "possible > 0 && earned !== null, on the figures as read above. A typed 0 earned is marked.",
  "row_percentage": "earned / possible * 100, for a marked row only. There is no percentage for an unmarked row and the page prints a dash and the words not marked yet.",
  "category_totals": "markedEarned = the sum of earned over the category's marked rows; markedPossible = the sum of possible over the same rows; possibleTyped = the sum of possible over EVERY row in the category, marked or not.",
  "category_percentage": "markedEarned / markedPossible * 100 where markedPossible > 0, which is the category's own points over its own points and NOT the average of its rows' percentages. Where markedPossible is 0 there is no percentage at all: the engine reports it as 0 in the flat answer and the page prints its own words, and catMarkedWorks<i> and the withheld flags say which is which.",
  "weight_typed_total": "the sum of the weights typed against the live categories.",
  "weight_present": "the sum of the weights typed against the live categories that hold at least one marked row.",
  "weight_outstanding": "weightTypedTotal - weightPresent, which is therefore the total of the weights of exactly the categories with nothing marked in them. It is clamped at 0 only against floating-point dust and is never negative.",
  "weighted_sum_marked": "the sum, over the marked categories, of that category's percentage times its typed weight. It is the figure both the course mark and the inverse solve rest on.",
  "mark_so_far": "weightedSumMarked / weightPresent where weightPresent > 0. It is divided by the weight ACTUALLY PRESENT and never by 100. Where weightPresent is 0 there is no course mark at all: the engine reports markSoFar as 0 and courseMarkWithheld as true, and the page prints no figure.",
  "the_figure_needed": "(target * weightTypedTotal - weightedSumMarked) / weightOutstanding, where weightOutstanding > 0. It rests on the reader's OWN typed total and never on an assumed 100, so it stays right when the weights come to 85 or to 130. It holds every already-marked category at the percentage it stands at now, so a row still unmarked inside a marked category does not move it. It may legitimately come out above 100, which means the outstanding weight would have to carry more than full marks, and that figure is reported and printed.",
  "the_three_target_states": "needed, where the arithmetic above gives a positive figure; reached, which has two limbs and they are read as the two limbs they are - the first where weightOutstanding is 0 and markSoFar is at least the target, the second where weightOutstanding is above 0 and the figure needed comes out 0 or below, which on that second limb is the inequality weightedSumMarked >= target * weightTypedTotal written another way. The two are not one inequality applying everywhere: markSoFar divides by the weight present and the figure needed divides by the typed total, so read as one rule they make reached and unreachable overlap at weightTypedTotal = 0, where an agent would report both. Read as the limbs they are, the three states are disjoint. And unreachable, where weightOutstanding is 0 and there is no course mark at least the target, so there is no remaining weight for anything to be carried on. target<k>Needed in the flat answer is the raw arithmetic in every state, 0 where there is no outstanding weight to divide by, and it is negative or above 100 exactly where the page prints its own words instead."
 },
 "completeness": {
  "an_entry_is_complete": "when none of THREE things is true of the sheet. `waiting` is true while any one of them is, and those three, in the engine's own order, are: the weight present is 0; any typed weight is still outstanding, which is weightOutstanding above 1e-9 and therefore any live category carrying a typed weight above 0 with no marked piece of work in it; or any piece of work with points possible above 0 carries no points earned figure. The first clause is not implied by the other two and has to be read on its own: a sheet whose categories all carry a typed weight of 0 leaves nothing outstanding, so a marked row on it makes the second and third clauses false while the first holds `waiting` true and the course mark withheld.",
  "what_that_withholds": "While the entry is incomplete the page's headline rows carry the words saying what work their figures stand on, and the warning in the glance says which of the states below is live. The course mark itself prints NO FIGURE at all wherever the weight present is 0, which is every sheet on which no category holding marked work carries a weight - whether because nothing is marked anywhere, or because the categories that do hold marked work carry a weight of 0 or none - and courseMarkWithheld says so. The two routes are not the same set of sheets and the page prints a different sentence for each: with nothing marked it says so, and with work marked behind no weight it says that instead, whatever is marked.",
  "what_it_does_not_withhold": "The weight typed, the weight present and the weight outstanding, which are complete on their own as soon as a weight is typed; each category's own percentage, which stands on that category's own marked work; and the figure the outstanding weight must carry, which uses only the typed weights, the typed target and the work already marked, all of them complete. Where some weight is typed and nothing at all is marked, that figure comes out equal to the target itself, which is exactly true and is the one thing such a reader can be told. Where no weight is typed at all the outstanding weight is 0, so there is nothing for a figure to be carried on: none is printed and the page prints its own words in its place.",
  "the_zero_tests": "weightPresent === 0 and markedPossible > 0 are read as written; the weight outstanding is read against 1e-9 rather than 0, because it is a difference of two sums of typed tenths."
 },
 "states": [
  {
   "state": "no weight present, by either of its two routes",
   "reached_when": "the weight present is 0: either no row carries both a points possible figure above 0 and a typed score, or the categories that do hold such a row carry a weight of 0 or none",
   "printed": "no course mark figure at all, the words no weight present yet in its place, and the warning in the glance naming which of the two routes this sheet is on - that nothing is marked anywhere, or that work is marked and the categories holding it carry a weight of nothing"
  },
  {
   "state": "a category with nothing possible against it yet",
   "reached_when": "every row in it has points possible of 0 or blank, or it holds no row at all",
   "printed": "no percentage for that category, the words no marked work in it yet, and its typed weight counted as outstanding"
  },
  {
   "state": "a category whose work is assigned and not scored",
   "reached_when": "its rows carry points possible above 0 and no score",
   "printed": "the same, and this is the state that has two defensible readings and only one right one: the weight is OUTSTANDING, not present, so the course mark is the mark on the work that is marked"
  },
  {
   "state": "a piece of work with points possible and no score yet",
   "reached_when": "possible > 0 and earned blank",
   "printed": "a dash and the words not marked yet on its row, no total moved for it, and the warning naming how many such rows there are"
  },
  {
   "state": "the typed weights do not come to 100",
   "reached_when": "the sum of the typed weights is anything other than 100",
   "printed": "the sum itself, plainly, with nothing scaled, and every figure on the sheet solved on that total"
  },
  {
   "state": "a target already reached",
   "reached_when": "the weighted total already marked is at least what the target asks of the whole typed weight",
   "printed": "the words already reached at the weight marked so far in place of a figure"
  },
  {
   "state": "a target no remaining weight can carry",
   "reached_when": "no weight is outstanding and there is no course mark at least the target",
   "printed": "the words no remaining weight can carry this target in place of a figure"
  },
  {
   "state": "a figure needed above 100",
   "reached_when": "the target needs more of the outstanding weight than full marks on it would give",
   "printed": "the figure itself, above 100, because that is the honest answer to what would be needed"
  },
  {
   "state": "a row above its own points possible",
   "reached_when": "a marked row's points earned exceed its points possible, which a reader types for a bonus",
   "printed": "the ratio as typed, above 100%, uncorrected and unclamped, with the state named in words"
  },
  {
   "state": "a row in no category on this sheet",
   "reached_when": "a row names a category past the live count, which a caller or the workbook's spelling can produce and the page's own select cannot",
   "printed": "the row at the foot of the sheet, in no block and in no total, with the state named in words"
  }
 ],
 "outputs": [
  {
   "name": "categoriesLive",
   "type": "integer",
   "of": "how many categories are live, 1 to 6"
  },
  {
   "name": "worksLive",
   "type": "integer",
   "of": "how many pieces of work are live, 1 to 20"
  },
  {
   "name": "targetRowsLive",
   "type": "integer",
   "of": "how many target rows are live, 0 to 3, whether or not a percentage is typed in them"
  },
  {
   "name": "waiting",
   "type": "boolean",
   "of": "true while the entry is incomplete, as `completeness` defines it"
  },
  {
   "name": "courseMarkWithheld",
   "type": "boolean",
   "of": "true where the weight present is 0, which is where markSoFar is reported as 0 and the page prints no figure at all"
  },
  {
   "name": "weightsOffTheHundred",
   "type": "boolean",
   "of": "true where the typed weights come to anything other than 100"
  },
  {
   "name": "weightTypedTotal",
   "type": "number",
   "of": "the sum of the weights typed against the live categories, in percentage points to one decimal"
  },
  {
   "name": "weightPresent",
   "type": "number",
   "of": "the sum of the typed weights of the categories that hold at least one marked piece of work"
  },
  {
   "name": "weightOutstanding",
   "type": "number",
   "of": "weightTypedTotal less weightPresent, never negative"
  },
  {
   "name": "weightedSumMarked",
   "type": "number",
   "of": "the sum over the marked categories of that category's percentage times its typed weight; the figure the course mark and the inverse solve both rest on"
  },
  {
   "name": "markSoFar",
   "type": "number",
   "of": "the course mark so far in percent, weightedSumMarked over weightPresent; 0 where no weight is present, which courseMarkWithheld says and where the page prints no figure"
  },
  {
   "name": "earnedMarkedTotal",
   "type": "number",
   "of": "the points earned added over every marked piece of work in a live category"
  },
  {
   "name": "possibleMarkedTotal",
   "type": "number",
   "of": "the points possible added over the same rows"
  },
  {
   "name": "worksMarked",
   "type": "integer",
   "of": "how many pieces of work are marked AND sit in a category this sheet knows, which is the count every total on the sheet is footed over; a marked row in no category is counted in worksNoCategory instead"
  },
  {
   "name": "worksAssignedUnmarked",
   "type": "integer",
   "of": "how many carry points possible above 0 with no score yet"
  },
  {
   "name": "worksNoPossible",
   "type": "integer",
   "of": "how many carry no points possible yet, so they count for nothing"
  },
  {
   "name": "worksNoCategory",
   "type": "integer",
   "of": "how many sit in no category on this sheet"
  },
  {
   "name": "worksOverPossible",
   "type": "integer",
   "of": "how many marked rows carry points earned above their own points possible, counted over every live row whether or not it sits in a category this sheet knows. Unlike worksMarked this count carries NO category condition, so a marked row in no category is counted here as well as in worksNoCategory"
  },
  {
   "name": "categoriesMarked",
   "type": "integer",
   "of": "how many live categories hold at least one marked piece of work"
  },
  {
   "name": "categoriesOutstanding",
   "type": "integer",
   "of": "how many do not, which is where the outstanding weight comes from"
  },
  {
   "name": "categoriesNoPossible",
   "type": "integer",
   "of": "how many have nothing possible against them at all yet"
  },
  {
   "name": "categoriesAssignedOnly",
   "type": "integer",
   "of": "how many carry points possible with nothing scored, which is the state with two readings and one right one"
  },
  {
   "name": "markedHoldingUnmarked",
   "type": "integer",
   "of": "how many marked categories still hold a row that is NOT MARKED - a row with no points possible above 0, or none with a points earned figure typed, or neither. This is wider than a row with no score typed, and the difference is reachable: a row carrying a typed score and 0 points possible is not marked, enters no total, and is counted here"
  },
  {
   "name": "targetsTyped",
   "type": "integer",
   "of": "how many target rows carry a percentage"
  },
  {
   "name": "targetsBlank",
   "type": "integer",
   "of": "how many target rows are live and carry none"
  },
  {
   "name": "targetsReached",
   "type": "integer",
   "of": "how many typed targets the marked work already carries"
  },
  {
   "name": "targetsUnreachable",
   "type": "integer",
   "of": "how many have no outstanding weight to be carried on"
  },
  {
   "name": "catWeight1",
   "type": "number",
   "of": "the weight typed against category 1, in percentage points; 0 where that category is not live or carries no weight"
  },
  {
   "name": "catEarned1",
   "type": "number",
   "of": "the points earned added over the marked pieces of work in category 1"
  },
  {
   "name": "catPossible1",
   "type": "number",
   "of": "the points possible added over the same rows of category 1"
  },
  {
   "name": "catPct1",
   "type": "number",
   "of": "category 1's own percentage, its marked points earned over its marked points possible; 0 where it holds no marked work, where the page prints its own words instead and catMarkedWorks1 is 0"
  },
  {
   "name": "catMarkedWorks1",
   "type": "integer",
   "of": "how many marked pieces of work category 1 holds"
  },
  {
   "name": "catWeight2",
   "type": "number",
   "of": "the weight typed against category 2, in percentage points; 0 where that category is not live or carries no weight"
  },
  {
   "name": "catEarned2",
   "type": "number",
   "of": "the points earned added over the marked pieces of work in category 2"
  },
  {
   "name": "catPossible2",
   "type": "number",
   "of": "the points possible added over the same rows of category 2"
  },
  {
   "name": "catPct2",
   "type": "number",
   "of": "category 2's own percentage, its marked points earned over its marked points possible; 0 where it holds no marked work, where the page prints its own words instead and catMarkedWorks2 is 0"
  },
  {
   "name": "catMarkedWorks2",
   "type": "integer",
   "of": "how many marked pieces of work category 2 holds"
  },
  {
   "name": "catWeight3",
   "type": "number",
   "of": "the weight typed against category 3, in percentage points; 0 where that category is not live or carries no weight"
  },
  {
   "name": "catEarned3",
   "type": "number",
   "of": "the points earned added over the marked pieces of work in category 3"
  },
  {
   "name": "catPossible3",
   "type": "number",
   "of": "the points possible added over the same rows of category 3"
  },
  {
   "name": "catPct3",
   "type": "number",
   "of": "category 3's own percentage, its marked points earned over its marked points possible; 0 where it holds no marked work, where the page prints its own words instead and catMarkedWorks3 is 0"
  },
  {
   "name": "catMarkedWorks3",
   "type": "integer",
   "of": "how many marked pieces of work category 3 holds"
  },
  {
   "name": "catWeight4",
   "type": "number",
   "of": "the weight typed against category 4, in percentage points; 0 where that category is not live or carries no weight"
  },
  {
   "name": "catEarned4",
   "type": "number",
   "of": "the points earned added over the marked pieces of work in category 4"
  },
  {
   "name": "catPossible4",
   "type": "number",
   "of": "the points possible added over the same rows of category 4"
  },
  {
   "name": "catPct4",
   "type": "number",
   "of": "category 4's own percentage, its marked points earned over its marked points possible; 0 where it holds no marked work, where the page prints its own words instead and catMarkedWorks4 is 0"
  },
  {
   "name": "catMarkedWorks4",
   "type": "integer",
   "of": "how many marked pieces of work category 4 holds"
  },
  {
   "name": "catWeight5",
   "type": "number",
   "of": "the weight typed against category 5, in percentage points; 0 where that category is not live or carries no weight"
  },
  {
   "name": "catEarned5",
   "type": "number",
   "of": "the points earned added over the marked pieces of work in category 5"
  },
  {
   "name": "catPossible5",
   "type": "number",
   "of": "the points possible added over the same rows of category 5"
  },
  {
   "name": "catPct5",
   "type": "number",
   "of": "category 5's own percentage, its marked points earned over its marked points possible; 0 where it holds no marked work, where the page prints its own words instead and catMarkedWorks5 is 0"
  },
  {
   "name": "catMarkedWorks5",
   "type": "integer",
   "of": "how many marked pieces of work category 5 holds"
  },
  {
   "name": "catWeight6",
   "type": "number",
   "of": "the weight typed against category 6, in percentage points; 0 where that category is not live or carries no weight"
  },
  {
   "name": "catEarned6",
   "type": "number",
   "of": "the points earned added over the marked pieces of work in category 6"
  },
  {
   "name": "catPossible6",
   "type": "number",
   "of": "the points possible added over the same rows of category 6"
  },
  {
   "name": "catPct6",
   "type": "number",
   "of": "category 6's own percentage, its marked points earned over its marked points possible; 0 where it holds no marked work, where the page prints its own words instead and catMarkedWorks6 is 0"
  },
  {
   "name": "catMarkedWorks6",
   "type": "integer",
   "of": "how many marked pieces of work category 6 holds"
  },
  {
   "name": "target1Typed",
   "type": "integer",
   "of": "how many rows of the first target's own kind carry a percentage: 1 from the page's own control, and the count of them in the workbook's spelling, where rows of one kind add"
  },
  {
   "name": "target1Value",
   "type": "number",
   "of": "the percentage typed in the 1st target SLOT, which is that slot whether or not the others are typed; 0 where nothing is typed in it, which target1Typed says"
  },
  {
   "name": "target1Needed",
   "type": "number",
   "of": "the percentage the outstanding weight must carry for the 1st target slot, as the raw arithmetic gives it: 0 where nothing is typed in that slot or no weight is outstanding, at or below 0 where the target is already reached, and above 100 where the outstanding weight would have to carry more than full marks. The page prints its own words wherever this is not a positive figure the outstanding weight can carry"
  },
  {
   "name": "target2Typed",
   "type": "integer",
   "of": "how many rows of the second target's own kind carry a percentage: 1 from the page's own control, and the count of them in the workbook's spelling, where rows of one kind add"
  },
  {
   "name": "target2Value",
   "type": "number",
   "of": "the percentage typed in the 2nd target SLOT, which is that slot whether or not the others are typed; 0 where nothing is typed in it, which target2Typed says"
  },
  {
   "name": "target2Needed",
   "type": "number",
   "of": "the percentage the outstanding weight must carry for the 2nd target slot, as the raw arithmetic gives it: 0 where nothing is typed in that slot or no weight is outstanding, at or below 0 where the target is already reached, and above 100 where the outstanding weight would have to carry more than full marks. The page prints its own words wherever this is not a positive figure the outstanding weight can carry"
  },
  {
   "name": "target3Typed",
   "type": "integer",
   "of": "how many rows of the third target's own kind carry a percentage: 1 from the page's own control, and the count of them in the workbook's spelling, where rows of one kind add"
  },
  {
   "name": "target3Value",
   "type": "number",
   "of": "the percentage typed in the 3rd target SLOT, which is that slot whether or not the others are typed; 0 where nothing is typed in it, which target3Typed says"
  },
  {
   "name": "target3Needed",
   "type": "number",
   "of": "the percentage the outstanding weight must carry for the 3rd target slot, as the raw arithmetic gives it: 0 where nothing is typed in that slot or no weight is outstanding, at or below 0 where the target is already reached, and above 100 where the outstanding weight would have to carry more than full marks. The page prints its own words wherever this is not a positive figure the outstanding weight can carry"
  }
 ],
 "worked_example": {
  "why": "one course worked end to end: six categories, twelve pieces of work, one category assigned and not scored, one marked category still holding an unmarked row, three targets, one of which the outstanding weight cannot carry even at full marks.",
  "state": {
   "categoryCount": 6,
   "categories": [
    {
     "label": "Homework",
     "weight": 20
    },
    {
     "label": "Quizzes",
     "weight": 15
    },
    {
     "label": "Labs",
     "weight": 15
    },
    {
     "label": "Midterm",
     "weight": 20
    },
    {
     "label": "Final",
     "weight": 20
    },
    {
     "label": "Participation",
     "weight": 10
    }
   ],
   "workCount": 12,
   "works": [
    {
     "label": "HW1",
     "categoryIndex": 0,
     "possible": 20,
     "earned": 18
    },
    {
     "label": "HW2",
     "categoryIndex": 0,
     "possible": 20,
     "earned": 19
    },
    {
     "label": "HW3",
     "categoryIndex": 0,
     "possible": 20,
     "earned": 17
    },
    {
     "label": "HW4",
     "categoryIndex": 0,
     "possible": 20,
     "earned": null
    },
    {
     "label": "Quiz1",
     "categoryIndex": 1,
     "possible": 10,
     "earned": 8
    },
    {
     "label": "Quiz2",
     "categoryIndex": 1,
     "possible": 10,
     "earned": 9
    },
    {
     "label": "Quiz3",
     "categoryIndex": 1,
     "possible": 10,
     "earned": 7
    },
    {
     "label": "Lab1",
     "categoryIndex": 2,
     "possible": 50,
     "earned": 45
    },
    {
     "label": "Lab2",
     "categoryIndex": 2,
     "possible": 50,
     "earned": 48
    },
    {
     "label": "Midterm Exam",
     "categoryIndex": 3,
     "possible": 100,
     "earned": 78
    },
    {
     "label": "Final Exam",
     "categoryIndex": 4,
     "possible": 100,
     "earned": null
    },
    {
     "label": "Check-ins",
     "categoryIndex": 5,
     "possible": 10,
     "earned": 10
    }
   ],
   "targetCount": 3,
   "targets": [
    {
     "value": 90
    },
    {
     "value": 85
    },
    {
     "value": 93
    }
   ]
  },
  "outputs": {
   "categoriesLive": 6,
   "worksLive": 12,
   "targetRowsLive": 3,
   "waiting": true,
   "courseMarkWithheld": false,
   "weightsOffTheHundred": false,
   "weightTypedTotal": 100,
   "weightPresent": 80,
   "weightOutstanding": 20,
   "weightedSumMarked": 6955,
   "markSoFar": 86.9375,
   "earnedMarkedTotal": 259,
   "possibleMarkedTotal": 300,
   "worksMarked": 10,
   "worksAssignedUnmarked": 2,
   "worksNoPossible": 0,
   "worksNoCategory": 0,
   "worksOverPossible": 0,
   "categoriesMarked": 5,
   "categoriesOutstanding": 1,
   "categoriesNoPossible": 0,
   "categoriesAssignedOnly": 1,
   "markedHoldingUnmarked": 1,
   "targetsTyped": 3,
   "targetsBlank": 0,
   "targetsReached": 0,
   "targetsUnreachable": 0,
   "catWeight1": 20,
   "catEarned1": 54,
   "catPossible1": 60,
   "catPct1": 90,
   "catMarkedWorks1": 3,
   "catWeight2": 15,
   "catEarned2": 24,
   "catPossible2": 30,
   "catPct2": 80,
   "catMarkedWorks2": 3,
   "catWeight3": 15,
   "catEarned3": 93,
   "catPossible3": 100,
   "catPct3": 93,
   "catMarkedWorks3": 2,
   "catWeight4": 20,
   "catEarned4": 78,
   "catPossible4": 100,
   "catPct4": 78,
   "catMarkedWorks4": 1,
   "catWeight5": 20,
   "catEarned5": 0,
   "catPossible5": 0,
   "catPct5": 0,
   "catMarkedWorks5": 0,
   "catWeight6": 10,
   "catEarned6": 10,
   "catPossible6": 10,
   "catPct6": 100,
   "catMarkedWorks6": 1,
   "target1Typed": 1,
   "target1Value": 90,
   "target1Needed": 102.25,
   "target2Typed": 1,
   "target2Value": 85,
   "target2Needed": 77.25,
   "target3Typed": 1,
   "target3Value": 93,
   "target3Needed": 117.25
  },
  "read_it_back": "Five of the six categories hold marked work, so the weight present is 80 of the 100 typed and the Final's 20 is outstanding. Homework's three marked rows are 54 of 60, which is 90.0%, and its fourth row is assigned with no score, so it enters neither total and the figure needed does not move for it. The weighted total already marked is 90 times 20 plus 80 times 15 plus 93 times 15 plus 78 times 20 plus 100 times 10, which is 6,955, and the course mark so far is 6,955 over the weight present of 80, which is 86.9375 and prints as 86.9%. To finish at 90 the outstanding 20 must carry (90 times 100 less 6,955) over 20, which is 102.25%, so the target needs more than full marks on the Final. To finish at 85 it must carry 77.25%, and to finish at 93 it must carry 117.25%."
 },
 "second_worked_example": {
  "why": "the weights coming to 85 rather than 100, which is where solving on an assumed 100 prints a wrong figure that no bound catches; and a marked category still holding an unmarked row beside a category with nothing marked at all.",
  "state": {
   "categoryCount": 2,
   "categories": [
    {
     "label": "Coursework",
     "weight": 45
    },
    {
     "label": "Exam",
     "weight": 40
    }
   ],
   "workCount": 3,
   "works": [
    {
     "label": "Essay",
     "categoryIndex": 0,
     "possible": 45,
     "earned": 36
    },
    {
     "label": "Seminar",
     "categoryIndex": 0,
     "possible": 10,
     "earned": null
    },
    {
     "label": "The exam",
     "categoryIndex": 1,
     "possible": 40,
     "earned": null
    }
   ],
   "targetCount": 2,
   "targets": [
    {
     "value": 70
    },
    {
     "value": 60
    }
   ]
  },
  "outputs": {
   "categoriesLive": 2,
   "worksLive": 3,
   "targetRowsLive": 2,
   "waiting": true,
   "courseMarkWithheld": false,
   "weightsOffTheHundred": true,
   "weightTypedTotal": 85,
   "weightPresent": 45,
   "weightOutstanding": 40,
   "weightedSumMarked": 3600,
   "markSoFar": 80,
   "earnedMarkedTotal": 36,
   "possibleMarkedTotal": 45,
   "worksMarked": 1,
   "worksAssignedUnmarked": 2,
   "worksNoPossible": 0,
   "worksNoCategory": 0,
   "worksOverPossible": 0,
   "categoriesMarked": 1,
   "categoriesOutstanding": 1,
   "categoriesNoPossible": 0,
   "categoriesAssignedOnly": 1,
   "markedHoldingUnmarked": 1,
   "targetsTyped": 2,
   "targetsBlank": 0,
   "targetsReached": 0,
   "targetsUnreachable": 0,
   "catWeight1": 45,
   "catEarned1": 36,
   "catPossible1": 45,
   "catPct1": 80,
   "catMarkedWorks1": 1,
   "catWeight2": 40,
   "catEarned2": 0,
   "catPossible2": 0,
   "catPct2": 0,
   "catMarkedWorks2": 0,
   "catWeight3": 0,
   "catEarned3": 0,
   "catPossible3": 0,
   "catPct3": 0,
   "catMarkedWorks3": 0,
   "catWeight4": 0,
   "catEarned4": 0,
   "catPossible4": 0,
   "catPct4": 0,
   "catMarkedWorks4": 0,
   "catWeight5": 0,
   "catEarned5": 0,
   "catPossible5": 0,
   "catPct5": 0,
   "catMarkedWorks5": 0,
   "catWeight6": 0,
   "catEarned6": 0,
   "catPossible6": 0,
   "catPct6": 0,
   "catMarkedWorks6": 0,
   "target1Typed": 1,
   "target1Value": 70,
   "target1Needed": 58.75,
   "target2Typed": 1,
   "target2Value": 60,
   "target2Needed": 37.5,
   "target3Typed": 0,
   "target3Value": 0,
   "target3Needed": 0
  },
  "read_it_back": "The two typed weights come to 85 and the page says so and scales nothing. Coursework holds one marked row of 36 out of 45, which is 80.0%, and an unmarked row of 10 points which enters no total. Coursework is marked, so its 45 is present; the exam category has nothing marked, so its 40 is outstanding. The course mark so far is 80 times 45 over 45, which is 80.0%. To finish at 70 the outstanding 40 must carry (70 times 85 less 3,600) over 40, which is 58.75%: solved against 100 instead of against the reader's own 85 it would have read 85.0%, which is wrong by more than 26 points. The second target of 60 asks less of what is left: 60 times 85 is 5,100, the work already marked comes to 3,600 weighted points, and the outstanding 40 must therefore carry 37.5%. The page prints its own words in place of a figure only where the work already marked carries the target by itself, which neither of these two does."
 },
 "third_worked_example": {
  "why": "the second spelling, so an agent can see that the workbook's own rows are the same function. Two weight rows, three pieces of work, one of them unmarked, and one target, handed in as a list of entries with the categories as plain labels.",
  "state": {
   "categories": [
    "Homework",
    "Quizzes"
   ],
   "entries": [
    {
     "kind": "A category weight",
     "what": "",
     "category": "1 Homework",
     "weight": 40
    },
    {
     "kind": "A category weight",
     "what": "",
     "category": "2 Quizzes",
     "weight": 60
    },
    {
     "kind": "A piece of work",
     "what": "HW 1",
     "category": "1 Homework",
     "possible": 20,
     "earned": 18
    },
    {
     "kind": "A piece of work",
     "what": "HW 2",
     "category": "1 Homework",
     "possible": 20,
     "earned": null
    },
    {
     "kind": "A piece of work",
     "what": "Quiz 1",
     "category": "2 Quizzes",
     "possible": 30,
     "earned": 27
    },
    {
     "kind": "The first target",
     "what": "",
     "target": 95
    }
   ]
  },
  "outputs": {
   "categoriesLive": 2,
   "worksLive": 3,
   "targetRowsLive": 1,
   "waiting": true,
   "courseMarkWithheld": false,
   "weightsOffTheHundred": false,
   "weightTypedTotal": 100,
   "weightPresent": 100,
   "weightOutstanding": 0,
   "weightedSumMarked": 9000,
   "markSoFar": 90,
   "earnedMarkedTotal": 45,
   "possibleMarkedTotal": 50,
   "worksMarked": 2,
   "worksAssignedUnmarked": 1,
   "worksNoPossible": 0,
   "worksNoCategory": 0,
   "worksOverPossible": 0,
   "categoriesMarked": 2,
   "categoriesOutstanding": 0,
   "categoriesNoPossible": 0,
   "categoriesAssignedOnly": 0,
   "markedHoldingUnmarked": 1,
   "targetsTyped": 1,
   "targetsBlank": 0,
   "targetsReached": 0,
   "targetsUnreachable": 1,
   "catWeight1": 40,
   "catEarned1": 18,
   "catPossible1": 20,
   "catPct1": 90,
   "catMarkedWorks1": 1,
   "catWeight2": 60,
   "catEarned2": 27,
   "catPossible2": 30,
   "catPct2": 90,
   "catMarkedWorks2": 1,
   "catWeight3": 0,
   "catEarned3": 0,
   "catPossible3": 0,
   "catPct3": 0,
   "catMarkedWorks3": 0,
   "catWeight4": 0,
   "catEarned4": 0,
   "catPossible4": 0,
   "catPct4": 0,
   "catMarkedWorks4": 0,
   "catWeight5": 0,
   "catEarned5": 0,
   "catPossible5": 0,
   "catPct5": 0,
   "catMarkedWorks5": 0,
   "catWeight6": 0,
   "catEarned6": 0,
   "catPossible6": 0,
   "catPct6": 0,
   "catMarkedWorks6": 0,
   "target1Typed": 1,
   "target1Value": 95,
   "target1Needed": 0,
   "target2Typed": 0,
   "target2Value": 0,
   "target2Needed": 0,
   "target3Typed": 0,
   "target3Value": 0,
   "target3Needed": 0
  },
  "read_it_back": "The two weight rows give Homework 40 and Quizzes 60, which come to 100. Homework holds one marked row of 18 out of 20 and one unmarked row of 20 points, so its percentage is 90.0% on the marked row alone and its 40 is present. Quizzes holds one marked row of 27 out of 30, which is 90.0%, so its 60 is present too. Every typed weight is present, so the weight outstanding is nothing, the course mark so far is 90.0%, and the one target of 95 has no remaining weight to be carried on: the page prints its own words for it rather than dividing by nothing."
 },
 "limits": [
  "No grading scale of any kind: no letter grade, no plus or minus band, no pass mark, no fail mark, no curve and no rounding convention presented as anybody's standard. Institutions differ about what a percentage is worth, and this engine supplies percentages only.",
  "No grade point average, grade point, quality point, credit, credit hour, honours, advanced or international conversion, and no conversion of any kind.",
  "No claim about what any school, college, university, teacher, syllabus, examination board, district or jurisdiction does, requires or computes.",
  "No transcript, class rank, honour roll, dean's list, probation, progression, graduation, admission, scholarship, financial-aid or athletic-eligibility figure or claim, and no statement that any mark is good, enough, passing, safe or at risk.",
  "No study, revision, tutoring, effort, time-management or motivation advice, no claim about how to raise a mark, and no claim about what anybody can or should achieve.",
  "No health, behavioural, developmental or learning-difficulty claim of any kind.",
  "No verdict, colour, flag, star, rank or league table, and no comparison between people: the engine answers about one sheet of typed figures and nobody else's.",
  "No name of any kind is asked for or read: no student name, teacher, school, class, course code, student number, date of birth, email or date. A label per piece of work and per category is all there is.",
  "No money figure in any output: no price, rate, cost, fee, tuition or amount of any kind. The one price on the page is the workbook's own.",
  "It will not turn one overall percentage into a per-category sheet, because one figure cannot say which category each of its parts belonged to. The two-category line in the page's input block is the most that figure can do.",
  "It will not correct a reader's weights. Where they do not come to 100 it says what they come to and solves everything on that total.",
  "Nothing is stored and nothing is sent. The page runs this engine in the reader's own browser, keeps no figure when the tab closes, and has no account."
 ]
}
