// Numbas version: finer_feedback_settings {"name": "Quantities in a spreadsheet", "extensions": ["sheets", "quantities"], "custom_part_types": [{"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": [], "navigation": {"allowregen": true, "showfrontpage": false, "preventleave": false, "typeendtoleave": false}, "question_groups": [{"pickingStrategy": "all-ordered", "questions": [{"name": "Quantities in a spreadsheet", "tags": [], "metadata": {"description": "

A custom marking algorithm on the spreadsheet part changes it to interpret quantities in cells, so they can be compared to the expected answer.

\n

In real use, you'd probably want more sophisticated logic, to give better feedback or to allow partial marks for different units, like the \"quantity with units\" part type does.

", "licence": "Creative Commons Attribution 4.0 International"}, "statement": "", "advice": "", "rulesets": {}, "extensions": ["quantities", "sheets"], "builtin_constants": {"e": true, "pi,\u03c0": true, "i": true, "j": false}, "constants": [], "variables": {"volume": {"name": "volume", "group": "Ungrouped variables", "definition": "repeat(qty(random(100..500#10 except 300), \"cm^3\"),n)", "description": "

Random volumes for each item.

", "templateType": "anything", "can_override": false}, "weight": {"name": "weight", "group": "Ungrouped variables", "definition": "v*d in \"kg\" for: [v,d] of: zip(volume,density)", "description": "

The weright for each item, chosen so that when the student divided the weight by the volume they get a nice decimal.

", "templateType": "anything", "can_override": false}, "density": {"name": "density", "group": "Ungrouped variables", "definition": "repeat(qty(random(10..50),\"g/cm^3\"), n)", "description": "

Random densities for each item.

", "templateType": "anything", "can_override": false}, "filled_sheet": {"name": "filled_sheet", "group": "Ungrouped variables", "definition": "sheet\n|> fill_range(encode_range(1,2,3,2), plain_string(d) for: d of: density)", "description": "

The spreadsheet with the densities filled in.

", "templateType": "anything", "can_override": false}, "sheet": {"name": "sheet", "group": "Ungrouped variables", "definition": "spreadsheet([\n [\"Weight\"] + (plain_string(w) for: w of: weight),\n [\"Volume\"] + (plain_string(v) for: v of: volume),\n [\"Density\"] + repeat(\"\", n)\n])\n|> update_range(\"A1:A3\", font_style(\"bold\"))", "description": "

The spreadsheet to fill in.

", "templateType": "anything", "can_override": false}, "n": {"name": "n", "group": "Ungrouped variables", "definition": "3", "description": "

The number of items to generate.

", "templateType": "anything", "can_override": false}}, "variablesTest": {"condition": "", "maxRuns": 100}, "ungrouped_variables": ["n", "volume", "density", "weight", "sheet", "filled_sheet"], "variable_groups": [], "functions": {"parse_qty": {"parameters": [["str", "string"], ["allowed_notation_styles", "list of string"]], "type": "quantity", "language": "jme", "definition": "let(\n [ns, n], matchnumber(str, allowed_notation_styles),\n units, str[len(ns)..len(str)],\n try(qty(n, units), e, qty(nan,\"\"))\n)"}}, "preamble": {"js": "", "css": ""}, "parts": [{"type": "spreadsheet", "useCustomName": false, "customName": "", "marks": "3", "scripts": {}, "customMarkingAlgorithm": "mark_ranges:\nmap(\n let(\n range_credit,\n sum(map(\n let(\n correctCellString, correctAnswer[c],\n correctCellNumber, parse_qty(correctCellString, notation_styles),\n studentCellString, studentAnswer[c],\n studentCellNumber, parse_qty(studentCellString, notation_styles),\n award(\n 1/len(cells), \n switch(\n correctCellString=\"\",\n isnan(scalar(studentCellNumber)) or studentCellString=\"\",\n isnan(scalar(correctCellNumber)),\n lower(correctCellString) = lower(studentCellString)\n ,\n assert(not compatible(correctCellNumber, studentCellNumber),\n scalar(abs(correctCellNumber - studentCellNumber)) <= settings[\"tolerance\"]\n )\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)", "extendBaseMarkingAlgorithm": true, "unitTests": [], "showCorrectAnswer": true, "showFeedbackIcon": true, "variableReplacements": [], "variableReplacementStrategy": "originalfirst", "nextParts": [], "suggestGoingBack": false, "adaptiveMarkingPenalty": 0, "exploreObjective": null, "prompt": "

Fill in the density values for each item in this table.

", "settings": {"initial_sheet": "sheet", "correct_answer": "filled_sheet", "disable_ranges": "[]", "mark_ranges": "dict(\"Density\": \"B3:D3\")", "marking_method": "per_cell", "tolerance": "0"}}], "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/"}], "resources": []}]}], "contributors": [{"name": "Christian Lawson-Perfect", "profile_url": "https://numbas.mathcentre.ac.uk/accounts/profile/7/"}]}