// Numbas version: finer_feedback_settings {"name": "Centroid of a composite area: rectangles and triangles", "extensions": ["geogebra", "sheets", "quantities"], "custom_part_types": [{"source": {"pk": 19, "author": {"name": "William Haynes", "pk": 2530}, "edit_page": "/part_type/19/edit"}, "name": "Engineering Accuracy with units", "short_name": "engineering-answer", "description": "

A value with units marked right if within an adjustable % error of the correct value.  Marked close if within a wider margin of error.

", "help_url": "", "input_widget": "string", "input_options": {"correctAnswer": "siground(settings['correctAnswer'],4)", "hint": {"static": true, "value": ""}, "allowEmpty": {"static": true, "value": true}}, "can_be_gap": true, "can_be_step": true, "marking_script": "mark:\nswitch( \n right and good_units and right_sign, add_credit(1.0,'Correct.'),\n right and good_units and not right_sign, add_credit(settings['C2'],'Wrong sign.'),\n right and right_sign and not good_units, add_credit(settings['C2'],'Correct value, but wrong or missing units.'),\n close and good_units, add_credit(settings['C1'],'Close.'),\n close and not good_units, add_credit(settings['C3'],'Answer is close, but wrong or missing units.'),\n incorrect('Wrong answer.')\n)\n\ninterpreted_answer:\nqty(student_scalar, student_units)\n\n\n\ncorrect_quantity:\nsettings[\"correctAnswer\"]\n\n\n\ncorrect_units:\nunits(correct_quantity)\n\n\nallowed_notation_styles:\n[\"plain\",\"en\"]\n\nmatch_student_number:\nmatchnumber(studentAnswer,allowed_notation_styles)\n\nstudent_scalar:\nmatch_student_number[1]\n\nstudent_units:\nreplace_regex('ohms','ohm',\n replace_regex('\u00b0', ' deg',\n replace_regex('-', ' ' ,\n studentAnswer[len(match_student_number[0])..len(studentAnswer)])),\"i\")\n\ngood_units:\ntry(\ncompatible(quantity(1, student_units),correct_units),\nmsg,\nfeedback(msg);false)\n\n\nstudent_quantity:\nswitch(not good_units, \n student_scalar * correct_units, \n not right_sign,\n -quantity(student_scalar, student_units),\n quantity(student_scalar,student_units)\n)\n \n\n\npercent_error:\ntry(\nscalar(abs((correct_quantity - student_quantity)/correct_quantity))*100 \n,msg,\nif(student_quantity=correct_quantity,0,100))\n \n\nright:\npercent_error <= settings['right']\n\n\nclose:\nright_sign and percent_error <= settings['close']\n\nright_sign:\nsign(student_scalar) = sign(correct_quantity)", "marking_notes": [{"name": "mark", "description": "This is the main marking note. It should award credit and provide feedback based on the student's answer.", "definition": "switch( \n right and good_units and right_sign, add_credit(1.0,'Correct.'),\n right and good_units and not right_sign, add_credit(settings['C2'],'Wrong sign.'),\n right and right_sign and not good_units, add_credit(settings['C2'],'Correct value, but wrong or missing units.'),\n close and good_units, add_credit(settings['C1'],'Close.'),\n close and not good_units, add_credit(settings['C3'],'Answer is close, but wrong or missing units.'),\n incorrect('Wrong answer.')\n)"}, {"name": "interpreted_answer", "description": "A value representing the student's answer to this part.", "definition": "qty(student_scalar, student_units)\n\n"}, {"name": "correct_quantity", "description": "", "definition": "settings[\"correctAnswer\"]\n\n"}, {"name": "correct_units", "description": "", "definition": "units(correct_quantity)\n"}, {"name": "allowed_notation_styles", "description": "", "definition": "[\"plain\",\"en\"]"}, {"name": "match_student_number", "description": "", "definition": "matchnumber(studentAnswer,allowed_notation_styles)"}, {"name": "student_scalar", "description": "", "definition": "match_student_number[1]"}, {"name": "student_units", "description": "

Modify the unit portion of the student's answer by

\n

1. replacing \"ohms\" with \"ohm\"  case insensitive

\n

2. replacing '-' with ' ' 

\n

3. replacing '°' with ' deg' 

\n

to allow answers like 10 ft-lb and 30°

", "definition": "replace_regex('ohms','ohm',\n replace_regex('\u00b0', ' deg',\n replace_regex('-', ' ' ,\n studentAnswer[len(match_student_number[0])..len(studentAnswer)])),\"i\")"}, {"name": "good_units", "description": "", "definition": "try(\ncompatible(quantity(1, student_units),correct_units),\nmsg,\nfeedback(msg);false)\n"}, {"name": "student_quantity", "description": "

This fixes the student answer for two common errors.  

\n

If student_units are wrong  - replace with correct units

\n

If student_scalar has the wrong sign - replace with right sign

\n

If student makes both errors, only one gets fixed.

", "definition": "switch(not good_units, \n student_scalar * correct_units, \n not right_sign,\n -quantity(student_scalar, student_units),\n quantity(student_scalar,student_units)\n)\n \n"}, {"name": "percent_error", "description": "", "definition": "try(\nscalar(abs((correct_quantity - student_quantity)/correct_quantity))*100 \n,msg,\nif(student_quantity=correct_quantity,0,100))\n "}, {"name": "right", "description": "", "definition": "percent_error <= settings['right']\n"}, {"name": "close", "description": "

Only marked close if the student actually has the right sign.

", "definition": "right_sign and percent_error <= settings['close']"}, {"name": "right_sign", "description": "", "definition": "sign(student_scalar) = sign(correct_quantity) "}], "settings": [{"name": "correctAnswer", "label": "Correct Quantity.", "help_url": "", "hint": "The correct answer given as a JME quantity.", "input_type": "code", "default_value": "", "evaluate": true}, {"name": "right", "label": "% Accuracy for right.", "help_url": "", "hint": "Question will be considered correct if the scalar part of the student's answer is within this % of correct value.", "input_type": "code", "default_value": "0.2", "evaluate": true}, {"name": "close", "label": "% Accuracy for close.", "help_url": "", "hint": "Question will be considered close if the scalar part of the student's answer is within this % of correct value.", "input_type": "code", "default_value": "1.0", "evaluate": true}, {"name": "C1", "label": "Close with units.", "help_url": "", "hint": "Partial Credit for close value with appropriate units.  if correct answer is 100 N and close is ±1%,
99  N is accepted.", "input_type": "percent", "default_value": "75"}, {"name": "C2", "label": "No units or wrong sign", "help_url": "", "hint": "Partial credit for forgetting units or using wrong sign.
If the correct answer is 100 N, both 100 and -100 N are accepted.", "input_type": "percent", "default_value": "50"}, {"name": "C3", "label": "Close, no units.", "help_url": "", "hint": "Partial Credit for close value but forgotten units.
This value would be close if the expected units were provided.  If the correct answer is 100 N, and close is ±1%,
99 is accepted.", "input_type": "percent", "default_value": "25"}], "public_availability": "always", "published": true, "extensions": ["quantities"]}, {"source": {"pk": 242, "author": {"name": "Christian Lawson-Perfect", "pk": 7}, "edit_page": "/part_type/242/edit"}, "name": "Spreadsheet", "short_name": "spreadsheet", "description": "

An editable spreadsheet. Ranges of cells can be disabled, and you can specify ranges of cells to be marked. A cell is marked correct if its value is equal to the value in the expected answer spreadsheet.

", "help_url": "", "input_widget": "spread-sheet", "input_options": {"correctAnswer": "settings[\"correct_answer\"]", "hint": {"static": true, "value": ""}, "initial_sheet": {"static": false, "value": "disable_cells(settings[\"initial_sheet\"], settings[\"disable_ranges\"])"}}, "can_be_gap": true, "can_be_step": true, "marking_script": "mark:\nif(sum(mark_ranges)=0,\n incorrect(),\n apply(mark_ranges)\n)\n\ninterpreted_answer:\nstudentAnswer\n\nrange_cells:\nmap(parse_range(ref),ref,values(settings[\"mark_ranges\"]))\n\ntotal_cells:\nlen(flatten(range_cells))\n\nrange_weights:\nswitch(\n settings[\"marking_method\"]=\"per_cell\",\n map(len(r)/total_cells, r, range_cells),\n // otherwise, mark per range\n repeat(1/len(range_cells), len(range_cells))\n)\n\nmark_ranges:\nmap(\n let(\n range_credit,\n sum(map(\n let(\n correctCellString, correctAnswer[c],\n correctCellNumber, parsenumber(correctCellString, notation_styles),\n studentCellString, studentAnswer[c],\n studentCellNumber, parsenumber(studentCellString, notation_styles),\n award(\n 1/len(cells), \n switch(\n correctCellString=\"\",\n isnan(studentCellNumber) or studentCellString=\"\",\n isnan(correctCellNumber),\n lower(correctCellString) = lower(studentCellString)\n ,\n abs(studentCellNumber - if(isnan(correctCellNumber),0,correctCellNumber)) <= settings[\"tolerance\"]\n )\n )\n ),\n c,\n cells\n )),\n message,\n switch(\n range_credit=0,\n if(len(cells)=1, \"This entry is incorrect.\", \"All entries in this range are incorrect.\"),\n range_credit=1,\n if(len(cells)=1, \"This entry is correct.\", \"All entries in this range are correct.\"),\n //otherwise\n \"Some entries in this range are correct.\"\n ),\n assert(len(cells)=0, add_credit(range_credit*w, \"{name}: \"+message)); \n range_credit\n ),\n [cells,w,name],\n zip(range_cells, range_weights, keys(settings[\"mark_ranges\"]))\n)\n\nnotation_styles:\n[\"plain\",\"si-en\"]\n\ncorrectAnswer:\nsettings[\"correct_answer\"]", "marking_notes": [{"name": "mark", "description": "This is the main marking note. It should award credit and provide feedback based on the student's answer.", "definition": "if(sum(mark_ranges)=0,\n incorrect(),\n apply(mark_ranges)\n)"}, {"name": "interpreted_answer", "description": "A value representing the student's answer to this part.", "definition": "studentAnswer"}, {"name": "range_cells", "description": "

For each range to be marked, the addresses of the cells in that range.

", "definition": "map(parse_range(ref),ref,values(settings[\"mark_ranges\"]))"}, {"name": "total_cells", "description": "

The total number of cells to be marked. Cells in overlapping ranges will be counted once for each range they're in.

", "definition": "len(flatten(range_cells))"}, {"name": "range_weights", "description": "

The weight of each range, as a proportion of the available credit.

", "definition": "switch(\n settings[\"marking_method\"]=\"per_cell\",\n map(len(r)/total_cells, r, range_cells),\n // otherwise, mark per range\n repeat(1/len(range_cells), len(range_cells))\n)"}, {"name": "mark_ranges", "description": "

Mark each of the ranges specified by the question author.

", "definition": "map(\n let(\n range_credit,\n sum(map(\n let(\n correctCellString, correctAnswer[c],\n correctCellNumber, parsenumber(correctCellString, notation_styles),\n studentCellString, studentAnswer[c],\n studentCellNumber, parsenumber(studentCellString, notation_styles),\n award(\n 1/len(cells), \n switch(\n correctCellString=\"\",\n isnan(studentCellNumber) or studentCellString=\"\",\n isnan(correctCellNumber),\n lower(correctCellString) = lower(studentCellString)\n ,\n abs(studentCellNumber - if(isnan(correctCellNumber),0,correctCellNumber)) <= settings[\"tolerance\"]\n )\n )\n ),\n c,\n cells\n )),\n message,\n switch(\n range_credit=0,\n if(len(cells)=1, \"This entry is incorrect.\", \"All entries in this range are incorrect.\"),\n range_credit=1,\n if(len(cells)=1, \"This entry is correct.\", \"All entries in this range are correct.\"),\n //otherwise\n \"Some entries in this range are correct.\"\n ),\n assert(len(cells)=0, add_credit(range_credit*w, \"{name}: \"+message)); \n range_credit\n ),\n [cells,w,name],\n zip(range_cells, range_weights, keys(settings[\"mark_ranges\"]))\n)"}, {"name": "notation_styles", "description": "

Accepted number notation styles for a value in an individual cell.

", "definition": "[\"plain\",\"si-en\"]"}, {"name": "correctAnswer", "description": "", "definition": "settings[\"correct_answer\"]"}], "settings": [{"name": "initial_sheet", "label": "Initial sheet", "help_url": "", "hint": "A spreadsheet object giving the initial state of the sheet that the student should fill in.", "input_type": "code", "default_value": "", "evaluate": true}, {"name": "correct_answer", "label": "Correct answer", "help_url": "", "hint": "A spreadsheet object representing a correct answer to the part.", "input_type": "code", "default_value": "", "evaluate": true}, {"name": "disable_ranges", "label": "Ranges to disable", "help_url": "", "hint": "A list of cell or range references, denoting the cells that should not be editable.", "input_type": "code", "default_value": "[]", "evaluate": true}, {"name": "mark_ranges", "label": "Ranges to mark", "help_url": "", "hint": "A dictionary of cell or range references, mapping names to ranges of cells, denoting the cells that should be compared for equality with the expected answer.", "input_type": "code", "default_value": "dict()", "evaluate": true}, {"name": "marking_method", "label": "Marking method", "help_url": "", "hint": "", "input_type": "dropdown", "default_value": "per_cell", "choices": [{"value": "per_cell", "label": "Each cell has the same weight"}, {"value": "per_range", "label": "Each range has the same weight"}]}, {"name": "tolerance", "label": "Allowed margin of error", "help_url": "", "hint": "", "input_type": "code", "default_value": "0", "evaluate": true}], "public_availability": "always", "published": true, "extensions": ["sheets"]}], "resources": [["question-resources/centroid-t-r.ggb", "centroid-t-r.ggb"]], "navigation": {"allowregen": true, "showfrontpage": false, "preventleave": false, "typeendtoleave": false}, "question_groups": [{"pickingStrategy": "all-ordered", "questions": [{"name": "Centroid of a composite area: rectangles and triangles", "tags": ["centroids", "first moment of area", "mechanics", "Mechanics", "statics", "Statics"], "metadata": {"description": "

Find the centroid of a shape made up of a rectangle and two triangles.

", "licence": "Creative Commons Attribution-NonCommercial 4.0 International"}, "statement": "

Determine the coordinates of the centroid of a polygon made up of a ({b0} $\\times$ {h0}) rectangle, {if(sign1='+', 'plus', 'minus')} a ({b1} $\\times$ {h1}) triangle, {if(sign2='+', 'plus', 'minus')} a ({b2} $\\times$ {h2}) triangle.  Length units are [{units}].

\n

{applet()}

\n

{answer_sheet}

", "advice": "
\n

$\\qquad\\bar{x} = \\dfrac{Q_y}{A} =  \\dfrac{\\Sigma A_i\\bar{x}_i}{\\Sigma A_i}=   \\dfrac{\\var{siground(QT[0],4)}}{\\var{AT}}  =\\var{display(xbar)}$

\n

$\\qquad \\bar{y} = \\dfrac{Q_x}{A} =  \\dfrac{\\Sigma A_i \\bar{y}_i}{\\Sigma A_i}=  \\dfrac{\\var{siground(QT[1],4)}}{\\var{AT}}  =\\var{display(ybar)}$

\n
", "rulesets": {}, "extensions": ["geogebra", "quantities", "sheets"], "builtin_constants": {"e": true, "pi,\u03c0": true, "i": true, "j": false}, "constants": [], "variables": {"A": {"name": "A", "group": "Ungrouped variables", "definition": "vector(0,random(2..12))", "description": "", "templateType": "anything", "can_override": false}, "C2": {"name": "C2", "group": "geometry", "definition": "vector(A[0],B[1])+(vector(B[0],A[1])-vector(A[0],B[1]))/3 ", "description": "", "templateType": "anything", "can_override": false}, "A0": {"name": "A0", "group": "geometry", "definition": "b0 h0", "description": "", "templateType": "anything", "can_override": false}, "A2": {"name": "A2", "group": "geometry", "definition": "b2 h2/2 if(sign2='+',1,-1)", "description": "", "templateType": "anything", "can_override": false}, "b0": {"name": "b0", "group": "geometry", "definition": "B[0]", "description": "", "templateType": "anything", "can_override": false}, "C": {"name": "C", "group": "Ungrouped variables", "definition": "vector(random(2..12),0)", "description": "", "templateType": "anything", "can_override": false}, "b2": {"name": "b2", "group": "geometry", "definition": "b0", "description": "", "templateType": "anything", "can_override": false}, "C1": {"name": "C1", "group": "geometry", "definition": "vector(B[0],C[1])+(vector(C[0],B[1])-vector(B[0],C[1]))/3 ", "description": "", "templateType": "anything", "can_override": false}, "A1": {"name": "A1", "group": "geometry", "definition": "b1 h1 /2 if(sign1='+',1,-1)", "description": "", "templateType": "anything", "can_override": false}, "ybar": {"name": "ybar", "group": "Unnamed group", "definition": "(a0 c0[1] + a1 c1[1] + a2 c2[1])/AT qty(1,units)", "description": "", "templateType": "anything", "can_override": false}, "h1": {"name": "h1", "group": "geometry", "definition": "h0", "description": "", "templateType": "anything", "can_override": false}, "B": {"name": "B", "group": "Ungrouped variables", "definition": "vector(random(2..12),random(2..12))", "description": "", "templateType": "anything", "can_override": false}, "h0": {"name": "h0", "group": "geometry", "definition": "B[1]", "description": "", "templateType": "anything", "can_override": false}, "sign2": {"name": "sign2", "group": "geometry", "definition": "if(b[1] [siground(v[0],4), siground(v[1],4)], [C0,C1,C2]))", "description": "", "templateType": "anything", "can_override": false}, "Q0": {"name": "Q0", "group": "geometry", "definition": "a0 c0", "description": "

[ai xbari, ai ybari]

", "templateType": "anything", "can_override": false}, "Q1": {"name": "Q1", "group": "geometry", "definition": "a1 c1", "description": "", "templateType": "anything", "can_override": false}, "Q2": {"name": "Q2", "group": "geometry", "definition": "a2 c2", "description": "", "templateType": "anything", "can_override": false}, "QT": {"name": "QT", "group": "geometry", "definition": "Q0+Q1+Q2", "description": "", "templateType": "anything", "can_override": false}, "answer_sheet": {"name": "answer_sheet", "group": "Ungrouped variables", "definition": "fill_range(centroids_sheet,\"E2:F5\",map(v->[siground(v[0],4),siground(v[1],4)],[Q0,Q1,Q2,QT]))", "description": "", "templateType": "anything", "can_override": false}, "xbar2": {"name": "xbar2", "group": "Unnamed group", "definition": "Qt[0]/(At)", "description": "", "templateType": "anything", "can_override": false}}, "variablesTest": {"condition": "B[1]<>A[1] and B[0]<>C[0]", "maxRuns": "100"}, "ungrouped_variables": ["A", "B", "C", "D", "units", "debug", "blank_sheet", "areas_sheet", "centroids_sheet", "answer_sheet"], "variable_groups": [{"name": "geometry", "variables": ["b0", "h0", "b2", "h1", "b1", "h2", "sign1", "sign2", "A0", "A1", "A2", "AT", "C0", "C2", "C1", "Q0", "Q1", "Q2", "QT"]}, {"name": "Unnamed group", "variables": ["xbar", "ybar", "xbar2"]}], "functions": {"Q": {"parameters": [["area", "?"], ["distance", "?"]], "type": "quantity", "language": "jme", "definition": "display(area*distance*qty(1,units)*qty(1,units)*qty(1,units))"}, "display": {"parameters": [["q", "quantity"]], "type": "quantity", "language": "jme", "definition": "string(siground(q,4))"}, "bar": {"parameters": [["coord", "number"]], "type": "quantity", "language": "jme", "definition": "display(quantity(coord,units))"}, "applet": {"parameters": [], "type": "ggbapplet", "language": "javascript", "definition": "\n// Create the worksheet. \n// This function returns an object with a container `element` and a `promise` resolving to a GeoGebra applet.\nvar params = {\n material_id: 'ftkm8mfx',\n //alternate method, load from file.\n //filename: \"resources/question-resources/centroid-t-r.ggb\",\n //width:400,\n //height:300\n // was geogebra_applet('ftkm8mfx',[['A',A],['B',B],['C',C]])\n}\n\nvar result = Numbas.extensions.geogebra.createGeogebraApplet(params);\n\n// Once the applet has loaded, run some commands to manipulate the worksheet.\nresult.promise.then(function(d) {\n var app = d.app;\n question.applet = d;\n \n \nfunction setGGBPoint(name) {\n // moves point in GGB to location of Numbas Vector Variable\n var pt = question.scope.evaluate(name).value\n app.setFixed(name,false,false);\n app.setCoords(name, pt[0], pt[1]);\n app.setFixed(name,true,true);\n }\n\n setGGBPoint(\"A\");\n setGGBPoint(\"B\");\n setGGBPoint(\"C\");\n setGGBPoint(\"D\");\n app.setVisible(\"Centroid\",false);\n \n});\n\n// This function returns the result of `createGeogebraApplet` as an object \n// with the JME data type 'ggbapplet', which can be substituted into the question's content.\nreturn new Numbas.jme.types.ggbapplet(result);"}}, "preamble": {"js": "question.signals.on('adviceDisplayed',function() {\n try{\n var app = question.applet.app;\n \n app.setVisible(\"Centroid\",true);\n \n }\n catch(err){} \n})\n\n\n\n", "css": "table.centroid{margin-left:0;}\n\ntable.centroid td {\n width:6em; \n vertical-align:center; \n text-align:center;}\ntable.centroid *.underline {border-bottom: 2px solid black;}\n\n"}, "parts": [{"type": "spreadsheet", "useCustomName": true, "customName": "Centroid Table", "marks": "36", "scripts": {}, "customMarkingAlgorithm": "", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "settings": {"initial_sheet": "blank_sheet", "correct_answer": "answer_sheet", "disable_ranges": "[\"A1:F1\", \"A1:A5\", \"C5:D5\"]", "mark_ranges": "[\"$A_i$\": \"B2:B4\", \"$\\\\bar{x}$\": \"C2:C4\", \"$\\\\bar{y}$\": \"D2:D4\", \"$A_i\\\\bar{x}_i$\": \"E2:E4\", \"$A_i\\\\bar{y}_i$\": \"F2:F4\",\"$A$\":\"B5\", \"$Q_y$\": \"E5\", \"$Q_x$\":\"F5\"]", "marking_method": "per_cell", "tolerance": "0.05"}}, {"type": "gapfill", "useCustomName": true, "customName": "Results", "marks": 0, "scripts": {}, "customMarkingAlgorithm": "", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "prompt": "

$\\bar{x} =\\dfrac{Q_y}{A} = $ [[0]] 

\n

$\\bar{y} =\\dfrac{Q_x}{A} = $ [[1]]

", "gaps": [{"type": "engineering-answer", "useCustomName": true, "customName": "$\\bar{x}$", "marks": "2", "scripts": {}, "customMarkingAlgorithm": "", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "settings": {"correctAnswer": "xbar", "right": "0.2", "close": "1.0", "C1": "75", "C2": "50", "C3": "25"}}, {"type": "engineering-answer", "useCustomName": true, "customName": "$\\bar{y}$", "marks": "2", "scripts": {}, "customMarkingAlgorithm": "", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "settings": {"correctAnswer": "ybar", "right": "0.2", "close": "1.0", "C1": "75", "C2": "50", "C3": "25"}}], "sortAnswers": false}], "partsMode": "all", "maxMarks": 0, "objectives": [], "penalties": [], "objectiveVisibility": "always", "penaltyVisibility": "always", "contributors": [{"name": "Christian Lawson-Perfect", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/7/"}, {"name": "William Haynes", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/2530/"}], "resources": ["question-resources/centroid-t-r.ggb"]}]}], "contributors": [{"name": "Christian Lawson-Perfect", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/7/"}, {"name": "William Haynes", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/2530/"}]}