User Tools

Site Tools


scoutradioz:howto_scouting_forms

Form design reference

Check the "Legacy" section for the information about how the pre-improvements definitions worked (prior JSON files will still work in the new system! it's backwards-compatible)

If you come across any issues trying to use the documentation below, please let us know on the Scoutradioz Discord


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 as you write it (although the new editor built into Scoutradioz will also do syntax checking!)

Each item in the form must have a type attribute, and most (but not all) also require an id attribute (which must be unique in the form). The following types are allowed.

First, you can use entries of header, subheader, and spacer to organize and annotate the user interface (these don't require IDs):

  • header
  • subheader
  • image (new!)
  • spacer

Next, you can specify several kinds of data collection, several of which can be further customized with additional attributes:

  • checkbox
  • counter with additional attributes 'allow_negative' and 'variant' (which can be “standard” or “bad”)
  • slider with additional attribute 'variant' (which can be “standard” or “time”)
  • multiselect
  • textblock

And last but certainly not least, you can also define derived metrics. These are like formula cells in a spreadsheet! They automatically calculate new values based on one or more other values:

  • derived

UI Organization and Annotation

header: This is a header element. It require a label attribute, containing the text to display. Example:

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

subheader: This is a subheader element. It also requires a label attribute, containing the text to display. Example:

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

image: This allows you to insert an image into the form. Images should be first uploaded via the organization management page using the “Manage form images” button (which takes you to the page to upload and manage images). When you upload an image, you give it a unique ID (e.g. “pit_auto_path_options”).

You can then refer to that ID as the image_id attribute of the “image” block, containing the ID associated with the image to display. Example:

{
    "type": "image",
    "image_id": "pit_auto_path_options"
}

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

{
    "type": "spacer"
}

Data Collection

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:

counter elements should also have two additional attributes to describe different behaviors:

  • "variant": "bad" (vs. "good") - this flips the “good”/“bad” colors; suitable for tracking bad things like misses, fouls, etc.
  • "allow_negative": true (vs. "false") - normally counters can't go below zero (0) but sometimes it can be useful to allow a negative number (e.g., if your robot de-scores something their alliance partner had scored)
{
    "type": "counter",
    "label": "Cargo scored by robot",
    "id": "autoHighScored",
    "allow_negative": true,
    "variant": "good"
}

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:

slider elements should also have an additional attribute to describe what kind it is:

  • "variant": "standard" - this shows the value of the slider as a number
    • or : "time" - this shows the value of the slider as MM:SS (suitable for tracking the time on the clock when something occurred, for example)
{
    "type": "slider",
    "label": "How would you rate this robot's defensive ability?",
    "id": "autoPoints",
    "options": {
        "min": 0,
        "max": 5,
        "step": 1
    },
    "variant": "standard"
}

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"
}

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"
},

Derived metrics

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 a formula attribute.

In the formula, you write the metric as an expression based on the ids of other items in the layout. You can also reference the ids of other derived metrics, but only if the other derived metrics appear above this one in the list.

The following mathematic operators are supported:

  • +, -, *, /, ==, >=, <=, <, >
  • ^ for exponents, e.g. a ^ b resolves to Math.pow(a, b)
  • || for a logical OR assignment, e.g. a || b resolves to a ? a : b. In the previous system, this would be { "operator": "conditional", "operands": ["$a", "$a", "$b"] }

