Automated grade calculation. Pulls data from Canvas and Gradescope exports, applies a reflection/revision scoring policy for problem sets and learning assessments (exams), and produces a formatted Excel workbook for easy viewing. It is highly recommended that a new user of this code do two things prior to executing any code: (1) read the full content of the README, and (2) read the full content of the config.py file and edit as necessary for their own course. For new users it is also suggested to turn the DEBUG flag up to 2.
First implemented and used in Physics 81, Spring 2026 at Stanford University. Has also been tested retroactively on grades from Physics 41 and Physics 41E (also at Stanford), demonstrating compatibility with other course structures. Not every edge case has been tested in terms of different functionalities, but most of the major flags (letter grades vs S/NC, automatic attendance, reading quizzes, problem set sub-skills, dropping the lowest problem set, prorating individual categories, changing the number of midterm assessments, rounding-up students near category boundaries, optional extra credit etc.) have been tested by applying the library to these three courses.
Collaboratively developed with Github Copilot, Codex, and a variety of different models.
To maintain FERPA compliance as well as abide by Stanford's Moderate-Risk Data handling guidelines, raw grade data containing identifying information about students (usually located within the canvas/, gradescope/, and extra_credit/ directories), as well as any Excel workbooks produced by the code, should be temporarily moved outside the project root directory whenever using AI tools to update/modify the codebase (especially agentic workflows). This is the only foolproof method of preventing student data from being fed into the models during automated testing by the LLMs themselves. The .github/copilot-instructions.md and .codex/config.toml files within the repository attempt to prevent access to common private-data file types (*.csv, *.tsv, *.xlsx, *.xls, *.xlsm, *.ods, and *.zip) and generally seem to be respected during execution of simple tasks, but extensive access testing has demonstrated that both Copilot and Codex can find workarounds to these restrictions during complex debugging operations.
The repository also includes AGENTS.md for AI coding agents that read that convention, plus CLAUDE.md and .claude/settings.json for Claude Code, although these have not been as extensively tested as those for Codex and Copilot. CLAUDE.md imports AGENTS.md so the shared privacy rules stay in one place, and .claude/settings.json denies Claude Code file-tool access to the private data directories and file extensions listed above. These instruction and settings files are portability aids, not complete privacy controls. When using Copilot, verify that repository custom instructions are enabled for the current tool surface and that .github/copilot-instructions.md appears in Copilot's response references. When using Claude Code, verify that CLAUDE.md is loaded and run claude doctor if you need to confirm project settings are valid.
Before using AI coding tools, run these commands from the project root:
- Move private grade files out of the repo:
python ./move_grade_files.py- Restore private grade files after development work:
python ./move_grade_files.py copybackThe move-out command relocates all private-data files with these extensions (.csv, .tsv, .xlsx, .xls, .xlsm, .ods, .zip) from the project root, canvas/, gradescope/, and extra_credit/ into a sibling ../<project_root>_tmp/ directory. For example, a project directory named grades uses ../grades_tmp/, while a directory named course_grades uses ../course_grades_tmp/. The script prints each moved file and preserves the same subdirectory layout inside that temporary directory. On copyback, the script removes the temporary directory if it is empty after the files are restored, and prints whether removal succeeded or files remain. If a source directory is missing, or if a destination file already exists, the script skips that item and prints a warning rather than overwriting anything.
Users are responsible for maintaining FERPA compliance, and care should be taken if updates/changes to this code are implemented with AI coding tools.
Stanford announced access to agentic workflows approved for FERPA and Moderate-Risk Data beginning June 30, 2026. The move_grade_files.py functionality is preserved here for users with personal workflows, and in the event that Stanford discontinues sponsorship and support of these tools.
https://uit.stanford.edu/news/new-ai-tools-stanford-arrive-june-30
- Install dependencies:
pip install -r requirements.txt - Read and edit
config.pyfor the course. - Create
canvas/,gradescope/, andextra_credit/as needed, then add the required exports. - Check configuration:
python validate_config.py - Create or update attendance data if using manual attendance:
python update_attendance.py - Optionally verify Gradescope files:
python verify_gradescope_files.py - Generate the gradebook:
python generate_gradebook.py - Review
<COURSE_PREFIX>_missing_scores.xlsx, enter normalized missing scores if needed, then re-run the generator.
grades/ # Default root directory name, can be anything.
├── AGENTS.md # Shared AI-agent privacy and development instructions
├── CLAUDE.md # Claude Code instructions that import AGENTS.md
├── .codex/config.toml # Codex privacy guardrails
├── .claude/settings.json # Claude Code project permission guardrails
├── .github/copilot-instructions.md # GitHub Copilot privacy instructions
├── config.py # All tunable parameters (weights, paths, counts)
├── requirements.txt # Python libraries required, in pip format for easy installation
├── <COURSE_PREFIX>_attendance.xlsx # Attendance file (create with update_attendance.py)
├── <COURSE_PREFIX>_gradebook.xlsx # Output gradebook (generated)
├── <COURSE_PREFIX>_missing_scores.xlsx # Missing-score review workbook (generated)
├── canvas/ # Canvas CSV exports (not tracked by git, so must be created)
├── gradescope/ # Gradescope CSV exports (also not tracked, must be created)
├── extra_credit/ # Problem-specific Gradescope CSV exports for optional extra credit
├── generate_gradebook.py # Main command: produce the gradebook
├── update_attendance.py # Command: create/update the attendance workbook
├── validate_config.py # Command: check config.py before loading private data files
├── verify_gradescope_files.py # Command: check which Gradescope files are present
├── grade_processing/ # Importable grading implementation modules
│ ├── debug_diagnostics.py # Privacy-conscious aggregate debug summaries
│ ├── grade_histogram.py # ASCII grade-distribution rendering helper
│ ├── load_data.py # Data loaders (Canvas, Gradescope, attendance)
│ ├── process_grades.py # Core grade calculation logic
│ ├── sid_utils.py # Shared SIS/Gradescope ID normalization and matching helpers
│ └── validation.py # Configuration validation logic
├── tests/
│ └── test_synthetic_grade_logic.py # Developer tests using synthetic in-memory data and temporary files only
└── tools/
└── inspect_student_grade.py # Instructor-only private diagnostic for one student
All commands are run from the grades/ root directory, using root-level paths such as python generate_gradebook.py.
pip install -r requirements.txtEdit this file before running anything. Key settings:
| Setting | Description |
|---|---|
COURSE_PREFIX |
Prepended to all generated output files. Default: course_year_quarter |
WEIGHTS |
Per-category grade weights. Values are normalized by their sum, so they don't need to sum to 1.0 |
ATTENDANCE_FILE |
File for tracking attendance. Initial execution of the update_attendance.py script will create this file, after which the user may edit for manual attendance tracking |
GRADESCOPE_MAIN_FILE |
Main Gradescope export filename used for automatic activity-sheet attendance. Default placeholder: Course_Term_YYYY_grades.csv |
ATTENDANCE_AUTO_FROM_GRADESCOPE |
If False, use the manual attendance workbook. If True, calculate attendance from activity-sheet submission times in the main Gradescope export |
ATTENDANCE_ACTIVITY_SHEET_COUNT |
Upper bound on activity-sheet numbers to search for when automatic attendance is enabled |
N_PROBLEM_SETS |
Upper bound on problem set numbers to search for, excluding PS0 |
PSET_POINTS_BACK_FRACTION |
Fraction of lost PS points recoverable via revisions |
PSET_DROP_LOWEST |
If True, drop each student's lowest-scoring problem set |
PSET_SUBSKILLS_ENABLED |
Set True if this course uses Canvas-tracked problem set sub-skills assignments |
N_PSET_SUBSKILLS |
Upper bound on problem set sub-skills assignment numbers to search for |
PSET_SUBSKILLS_WEIGHT_FRACTION |
Fraction of the configured problem_sets weight assigned to problem set sub-skills when enabled |
N_LEARNING_ASSESSMENTS |
Number of midterm LAs (not including the final) |
LA_POINTS_BACK_FRACTIONS |
Per-LA revision fraction dict (final has no revisions) |
REFLECTION_MAX_SCORE |
Maximum score on each per-problem reflection/revision rubric used to scale points recovered |
RQ_ENABLED |
Set True if this course uses Canvas-tracked reading quizzes |
N_READING_QUIZZES |
Upper bound on reading quiz assignment numbers to search for |
PRORATE_THRESHOLDS |
Per-category pro-rate threshold (PSets/sub-skills/attendance/RQs only, not LAs) |
Z_SCORE_THRESHOLD |
Scores below this fraction are excluded from z-score statistics (for withdraws etc) |
GRADE_CUTOFFS |
Final numerical thresholds for letter grades or S/NC cutoff |
OUTPUT_SCORE_DECIMAL_PLACES |
Maximum number of decimal places displayed for normalized scores in the main gradebook workbook; trailing zeros are hidden, and stored numeric values are rounded to one extra place |
DEBUG_LEVEL |
Debug verbosity. Level 2 prints aggregate diagnostics only; it does not print student score samples |
The defaults below come from config.py — edit config.py to change them. The code normalizes weights by their sum, so values do not need to sum to 1.0; set a category to 0 to exclude it.
| Category | Default weight |
|---|---|
| Problem Sets | 20% |
| LA 1 (Midterm 1) | 20% |
| LA 2 (Midterm 2) | 20% |
| Final | 30% |
| Attendance | 10% |
| Reading Quizzes | 0% (disabled) |
| Problem Set Sub-skills | 0% (disabled) |
When PSET_SUBSKILLS_ENABLED = True, the configured problem_sets weight is split by PSET_SUBSKILLS_WEIGHT_FRACTION. For example, if problem_sets is 30% and PSET_SUBSKILLS_WEIGHT_FRACTION = 0.05, regular problem sets receive 28.5% and problem set sub-skills receive 1.5%. The total configured weight remains 30%.
To add more midterm learning assessments, increase N_LEARNING_ASSESSMENTS and add a matching la_i entry to WEIGHTS for each numbered LA. For example, with three midterms, set N_LEARNING_ASSESSMENTS = 3 and include la_1, la_2, and la_3 in WEIGHTS. The Gradescope filename patterns use the same numbering, so the third midterm is expected at Learning_Assessment_3_scores.csv with an optional Learning_Assessment_3_-_Reflections_and_Revisions_scores.csv. LA_POINTS_BACK_FRACTIONS may include per-LA entries for any numbered LA; LA_POINTS_BACK_DEFAULT is used when an entry is omitted.
These defaults are intentionally generic starting points. Edit config.py for the actual course calendar, assignment names, and grading policy before loading real exports.
| Setting | Default |
|---|---|
COURSE_PREFIX |
course_year_quarter |
GRADESCOPE_MAIN_FILE |
Course_Term_YYYY_grades.csv |
N_PROBLEM_SETS |
9 |
PSET_POINTS_BACK_FRACTION |
0.5 |
PSET_NO_REVISION_THRESHOLD |
3 |
PSET_DROP_LOWEST |
True |
PSET_SUBSKILLS_ENABLED |
False |
PSET_SUBSKILLS_WEIGHT_FRACTION |
0.05 |
N_LEARNING_ASSESSMENTS |
2 midterm LAs, plus a final |
LA_POINTS_BACK_FRACTIONS |
0.25 for LA1 and LA2 |
REFLECTION_MAX_SCORE |
5.0 |
ATTENDANCE_AUTO_FROM_GRADESCOPE |
False |
ATTENDANCE_ACTIVITY_SHEET_COUNT |
30 |
RQ_ENABLED |
False |
N_READING_QUIZZES |
30 |
GRADE_ROUNDING_MARGIN |
0.01 |
OUTPUT_SCORE_DECIMAL_PLACES |
4 |
Z_SCORE_THRESHOLD |
0.50 |
- Export the entire gradebook from Canvas as CSV.
- Place in
canvas/. The loader auto-selects the most recent file based on theYYYY-MM-DDTHHMMtimestamp in the filename. - Required columns:
Student,ID,SIS User ID,SIS Login ID,Section,SU ID. - Optional Canvas-tracked assignments, including reading quizzes and problem set sub-skills, are matched from configured patterns such as
RQ_ASSIGNMENT_PATTERNandPSET_SUBSKILLS_ASSIGNMENT_PATTERN. Canvas assignment IDs such as(766054)are ignored, and matching tolerates case, spacing, and common punctuation differences such as colons and hyphens. The configured pattern should still describe the expected assignment name, for example"Problem Set {i}: Sub-skills".
Place all Gradescope CSV exports in gradescope/. Expected filenames (configurable via patterns in config.py):
| Assignment | Filename |
|---|---|
| Problem Set i | Problem_Set_{i}_scores.csv |
| PS Reflection/Revision i | Problem_Set_{i}_-_Reflections_and_Revisions_scores.csv |
| Learning Assessment i | Learning_Assessment_{i}_scores.csv |
| LA Reflection/Revision i | Learning_Assessment_{i}_-_Reflections_and_Revisions_scores.csv |
| Final Exam | Final_Learning_Assessment_scores.csv |
| Main grades export | e.g. Course_Term_YYYY_grades.csv |
The Canvas export is the roster reference used by the gradebook. The main Gradescope export is not the primary roster source; it is required when automatic activity-sheet attendance is enabled because it contains the activity submission-time columns used for attendance.
Run verify_gradescope_files.py to check which files are present or missing.
Use manual attendance unless ATTENDANCE_AUTO_FROM_GRADESCOPE = True in config.py.
For manual attendance, first-time setup creates the template with 10 weekly columns and a two-line header (second row reserved for days tracked):
python update_attendance.pyEdit the second header row (under each 'Week X') to set the number of days tracked that week. For each student, fill each week's cell with a fraction (days present / days tracked) or with a count (days present). Recalculate final attendance scores:
python update_attendance.py updateThe updater avoids writing pandas index columns and reconstructs a clean two-line header when updating; it will prompt before overwriting the existing attendance file when run without the 'update' argument.
Automatic activity-sheet attendance is also available. Set this in config.py:
ATTENDANCE_AUTO_FROM_GRADESCOPE = TrueThen configure the activity-sheet settings as needed:
ATTENDANCE_ACTIVITY_SHEET_COUNT = 30
ATTENDANCE_ACTIVITY_SHEET_PATTERN = "Activity Sheet {i}"
ATTENDANCE_ACTIVITY_SUBMISSION_TIME_SUFFIX = " - Submission Time"
ATTENDANCE_ALLOWED_NON_GROUP_OR_BLANK = 3With automatic attendance enabled, load_attendance() calculates attendance directly from GRADESCOPE_MAIN_FILE. Matching non-empty submission timestamps are treated as group submissions and receive full credit for that activity sheet. Individual submissions and blank submissions are counted against the configured allowance; within the allowance they receive full credit, and after the allowance they receive no credit.
ATTENDANCE_ACTIVITY_SHEET_COUNT is an upper bound on activity-sheet numbers to search for. Missing activity-sheet columns are skipped, so the attendance denominator is based on detected activity sheets rather than the configured upper bound.
To write a review workbook with one abbreviated column per detected activity sheet (as1, as2, ..., asN) plus the final normalized attendance column:
python update_attendance.py updateOn occasion, a sub-part of a problem or an entire problem on a problem set or a learning assessment will be counted for extra credit, although only once or twice per quarter. It is assumed that this is done with 0 point rubric items on Gradescope, with the number of extra credit points in the rubric description. This extra credit can be applied across all problem sets, or split evenly across the configured learning assessments (midterms), or both!
Optional extra credit is supplied via CSV files from the Gradescope "Export Evaluations" functionality. This gives problem-specific application of rubric items for each student. Configure paths in config.py via PSET_EXTRA_CREDIT_CSV and LA_EXTRA_CREDIT_CSV and place the files in the directory extra_credit, as detailed in the directory structure at the top of this README.
- CSV format: configure paths in
config.pyviaPSET_EXTRA_CREDIT_CSVandLA_EXTRA_CREDIT_CSVand place the files in the directoryextra_credit(or other path if desired).- Rows: one student per row. The 5th column must contain the student's SIS User ID, which is standard for the "Export Evaluations" function.
- Extra-credit columns: headers (i.e. rubric item descriptions) must include the pattern
(+X pts)(e.g.Full Solution (+3 pts)). - Student cells:
TRUE/FALSE(case-insensitive). If a column isTRUEfor a student,Xpoints are awarded for that student. - For problem sets, the awarded points are added to the student's PSet revised total after dropping the lowest PSet (if enabled) but before normalization, and capped at the PSet total max.
- For LAs, the extra credit is split evenly across loaded numbered LAs from
LA1throughLA{N_LEARNING_ASSESSMENTS}that have positive max scores; each LA's revised score is capped at its max.
When generate_gradebook.py runs, extra credit CSVs are loaded automatically by grade_processing.load_data.load_extra_credit() and applied during category score calculation.
python verify_gradescope_files.pyIf ATTENDANCE_AUTO_FROM_GRADESCOPE = False and manual attendance entries have changed, recalculate the normalized attendance column before generating the gradebook:
python update_attendance.py updateIf ATTENDANCE_AUTO_FROM_GRADESCOPE = True, skip this step for gradebook generation; generate_gradebook.py calculates attendance directly from GRADESCOPE_MAIN_FILE. Run python update_attendance.py update only when you want to write or refresh the automatic attendance review workbook.
python generate_gradebook.pyThe generator runs the same validation logic exposed by validate_config.py first and exits with a list of configuration issues if required settings are missing or inconsistent. To check configuration without generating a gradebook:
python validate_config.pyProduces <COURSE_PREFIX>_gradebook.xlsx with the following sheets:
| Sheet | Contents |
|---|---|
| Summary | One row per student; all category and overall scores |
| Summary (Sorted) | Same columns as Summary, sorted by revised overall score descending |
| Problem Sets | Per-assignment original/revised/max raw scores |
| Learning Assessments | Per-LA scores including final |
| Attendance | Attendance data reference |
| Category Scores | Normalized category scores plus each numbered mid-quarter LA (original/revised) and the final |
| Canvas Roster | Raw roster reference |
| Upload | Two-column upload sheet with headers Student ID and Grade (uses rounded-up letter grade by default) |
All score values are normalized to [0, 1]. Main gradebook score columns are displayed with up to OUTPUT_SCORE_DECIMAL_PLACES decimal places, trailing zeros are hidden, and stored numeric values are rounded to one extra decimal place.
When generate_gradebook.py runs it also creates/updates a workbook named <COURSE_PREFIX>_missing_scores.xlsx (path configured in config.MISSING_SCORES_FILE). This workbook contains two sheets:
Missing Exams: lists students who have a 0 or missing score on any Learning Assessment or the final. Columns:SIS User ID,Student,LA1,LA1 Replacement,LA2,LA2 Replacement, ...,LA{N},LA{N} Replacement,FinalwhereNisN_LEARNING_ASSESSMENTS. The first two rows after the header showclass_meanandclass_stdvalues for each exam, calculated from nonzero normalized revised LA scores where revision data is available; the final uses its raw normalized score because it has no revisions. The generator normalizes each exam as total score divided by max points, inferring max points from Gradescope problem-column point totals if needed. Replacement columns are static, review-only suggestions for missing midterm LAs: each suggestion uses the target LA class mean plus target LA class standard deviation times the student's average z-score on later revised LAs and the final. For the last midterm LA, that average contains only the final z-score. Suggestions are capped to the valid normalized range from 0 to 1 and written to four decimal places. If you want to apply a suggested value, copy it into the correspondingLA#column; the replacement suggestion remains visible on the next run for auditability. Only normalized numeric scores entered in theLA#orFinalcolumns are preserved and incorporated into grade calculations on the next run; copied midterm LA values populate both the raw and revised LA scores used for grade calculations.Missing Problem Sets: lists students who have a 0 or missing score on any loaded Problem Set. Columns:SIS User ID,Student,PS1,PS2, ...,PS{N}whereNis theN_PROBLEM_SETSsearch upper bound. If an entire problem-set export is absent, that assignment is skipped rather than listed as missing for every student. Manual normalized numeric entries are preserved and applied to the corresponding problem-set totals for grade calculation.
Populate these sheets with oral/alternate exam scores or corrected PS totals as normalized values from 0 to 1, then re-run generate_gradebook.py to incorporate manual scores into the gradebook.
python tools/inspect_student_grade.py <SIS_USER_ID>Prints a detailed breakdown of every PS, each mid-quarter LA, the final, attendance, and RQ score for that student, followed by the summary-only LA aggregate and overall-grade calculation. This is an instructor utility for targeted local review with private files in place. It is not safe for agentic AI workflows and should not be run by coding agents.
Run the synthetic tests with:
python tests/test_synthetic_grade_logic.pyThese tests use synthetic in-memory data and temporary directories; they do not require real Canvas or Gradescope exports and do not leave dummy grade files in the project.
Set DEBUG_LEVEL = 2 in config.py for aggregate diagnostics while generating
the gradebook. The generator reports table-level health information such as
row counts, score-column valid/zero/nonzero counts, missing/non-numeric counts,
min/max/mean values, duplicate ID counts, and all-zero warnings. These summaries
are intended to catch failed score calculations, zeroed-out columns, NaN-heavy
tables, and ID-matching issues without printing individual student rows or
sample scores.
For AI-assisted development, use the synthetic tests and these aggregate
diagnostics. Do not run tools/inspect_student_grade.py in agentic sessions,
because it prints a detailed single-student grade breakdown.
Problem set and learning assessment revisions are represented by separate Gradescope reflection/revision assignments. Each revised problem has its own rubric score in that reflection/revision export, and the grade calculation treats that per-problem score as a multiplier on the allowed points back. The code does not inspect individual rubric item labels; it reads the exported per-problem score and divides it by REFLECTION_MAX_SCORE.
The original PH81 implementation used a 5-point rubric per revised problem:
| Component | Points | Current rubric meaning |
|---|---|---|
| Reflection | 3 | 1 point for clearly identifying where the original solution went wrong, plus up to 2 points for the quality of the reflection |
| Revision | 2 | Up to 2 points for the revised solution, with partial credit if the revision is not sufficiently complete |
The current Gradescope rubric also uses non-credit/penalty items for cases such as no original solution tagged, unclear labeling that cannot be graded, or no reflection/revision submitted. Those items affect the exported reflection/revision score before this code reads it.
Instructors using a different reflection/revision scoring method should update the Gradescope rubric and config.REFLECTION_MAX_SCORE together. The points-back fractions (PSET_POINTS_BACK_FRACTION and LA_POINTS_BACK_FRACTIONS) are separate policy choices: they control how much of the lost original-assignment credit is recoverable when the rubric score is full credit.
- Problems worth ≤
PSET_NO_REVISION_THRESHOLDpoints are not eligible for revision. - For eligible problems:
points_back = PSET_POINTS_BACK_FRACTION × points_lost × (reflection_score / REFLECTION_MAX_SCORE) revised_score = original_score + points_back- Category score is weighted by max points across all PSets (optionally dropping the lowest).
N_PROBLEM_SETSis an upper bound on problem set numbers to search for. Missing problem-set files are skipped for category-score denominators because their generated max-point columns are 0.- Pro-rate optionally applies: if score ≥ threshold → 1.0; otherwise
score / threshold.
- Loaded from Canvas when
PSET_SUBSKILLS_ENABLED = True. - Assignment columns must match
PSET_SUBSKILLS_ASSIGNMENT_PATTERNafter mild normalization of case, spacing, Canvas assignment IDs, colons, and hyphens. - Each sub-skill assignment is normalized by its own Canvas Points Possible value, then all detected sub-skills are averaged equally.
N_PSET_SUBSKILLSdefines the assignment numbers to search for; missing/unmatched sub-skills are skipped rather than counted as zero.- Pro-rate optionally applies using
PRORATE_THRESHOLDS['problem_set_subskills']. - Overall weighting is carved out of the configured
problem_setsweight usingPSET_SUBSKILLS_WEIGHT_FRACTION.
- All problems are eligible for revision.
- Same formula as PSets, using per-LA
LA_POINTS_BACK_FRACTIONS. - The final exam has no revisions.
- Pro-rate does not apply to LAs.
N_LEARNING_ASSESSMENTScontrols the numbered midterm LAs. For eachifrom 1 throughN_LEARNING_ASSESSMENTS, configure the matchingla_iweight inWEIGHTS; validation fails if one is missing.- Each LA is normalized to [0, 1] individually and weighted by its own entry in
WEIGHTS. - The Category Scores sheet shows each numbered LA (original and revised) and the final individually. The weighted-average
laandla_reviseddisplay values appear only in the two Summary sheets.
- Loaded from Canvas. Assignment columns must match
RQ_ASSIGNMENT_PATTERN(e.g.,"RQ 1"). - Each matched RQ is normalized by its own Canvas Points Possible value, then the detected normalized RQ scores are averaged equally.
N_READING_QUIZZESdefines the assignment numbers to search for; missing/unmatched RQs are skipped rather than counted as zero.- Only active when
RQ_ENABLED = True.
overall = sum(category_score_i × weight_i) / sum(weight_i)
Dividing by the sum of weights means unused categories (weight = 0) are automatically excluded.
- Computed for revised overall score only.
- Mean and std exclude students below
Z_SCORE_THRESHOLD(default 50%).
Final letter grades assigned based on the GRADE_CUTOFFS dictionary defined in the config.py file. If a student's numeric overall score falls within GRADE_ROUNDING_MARGIN (default 1%) below a higher cutoff, the grade is rounded up; that rounded value is stored in the Summary sheets as column letter_round so it can be reviewed before uploading. By default, the "Upload" sheet uses the rounded grade. Adjust the margin in config.py via GRADE_ROUNDING_MARGIN.
All scores are normalized to [0, 1].
| Column | Description |
|---|---|
ps{i} / ps{i}_revised / ps{i}_max |
Per-PS raw scores |
la{i} / la{i}_revised / la{i}_max |
Per-LA raw scores |
final / final_max |
Final exam raw score and max points; there is no final_revised column because the final has no revisions |
pset / pset_revised |
PS category score [0, 1] |
pset_subskills |
Problem set sub-skills score [0, 1] |
la{i} / la{i}_revised |
Individual normalized LA score [0, 1] in Category Scores |
final |
Individual normalized final-exam score [0, 1] in Category Scores |
la / la_revised |
Weighted-average LA display score [0, 1] in Summary and Summary (Sorted) only |
attendance |
Attendance fraction [0, 1] |
rq |
Reading quiz mean [0, 1] |
overall / overall_revised |
Weighted overall grade [0, 1] |
revision_benefit |
overall_revised − overall |
zscore |
Revised-score z-score relative to students above threshold |
letter |
Letter grade from revised overall score |
letter_round |
Letter grade rounded up when within GRADE_ROUNDING_MARGIN (stored separately; used by the Upload sheet) |
This project was created and maintained by Charles Blakemore for Stanford physics instruction, with substantial development support from AI coding tools, especially GitHub Copilot and OpenAI Codex. These tools were used extensively for implementation, refactoring, documentation, and debugging support, alongside direct instructor design, review, testing, and maintenance.
Grading policy decisions, privacy constraints, validation, and final responsibility for use of the code remain with the human maintainer. The repository is intentionally documented with AI-assisted development in mind: privacy guardrails, synthetic tests, and aggregate diagnostics are included so future maintainers can continue using coding assistants without exposing student data.
This project is released under the MIT License. See LICENSE for details. Given standard 'work-for-hire' stipulations, Stanford University retains copyright.