Skip to content

[DELETE] HowTo Form JSON

Jordan Lees edited this page May 15, 2024 · 1 revision

The JSON layout provided must be an array, i.e. wrapped in [], where each item in the array is an object, i.e. wrapped in {}. We recommend that you use a text editor like VS Code that automatically checks for syntax errors.

Each item must have a type attribute. The following types are allowed:

  • checkbox
  • counter
  • counterallownegative
  • badcounter
  • textblock
  • slider
  • timeslider
  • multiselect
  • derived
  • spacer
  • h2
  • h3

spacer: This one simply inserts a spacer at this point in the form. It DOES NOT require an id or label. Example:

{
	"type": "spacer"
}

h2: This is a header element. It DOES NOT require an id but DOES require a label attribute, containing the text to display. Example:

{
	"type": "h2",
	"label": "Autonomous"
}

h3: This is a subheader element. It DOES NOT require an id but DOES require a label attribute, containing the text to display. Example:

{
	"type": "h3",
	"label": "Upper Hub"
}

checkbox: This is a checkbox, i.e. a yes/no question. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. Example:

{
	"type": "checkbox",
	"label": "Did they taxi (move out from the tarmac)?",
	"id": "didTaxi"
}

counter: This is a counter, i.e. a number that can be increased or decreased which cannot go below zero. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. Example:

{
	"type": "counter",
	"label": "Cargo scored by robot",
	"id": "autoHighScored"
}

counterallownegative: This is a counter, i.e. a number that can be increased or decreased and CAN go below zero. An example for when you may allow it to go below zero is if a robot moves a game piece away from where it needs to rest to score points, effectively un-scoring a point. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. Example:

{
	"type": "counterallownegative",
	"label": "Cargo scored by robot",
	"id": "autoHighScored"
}

textblock: This is a text block, i.e. a multi-line text input. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. Example:

{
	"type": "textblock",
	"label": "If you had to pick a favorite FRC team other than the team you are on which would it be and why?",
	"id": "iceBreakerAnswer"
},

slider: This is a slider, i.e. a number input that can be adjusted by dragging a slider. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. It also requires an options attribute, containing min, max, and step. step is allowed to be negative, in which case, the slider will be reversed. Example:

{
	"type": "slider",
	"label": "How would you rate this robot's defensive ability?",
	"id": "autoPoints",
	"options": {
		"min": 0,
		"max": 5,
		"step": 1
	}
}

timeslider: This is a slider, i.e. a number input that can be adjusted by dragging a slider. Timesliders display in minutes and seconds instead of just a number. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. It also requires an options attribute, containing min, max, and step. Example:

{
	"type": "timeslider",
	"label": "How much time was on the clock when they started balancing?",
	"id": "chargingTimeStart",
	"options": {
		"min": 0,
		"max": 90,
		"step": -5
	}
}

multiselect: This is a multi-select, i.e. a dropdown question with multiple possible answers to pick between. It DOES require a unique id in camelCase, and requires a label attribute, containing the question. It also requires an options attribute, containing an array of strings. Example:

{
	"type": "multiselect",
	"label": "Charging station status",
	"options": [
		"N/A",
		"Docked",
		"Engaged"
	],
	"id": "autoChargingStation"
}

derived: This is a derived value, i.e. a value that is calculated based on other values. It DOES require a unique id in camelCase, but does NOT require a label. It requires an operations attribute, containing an array of operations. Speaking of...

Derived metrics

Each operation must have an operator attribute, which is a string. All operators EXCEPT multiselect must have an operands attribute, which is an array. The operator attribute can be one of the following:

  • sum: Sums all the operands. Operands: [a, b, c, ...], where each can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout.
  • multiply: Multiplies all the operands together. Operands: [a, b, c, ...], where each can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout.
  • subtract: Subtracts the second operand from the first. Operands: [minuend, subtrahend], where each can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout.
  • divide: Divides the first operand by the second. Operands: [dividend, divisor], where each can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout. If the divisor is zero, then the result will be zero, which is not mathematically accurate but results in more helpful metrics.
  • min or max: Returns the minimum or maximum of the two operands. Operands: [a, b], where each can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout.
  • log: Returns the logarithm of the first operand with base of the second. Operands: [x, base], where base must be a number, but x can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout.
  • abs: Returns the absolute value of the operand. Operands: [x], where x can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout.
  • condition: Returns the second operand if the first operand is true, or the third operand if the first operand is false. Operands: [boolean, valueIfTrue, valueIfFalse], where the first operand must be a reference to an intermediate variable earlier in the chain of operations or the id of another item in the form layout; and the second and third operands can be either a number, a reference to an intermediate variable in the chain of operations, or the id of another item in the form layout.
  • gt (greater than), gte (greater than or equal), lt (less than), lte (less than or equal), eq (equal), ne (not equal): Returns 1 if the comparison is true, or 0 if the comparison is false. Operands: [a, b], where each can be either a number, a reference to an intermediate variable earlier in the chain of operations (a string starting with $), or the id of another item in the form layout. If used with a condition operation further down the chain, the 1 will be treated as true and 0 treated as false.
  • multiselect: Since the multiselect form option gives a list of strings, and you can't do math with strings, this derived operation lets you apply a numerical value to a given multiselect option. Instead of an operands attribute, the multiselect requires a quantifiers attribute, which is an object. In the earlier example where charging station status has the options ["N/A", "Docked", and "Engaged"], the associated quantifiers attribute would be "quantifiers": { "N/A": 0, "Docked": 8, "Engaged": 12 } in order to attach point values to the 2023 game's charging station.

The result of the LAST operation in the chain will be the final result that endss up in the metric. All preceeding operations MUST have an as field, which will let it be referenced by operands later in the chain.

In operations that expect a number (most operations), a true/false (e.g. the result of a checkbox) will be treated as 1 and 0, respectively.

Important: You SHOULD NOT fetch the final value of OTHER derived metrics inside the operands of a derived metric (i.e. you can only fetch the values of a checkbox, counter, slider, multiselect, etc). It would be computationally (and programmatically) complex to track the dependencies of certain derived metrics on others, and it takes nanoseconds to calculate each step in the operation chain, so the operations inside a derived metric should be "self contained", i.e. not depend on others.

Example

Let's break down a relatively simple derived metric. For the 2022 and 2023 seasons, Team 102 created a metric called "reliability factor" which gives an at-a-glance view of the robot's overall reliability when you look at their average scores. 102's form included a checkbox with the id "diedDuringMatch" and a checkbox with the id "recoveredFromFreeze", whose meanings should be self explanatory. If the robot DID NOT die, the metric should be 1, if they died WITHOUT freezing, the metric should be 0, and if they died BUT RECOVERED DURING THE MATCH, the metric should be 0.5.

Since we know a checked checkbox is treated as a 1 and an unchecked checkbox is treated as a 0, we can express this metric with the formula: (0.5 * recoveredFromFreeze) + (1 - diedDuringMatch). Since we don't expect the students to check recoveredFromFreeze when diedDuringMatch is not checked, we do not expect the result to be greater than 1. (More on that later...)

Here's the derived metric for reliability factor:

{
	"type": "derived",
	"operations": [
		{
			"operator": "multiply",
			"operands": [ 0.5, "recoveredFromFreeze" ],
			"as": "recover"
		},
		{
			"operator": "subtract",
			"operands": [ 1, "diedDuringMatch" ],
			"as": "1minusdied"
		},
		{
			"operator": "sum",
			"operands": [ "$1minusdied", "$recover" ]
		}
	],
	"id": "reliabilityFactor"
}

The first operation calculates the value inside the first set of parentheses (0.5 * recoveredFromFreeze) by using multiply with the hardcoded value 0.5 and fetching the value from recoveredFromFreeze, and then similarly the second operation calculates the value inside the second set of parentheses, but this time using the subtract operand. Note the use of as in the first two operations. Additionally, note that the contents of the as field DO NOT start with a $.

The last operation simply sums them together. Note that both items in the operands field start with a $. The $ at the start tells the computing function to check from the pool of intermediate variables INSTEAD of checking from the pool of completed form elements (checkboxes, sliders, etc).

Flags

As an idea, you can use derived metrics to catch "impossible" scenarios as a way to check and see if the scouters might be putting in bogus data or making mistakes. In the previous example, we noted that the result should never be 1.5. How do we catch that?

We can do it in just 2 operations by just checking (recoveredFromFreeze - diedDuringMatch) > 0. If recoveredFromFreeze = true (1) and diedDuringMatch = false (0), then the result will be 1. Otherwise, the result will be 0. The scouting lead can then at a glance see if there's potential bad data if the "Recover From Freeze🚩" metric is ever non zero.

{
	"type": "derived",
	"operations": [
		{
		"operator": "subtract",
		"operands": [ "recoveredFromFreeze", "diedDuringMatch" ],
		"as": "recMinusDied"
	},
	{
		"operator": "gt",
		"operands": [ "$recMinusDied", 0 ]
	}
	],
	"display_as": "number",
	"id": "recoverWithoutFreeze🚩"
},