The following functions are supported:

  • min(a, b): Returns the mathematical min of the two arguments, e.g. min(1, 2) resolves to 1
  • max(a, b): Returns the mathematical max of the two arguments, e.g. max(1, 2) resolves to 2
  • log(a, b): Returns the log base b of a, e.g. log(a, 2.71828) returns the natural log (ln) of a, and log(100, 10) returns 2
  • abs(a): Returns absolute value of a
  • if(cond1, ifTrue, ifFalse), if(cond1, if1True, cond2, if2True, ..., ifFalse): Equivalent to a chain of ternary operators as long as you want, e.g. if(a, 1, b, 2, c, 3, 0) returns 2 if a is falsy and b is true, or 0 if all three (a, b, and c) are falsy. (if a value is the string 'false', the string '0', the number 0, or NaN, it is falsy. Empty strings get parsed as NaN, so they are also falsy. All other values are truthy.)
  • multiselect(variable, value1, ifValue1, value2, ifValue2, ..., [ifNoneMatch]): Used to assign values to a multiselect input. This is the only derived metric that allows strings as an input. Equivalent to switch (variable) { case value1: return ifValue1; case value2: return ifValue2; default: return ifNoneMatch }. e.g. assume we have a multiselect input with the id “myMultiselect” and the options “ValueA” and “ValueB”, where ValueA is worth 2 points and ValueB is worth 6 points. The formula to assign point values to this input would be multiselect(myMultiselect, 'ValueA', 2, 'valueb', 6). The string matching is case insensitive and ifNoneMatch is optional in the end.

Examples:

  {
    "type": "derived",
    "formula": "2*(didStartingZone + autoAmp) + 5*autoSpeaker",
    "id": "totalAutoPoints"
  },
  {
    "type": "derived",
    "formula": "2*teleopSpeaker + teleopAmpSpeaker*5 + teleopAmp",
    "id": "totalTeleopPoints"
  },
  {
    "type": "derived",
    "formula": "5*teleopTrap + endgameSpotlit + multiselect(endgameStage, 'not parked', 0, 'parked (at least partially in the stage zone)', 1, 'on stage (fully off the Ground)', 3)",
    "id": "totalEndgamePoints"
  },
  {
    "type": "derived",
    "formula": "totalAutoPoints + totalTeleopPoints + totalEndgamePoints",
    "id": "contributedPoints"
  },
  {
    "type": "derived",
    "formula": "autoAmp + autoSpeaker",
    "id": "totalAutoNotes"
  },
  {
    "type": "derived",
    "formula": "teleopAmp + teleopSpeaker + teleopAmpSpeaker",
    "id": "totalTeleopNotes"
  },
  {
    "type": "derived",
    "formula": "autoAmp + teleopAmp",
    "id": "totalAmpNotes"
  },
  {
    "type": "derived",
    "formula": "autoSpeaker + teleopSpeaker",
    "id": "totalSpeakerNotes"
  },
  {
    "type": "derived",
    "formula": "autoAmp + autoSpeaker + teleopAmp + teleopSpeaker + teleopAmpSpeaker",
    "id": "totalNotes"
  },
  {
    "type": "derived",
    "formula": "((135 - max(onStageTimeStart, onStageTimeEnd)) / (totalTeleopNotes + teleopTrap)) || 160",
    "id": "cycleTime"
  },
  {
    "type": "derived",
    "formula": "14.646447 - log(cycleTime, 1.4142136)",
    "id": "cycleSpeedFactor"
  },
  {
    "type": "derived",
    "formula": "14.646447 - log(max(5, abs(onStageTimeEnd - onStageTimeStart)), 1.4142136)",
    "id": "onStageSpeedFactor"
  },

Flags

TODO: CONVERT TO FORMULA FOR NEW SYSTEM

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🚩"
},

Legacy JSON stuff

Below here are the old docs on how to do things in the old system. Preserved for posterity.

For backwards-compatibility these will still work even after the platform has been updated to enable the new system.

See the forms archive for examples of JSON from before the change, e.g. Team 102's match form JSON using the old system

IDs

Every item in the old system needed a unique ID, even labels. For example,

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

'operators' in 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).

Legacy types

Older JSON layouts may have some of these items; these will still work for now but are deprecated and may stop being supported later:

  • h2 (now 'header')
  • h3 (now 'subheader')
  • counterallownegative (this is now a counter with additional attributes)
  • badcounter (this is now a counter with additional attributes)

And the legacy 'derived' metrics system (described above) was a complex series of 'operations' objects, now replaced by 'formula'.

scoutradioz/howto_scouting_forms.txt · Last modified: 2025/02/10 11:22 by moconnell@team102.org

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki