-
Notifications
You must be signed in to change notification settings - Fork 1
Lab exam and peer eval info for students #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
novo52
wants to merge
24
commits into
master
Choose a base branch
from
info/lab-exam
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
09b0d73
Render the peer evaluation rubric from a single source
novo52 a938087
Add the Gazprea peer evaluation page and rubric chart
novo52 29fdaf3
Add the lab exam page
novo52 5e9f125
Ground the lab exam page in the confirmed room, schedule, and access …
novo52 b3d7c26
Describe the exam session as the monitor and Classroom 50 actually im…
novo52 e8b4592
State the closed-internet rule and the git exception it needs
novo52 487c49c
State the exam as open-computer and closed-internet, and trim what th…
novo52 f63a484
Describe grading by hidden test suite, mutant comparison, and written…
novo52 d25e057
Add an exam vehicles page explaining Sweep and the three littleCs
novo52 b943852
Removed mistakenly committed files
novo52 2d4e07d
Fixed wording
novo52 03651f5
Merge remote-tracking branch 'origin/master' into info/lab-exam
novo52 d9a2c70
Resolve the lab exam cross-references through Sphinx instead of raw H…
novo52 7e8430b
Three peer evaluations, with pairings and rooms published on Canvas
novo52 3f55c3f
Generate the peer evaluation quiz from the rubric
novo52 e6f21be
Emphasise live code navigation in the peer evaluation Q&A
novo52 22e1f1d
Revise the lab exam and peer evaluation pages from review
novo52 078240e
Share the text2qti machinery between the peer evaluation forms
novo52 ca020b8
Add the session feedback form the evaluated team fills out
novo52 3a5664a
Drop the rationale for grading how a session was run
novo52 1871f66
Add the evaluator cheat sheet and the session tracking sheet
novo52 d8f1093
State what each peer evaluation form is a condition of
novo52 2ade279
Match the generated quiz titles to Canvas and record the settings an …
novo52 5b30db2
Give the whole class the 2.5x universal time multiplier
novo52 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,271 @@ | ||
| """Directives that render the peer evaluation rubric from ``rubric_data.py``. | ||
|
|
||
| Each directive emits a reStructuredText ``list-table`` and hands it back to the | ||
| parser, so the output matches tables written by hand elsewhere in the docs. | ||
|
|
||
| - ``rubric-weights`` objective, scope and weight for all six objectives | ||
| - ``rubric-levels`` what each of the four performance levels means | ||
| - ``rubric-anchors`` the mark a given set of rubric placements is worth | ||
| - ``rubric-objective`` one objective: its lead-in and its four descriptors | ||
| - ``rubric-objectives`` every objective of a given scope, in order | ||
| - ``rubric-chart`` all objectives against all levels, as one wide grid | ||
| - ``rubric-coverage`` the areas an evaluator must ask about, as a bullet list | ||
| - ``rubric-required-questions`` the three questions every member is asked | ||
| - ``rubric-objective-titles`` every objective by title and scope, without descriptors | ||
| - ``rubric-session-expectations`` what the evaluated team judges its evaluators against | ||
| - ``rubric-session-anchors`` the mark a given quality of session is worth | ||
| - ``rubric-follow-ups`` follow-up question templates, by purpose | ||
| - ``rubric-etiquette`` how to ask, as a bullet list | ||
| - ``rubric-clock`` where the session should be at a given time | ||
| - ``rubric-tracking`` a blank grid, one row per evaluated member | ||
| - ``rubric-coverage-tracking`` the coverage areas with room to record who answered | ||
|
|
||
| The pages that are reference sheets rather than pages to read through get their | ||
| own stylesheets, attached here so that the rest of the site keeps the theme's | ||
| usual layout. | ||
| """ | ||
|
|
||
| from docutils import nodes | ||
| from docutils.parsers.rst import Directive, directives | ||
| from docutils.statemachine import StringList | ||
|
|
||
| import rubric_data | ||
|
|
||
|
|
||
| def _list_table(rows, widths): | ||
| """Render ``rows`` as a header-row list-table, one line per cell.""" | ||
| lines = [".. list-table::", " :header-rows: 1", " :widths: " + " ".join(str(w) for w in widths), ""] | ||
| for row in rows: | ||
| for index, cell in enumerate(row): | ||
| lines.append((" * - " if index == 0 else " - ") + cell) | ||
| lines.append("") | ||
| return lines | ||
|
|
||
|
|
||
| def _bullets(items): | ||
| return ["* " + item for item in items] + [""] | ||
|
|
||
|
|
||
| def _numbered(items): | ||
| return ["{}. {}".format(number, item) for number, item in enumerate(items, start=1)] + [""] | ||
|
|
||
|
|
||
| def _objective(objective_id): | ||
| for objective in rubric_data.OBJECTIVES: | ||
| if objective["id"] == objective_id: | ||
| return objective | ||
| raise KeyError(objective_id) | ||
|
|
||
|
|
||
| class _RubricDirective(Directive): | ||
| """Parses the lines from :meth:`lines` in the context of the calling page.""" | ||
|
|
||
| has_content = False | ||
|
|
||
| #: Classes to put on the table this directive produces. The ``rst-class`` | ||
| #: directive resolves through a document-level transform, which a parse | ||
| #: into a detached node never reaches, so the class goes on directly. | ||
| table_classes = () | ||
|
|
||
| def lines(self): | ||
| raise NotImplementedError | ||
|
|
||
| def run(self): | ||
| parent = nodes.Element() | ||
| self.state.nested_parse(StringList(self.lines(), source=""), self.content_offset, parent) | ||
| for table in parent.findall(nodes.table): | ||
| table["classes"].extend(self.table_classes) | ||
| return parent.children | ||
|
|
||
|
|
||
| class RubricWeights(_RubricDirective): | ||
| def lines(self): | ||
| rows = [["Objective", "Scope", "Weight"]] | ||
| rows += [[o["title"], o["scope"], o["weight"]] for o in rubric_data.OBJECTIVES] | ||
| return _list_table(rows, [60, 20, 20]) | ||
|
|
||
|
|
||
| class RubricLevels(_RubricDirective): | ||
| def lines(self): | ||
| rows = [["Level", "Meaning"]] | ||
| rows += [[level, rubric_data.LEVEL_MEANINGS[level]] for level in rubric_data.LEVELS] | ||
| return _list_table(rows, [30, 70]) | ||
|
|
||
|
|
||
| class RubricAnchors(_RubricDirective): | ||
| def lines(self): | ||
| rows = [["Rubric placement", "Mark"]] + [list(anchor) for anchor in rubric_data.ANCHORS] | ||
| return _list_table(rows, [70, 30]) | ||
|
|
||
|
|
||
| class RubricObjective(_RubricDirective): | ||
| required_arguments = 1 | ||
|
|
||
| def lines(self): | ||
| return _objective_lines(_objective(self.arguments[0])) | ||
|
|
||
|
|
||
| class RubricObjectives(_RubricDirective): | ||
| required_arguments = 1 | ||
|
|
||
| def lines(self): | ||
| scope = self.arguments[0] | ||
| lines = [] | ||
| for objective in rubric_data.OBJECTIVES: | ||
| if objective["scope"].lower() == scope.lower(): | ||
| lines += _objective_lines(objective) | ||
| return lines | ||
|
|
||
|
|
||
| def _objective_lines(objective): | ||
| lines = ["**{}.** {}".format(objective["title"], objective["lead"]), ""] | ||
| rows = [["Level", "Descriptor"]] | ||
| rows += [[level, objective["descriptors"][level]] for level in rubric_data.LEVELS] | ||
| return lines + _list_table(rows, [22, 78]) | ||
|
|
||
|
|
||
| class RubricChart(_RubricDirective): | ||
| def lines(self): | ||
| header = ["Objective"] + rubric_data.LEVELS | ||
| rows = [header] | ||
| for objective in rubric_data.OBJECTIVES: | ||
| label = "**{}** ({}, {})".format(objective["title"], objective["scope"], objective["weight"]) | ||
| rows.append([label] + [objective["descriptors"][level] for level in rubric_data.LEVELS]) | ||
| return _list_table(rows, [16, 21, 21, 21, 21]) | ||
|
|
||
|
|
||
| class RubricCoverage(_RubricDirective): | ||
| def lines(self): | ||
| return _bullets(rubric_data.COVERAGE_AREAS) | ||
|
|
||
|
|
||
| class RubricRequiredQuestions(_RubricDirective): | ||
| #: ``:numbered:`` numbers the questions, so that a page can refer to one by | ||
| #: its number. The tracking sheet heads its tick boxes with them. | ||
| option_spec = {"numbered": directives.flag} | ||
|
|
||
| def lines(self): | ||
| if "numbered" in self.options: | ||
| return _numbered(rubric_data.REQUIRED_QUESTIONS) | ||
| return _bullets(rubric_data.REQUIRED_QUESTIONS) | ||
|
|
||
|
|
||
| class RubricObjectiveTitles(_RubricDirective): | ||
| """Every objective by title and scope, without its descriptors. | ||
|
|
||
| What an evaluator has to have evidence for by the time the session ends, | ||
| at the size that fits on a sheet they hold during it. | ||
| """ | ||
|
|
||
| def lines(self): | ||
| rows = [["Judge each member on", "Scope"]] | ||
| rows += [[objective["title"], objective["scope"]] for objective in rubric_data.OBJECTIVES] | ||
| return _list_table(rows, [80, 20]) | ||
|
|
||
|
|
||
| class RubricSessionExpectations(_RubricDirective): | ||
| def lines(self): | ||
| rows = [["The evaluating team", "What that looks like"]] | ||
| rows += [list(expectation) for expectation in rubric_data.SESSION_EXPECTATIONS] | ||
| return _list_table(rows, [30, 70]) | ||
|
|
||
|
|
||
| class RubricSessionAnchors(_RubricDirective): | ||
| def lines(self): | ||
| rows = [["Mark", "The session"]] | ||
| rows += [[mark, description] for description, mark in rubric_data.SESSION_ANCHORS] | ||
| return _list_table(rows, [10, 90]) | ||
|
|
||
|
|
||
| class RubricFollowUps(_RubricDirective): | ||
| def lines(self): | ||
| rows = [["To", "Ask"]] + [list(template) for template in rubric_data.FOLLOW_UP_TEMPLATES] | ||
| return _list_table(rows, [18, 82]) | ||
|
|
||
|
|
||
| class RubricEtiquette(_RubricDirective): | ||
| def lines(self): | ||
| return _bullets(rubric_data.ETIQUETTE) | ||
|
|
||
|
|
||
| class RubricClock(_RubricDirective): | ||
| """The two sessions of a lab period against where each should be by then.""" | ||
|
|
||
| def lines(self): | ||
| rows = [["1st", "2nd", "Where you should be"]] | ||
| rows += [list(mark) for mark in rubric_data.SESSION_CLOCK] | ||
| return _list_table(rows, [9, 9, 82]) | ||
|
|
||
|
|
||
| #: Written into a cell that the evaluator fills in by hand. A list-table cell | ||
| #: cannot be empty, so a blank one carries a space the page does not show. | ||
| BLANK = " " | ||
|
|
||
|
|
||
| class RubricTracking(_RubricDirective): | ||
| """One row per evaluated member, left blank to be filled in during the session.""" | ||
|
|
||
| #: A tick box per required question, headed by its number on the list the | ||
| #: page prints below the grid. | ||
| REQUIRED = [str(number) for number in range(1, len(rubric_data.REQUIRED_QUESTIONS) + 1)] | ||
|
|
||
| def lines(self): | ||
| headings = ["Member", "Areas they claim"] + self.REQUIRED | ||
| headings += ["Questions asked", "Points", "Notes"] | ||
| rows = [headings] + [[BLANK] * len(headings) for _ in range(4)] | ||
| return _list_table(rows, [12, 16, 4, 4, 4, 8, 6, 30]) | ||
|
|
||
|
|
||
| class RubricCoverageTracking(_RubricDirective): | ||
| """The coverage areas with a blank beside each. | ||
|
|
||
| One line of writing per area, against the several lines a member's row | ||
| gets, so the table is named for the stylesheet to size it on its own. | ||
| """ | ||
|
|
||
| table_classes = ("coverage-tracking",) | ||
|
|
||
| def lines(self): | ||
| rows = [["Area", "Who answered"]] | ||
| rows += [[area, BLANK] for area in rubric_data.COVERAGE_AREAS] | ||
| return _list_table(rows, [55, 45]) | ||
|
|
||
|
|
||
| #: Pages laid out as reference sheets by ``_static/css/rubric_sheet.css``. | ||
| SHEET_PAGES = ("rubric_chart", "evaluator_sheet", "tracking_sheet") | ||
|
|
||
| #: Sheets that carry a stylesheet of their own past the shared layout, keyed by | ||
| #: page name. The tracking sheet is written on by hand and needs room to write | ||
| #: in; the cheat sheet is read from and has to fit its four tables on one side. | ||
| SHEET_CSS = { | ||
| "tracking_sheet": "css/tracking_sheet.css", | ||
| "evaluator_sheet": "css/evaluator_sheet.css", | ||
| } | ||
|
|
||
|
|
||
| def _attach_sheet_css(app, pagename, templatename, context, doctree): | ||
| if pagename in SHEET_PAGES: | ||
| app.add_css_file("css/rubric_sheet.css") | ||
| if pagename in SHEET_CSS: | ||
| app.add_css_file(SHEET_CSS[pagename]) | ||
|
|
||
|
|
||
| def setup(app): | ||
| app.add_directive("rubric-weights", RubricWeights) | ||
| app.add_directive("rubric-levels", RubricLevels) | ||
| app.add_directive("rubric-anchors", RubricAnchors) | ||
| app.add_directive("rubric-objective", RubricObjective) | ||
| app.add_directive("rubric-objectives", RubricObjectives) | ||
| app.add_directive("rubric-chart", RubricChart) | ||
| app.add_directive("rubric-coverage", RubricCoverage) | ||
| app.add_directive("rubric-required-questions", RubricRequiredQuestions) | ||
| app.add_directive("rubric-objective-titles", RubricObjectiveTitles) | ||
| app.add_directive("rubric-session-expectations", RubricSessionExpectations) | ||
| app.add_directive("rubric-session-anchors", RubricSessionAnchors) | ||
| app.add_directive("rubric-follow-ups", RubricFollowUps) | ||
| app.add_directive("rubric-etiquette", RubricEtiquette) | ||
| app.add_directive("rubric-clock", RubricClock) | ||
| app.add_directive("rubric-tracking", RubricTracking) | ||
| app.add_directive("rubric-coverage-tracking", RubricCoverageTracking) | ||
| app.connect("html-page-context", _attach_sheet_css) | ||
| return {"parallel_read_safe": True, "parallel_write_safe": True} | ||
|
Comment on lines
+253
to
+271
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have other places we can use these? |
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| /* Fits the evaluator cheat sheet onto a single landscape sheet, on top of the | ||
| * reference-sheet layout in rubric_sheet.css. Loaded only on that page, by the | ||
| * html-page-context handler in _ext/rubric.py. | ||
| * | ||
| * The page is five short tables and a list. They run in two columns, which is | ||
| * what buys the single sheet: a landscape page is wide enough that one column | ||
| * leaves most of each line empty. | ||
| * | ||
| * The layout applies on screen as well as in print, so an evaluator working | ||
| * from a laptop sees the sheet their neighbour has on paper. */ | ||
|
|
||
| .rst-content section section, | ||
| .rst-content .section .section { | ||
| break-inside: avoid; | ||
| } | ||
|
|
||
| /* The theme nests the page body a few levels below .rst-content, so the | ||
| * columns are set on the one section that holds the whole page. */ | ||
| .rst-content [itemprop="articleBody"] > section, | ||
| .rst-content [itemprop="articleBody"] > .section { | ||
| column-count: 2; | ||
| column-gap: 1cm; | ||
| } | ||
|
|
||
| .rst-content h1 { | ||
| column-span: all; | ||
| font-size: 14pt; | ||
| margin: 0 0 6pt; | ||
| } | ||
|
|
||
| .rst-content h2 { | ||
| font-size: 11pt; | ||
| margin: 0 0 3pt; | ||
| } | ||
|
|
||
| .rst-content p, | ||
| .rst-content li { | ||
| font-size: 9.5pt; | ||
| line-height: 1.25; | ||
| margin: 0 0 4pt; | ||
| } | ||
|
|
||
| .rst-content ul { | ||
| margin: 0 0 4pt; | ||
| padding-left: 1.1em; | ||
| } | ||
|
|
||
| .rst-content table.docutils td, | ||
| .rst-content table.docutils th, | ||
| .rst-content table.docutils p { | ||
| font-size: 9pt !important; | ||
| line-height: 1.2 !important; | ||
| } | ||
|
|
||
| .rst-content table.docutils { | ||
| margin-bottom: 5pt; | ||
| } | ||
|
|
||
| .rst-content table.docutils td, | ||
| .rst-content table.docutils th { | ||
| padding: 3px 5px; | ||
| } | ||
|
|
||
| @media print { | ||
| @page { | ||
| size: landscape; | ||
| margin: 0.8cm; | ||
| } | ||
|
|
||
| /* On paper the column height is the page, so the first column is filled | ||
| * before the second is started. On screen the page has no such height and | ||
| * the columns are balanced against each other instead. */ | ||
| .rst-content [itemprop="articleBody"] > section, | ||
| .rst-content [itemprop="articleBody"] > .section { | ||
| column-fill: auto; | ||
| } | ||
|
|
||
| /* Printed on a page whose footer the theme has already removed. */ | ||
| .rst-content .admonition { | ||
| display: none !important; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there places in the doc where we rewrite these tables manually? Can we refactor the docs to have a single source of truth for these rubrics and point explicitly to the course info page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had no luck with Canvas Rubriks :-( Consequently, I've been moving towards this document as the source of truth. If we want to post it (or link to it?) in other parts of the course description, what is the best format? PDF? HTML?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rcunrau The chart in the docs is intended to be printable to pdf or render nicely in a browser for student reference during evaluations. We can link to it.