CSC/ECE 517 Spring 2026 - E2612. Course-based reports
E2612: Course-Based Reports
Design Document
| Project | E2612 — Course-based Reports |
|---|---|
| Mentor | Ed Gehringer |
| Authors | Mihir Kamat, Ravi Goparaju |
| Status | Implemented |
| Last Updated | April 2026 |
1. Overview
Expertiza currently surfaces course-level student performance data through two separate reports — a 360-degree assessment report and an aggregated teammate review report — each with their own access points and presentation formats. This fragmentation makes it difficult for instructors and TAs to get a coherent picture of how students are doing across a course.
This project consolidates both into a single unified table, accessible from the Courses list page. The table displays every enrolled student alongside their grades, topics, teammate scores, and author feedback scores for every assignment in the course, with instructor-controlled column visibility to keep the view manageable. The implementation targets Expertiza's reimplemented React/TypeScript frontend. The legacy Rails views serve only as a reference for the underlying data model; no legacy code was reused.
2. Requirements
The following requirements were established through the project specification and mentor meetings with Professor Ed Gehringer.
Display
- All relevant data must appear in a single unified table view, without pagination or splitting across multiple components.
- Students are placed along the vertical axis (rows); assignments are represented as grouped sub-column sets along the horizontal axis.
- Assignment column groups must be ordered chronologically by due date, earliest to latest.
Interactivity
- Each column must support click-to-sort, toggling between ascending and descending order with a visible directional indicator.
- Individual column types (e.g., Peer Grade, Topic) must be togglable via checkboxes, hiding or showing that field across all assignments at once.
Column Exclusion (highest priority rule)
- If an assignment does not structurally support a given column type (e.g., a non-topic assignment has no topic), that column must be excluded entirely from that assignment's group — it must not appear as an empty or placeholder cell.
- This rule overrides checkbox visibility settings.
- Assignments that do support a column type but simply have no data yet remain visible with an empty cell and stay toggleable.
- The report must be reachable via a button in each course row on the Courses list page.
3. Access Point and Routing
The report is triggered by a brown report icon button in the Actions column of the Courses list. Clicking it opens a dedicated route:
/courses/:courseId/class_assignment_overview
A dedicated route is warranted because the table is wide and data-heavy, and embedding it inline would compromise readability. The route is protected and requires at least ROLE.TA to access.
When the user is on the report route, Course.tsx suppresses the normal course management table and renders only the nested outlet, preventing the courses list from appearing underneath the report.
4. Table Design
4.1 Layout
The table is structured as a student-by-assignment matrix. Each row represents one enrolled student; each assignment contributes a group of sub-columns spanning the horizontal axis.
A separate locked table above the main table displays the class average row. The class average row renders all numeric fields in bold. Null values for individual student cells are preserved as empty; for class average calculations only, null scores are counted as zero.
4.2 Per-Assignment Sub-Columns
Assignment groups appear in ascending chronological order, sorted by each assignment's final review deadline. The backend enforces this ordering — if an assignment's latest due date is not a review deadline, the backend returns a 500 error.
Each assignment group may contain the following sub-columns, depending on what the assignment structurally supports and which toggles the instructor has enabled:
| Sub-column | Data Source | Notes |
|---|---|---|
| Topic | SignedUpTeams via TeamsParticipants |
Only rendered if has_topics is true for that assignment
|
| Peer Grade | aggregate_review_grade on AssignmentTeam |
Weighted average of reviews received |
| Instructor Grade | grade_for_submission on the Teams table |
Only populated if the instructor entered grades within Expertiza |
| Avg. Teammate Score | Teammate review response maps for that participant | Average score received from teammates |
| Avg. Author Feedback Score | Author feedback response maps tied to reviews written by that participant | Toggleable; used by some instructors |
The column exclusion rule is enforced at column generation time in courseAssignmentOverviewService.ts, not at render time. The topic sub-column is only created for assignments where has_topics is true, regardless of checkbox state. All other sub-columns are created for every assignment and are subject only to checkbox visibility.
4.3 Class Average Row
A class average row is computed across all student rows and rendered in a separate locked table above the main student table. Numeric fields are averaged across all students. Null values are treated as zero for class average calculations only. Student rows still preserve null values and render empty cells rather than zero.
5. Column Visibility and Sorting
5.1 Checkbox Controls
A control panel above the table exposes one checkbox per sub-column type. The available toggles are:
- Topic
- Peer Grade
- Instructor Grade
- Avg. Teammate Score
- Avg. Author Feedback Score
Toggling a checkbox hides or shows that field type across every assignment simultaneously. These are global per-field-type toggles, not per-assignment. The column exclusion rule takes priority over all checkbox state — a checkbox cannot surface a column that the assignment does not structurally support.
5.2 Row Sorting
Any column header can be clicked to sort all student rows by that column's values. A second click reverses the direction. The sort state is indicated by a caret in the column header. Pagination and global filtering are both disabled; all rows are shown in a single unified view.
6. Backend Implementation
6.1 Endpoint
GET /course_reports?course_id=:courseId
Authorization is restricted to teaching staff for the requested course — the course instructor or mapped TAs.
| Status Code | Condition |
|---|---|
| 200 | Authorized instructor or TA |
| 401 | Missing or invalid bearer token |
| 403 | Authenticated user is not teaching staff for this course |
| 404 | Course not found |
| 500 | An assignment's final due date is not a review deadline |
6.2 Response Shape
{
"course_id": 44,
"course_name": "CSC 517",
"assignments": [
{
"assignment_id": 12,
"assignment_name": "Project 1",
"has_topics": true
},
{
"assignment_id": 18,
"assignment_name": "Project 2",
"has_topics": false
}
],
"students": [
{
"user_id": 101,
"user_name": "alice",
"assignments": {
"12": {
"participant_id": 501,
"peer_grade": 90.0,
"instructor_grade": 95,
"avg_teammate_score": 82.5,
"avg_author_feedback_score": 88.0,
"topic": "Topic Alpha"
},
"18": {
"participant_id": 509,
"peer_grade": 84.0,
"instructor_grade": 91,
"avg_teammate_score": null,
"avg_author_feedback_score": 76.0
}
}
}
]
}
Assignments are sorted by their final review deadline before building the report. The topic field is present only when has_topics is true for that assignment.
6.3 Data Sources
| Data Point | Source |
|---|---|
| Instructor grade | grade_for_submission on the Teams table
|
| Peer grade | aggregate_review_grade on AssignmentTeam
|
| Avg. teammate score | Teammate review response maps for that participant |
| Avg. author feedback score | Author feedback response maps tied to reviews written by that participant |
| Assignment ordering | Final review deadline from assignment due date records |
| Topic selection | SignedUpTeams via TeamsParticipants
|
7. Frontend Implementation
7.1 New Files
| File | Purpose |
|---|---|
src/pages/Courses/CourseReport.tsx |
Main report page component. Fetches data, manages checkbox toggle state, renders the class average table and student table. |
src/services/courseAssignmentOverviewService.ts |
Transform layer. Converts the backend response into flat table rows and dynamic grouped TanStack column definitions. Enforces the column exclusion rule. |
src/types/courseAssignmentOverview.ts |
Type definitions for the API response shape, frontend row model, and checkbox visibility state. |
7.2 Modified Files
| File | Change |
|---|---|
src/App.tsx |
Added protected route for :courseId/class_assignment_overview, requiring at least ROLE.TA.
|
src/pages/Courses/Course.tsx |
Added onReportHandle callback; suppresses the course list table when the current route is the report page.
|
src/pages/Courses/CourseColumns.tsx |
Added a brown rectangular report icon button to the Actions column, navigating to the report page. |
src/components/Table/Table.tsx |
Added disablePaginationRowModel prop. When true, getPaginationRowModel is not attached to the TanStack config, allowing all rows to render without pagination internals interfering.
|
7.3 Frontend Type Definitions
interface AssignmentMetadata {
assignment_id: number;
assignment_name: string;
has_topics: boolean;
}
interface AssignmentFieldData {
participant_id: number;
peer_grade: number | null;
instructor_grade: number | null;
avg_teammate_score: number | null;
avg_author_feedback_score: number | null;
topic?: string;
}
interface StudentReportEntry {
user_id: number;
user_name: string;
assignments: { [assignmentId: string]: AssignmentFieldData | null };
}
interface CourseReportApiResponse {
course_id: number;
course_name: string;
assignments: AssignmentMetadata[];
students: StudentReportEntry[];
}
type VisibleFields = {
topic: boolean;
peerGrade: boolean;
instructorGrade: boolean;
avgTeammateScore: boolean;
avgAuthorFeedbackScore: boolean;
};
8. Reuse Decisions
| Component | Decision | Rationale |
|---|---|---|
Table.tsx |
Reused directly, with one prop addition | Provides sort, grouped headers, and column visibility out of the box |
gradesService.ts pattern |
Pattern reused | Consistent service/transform separation |
Bootstrap Form.Check |
Reused | Matches existing form patterns in the codebase |
Course.tsx routing conventions |
Reused | Consistent navigation entry point |
ReviewTableau.tsx transform pattern |
Adapted, not copied | Useful reference for dynamic column generation |
ViewScores.tsx data shape |
Not reused | Data model is too simple for this use case |
| Legacy Rails report views | Not reused | Clean reimplementation only |
The project was designed to be minimally intrusive by reusing existing, well-tested code whenever possible instead of rebuilding functionality from scratch. The biggest reuse decision was the shared Table.tsx component, whose TanStack Table foundation already supported grouped headers, sorting, and column visibility, covering most of the report’s interactive needs with only a small addition: disablePaginationRowModel. We also followed the existing patterns in gradesService.ts and Course.tsx for the service and routing layers, keeping the new code consistent and easy to understand within the existing codebase.
9. Tests
Frontend
The main page test is CourseReport.test.tsx (line 98). It mocks useParams, useAPI, and the shared Table component, then verifies:
Fetches /course_reports?course_id=44 when courseId exists, and skips the request when missing.
Shows loading spinner and error alert states without rendering tables.
Renders the title as Course Report — CSC 517.
Renders all field toggles checked by default: Topic, Peer Grade, Instructor Grade, Avg. Teammate Score, Avg. Author Feedback Score.
Splits data into two tables: first table gets only Class Average, second table gets student rows.
Disables pagination/global filtering on both tables.
Updates column visibility when each checkbox is toggled.
The transformation/service tests are in courseAssignmentOverviewService.test.ts (line 92). They cover:
buildRows: one row per student plus a class average row.
Dynamic keys like a3_peerGrade, a3_instructorGrade, etc.
Topic fields only for topic-enabled assignments.
Missing assignment participation is skipped.
Student null scores remain null.
Class average treats null numeric scores as 0.
Empty student lists still produce a Class Average row.
buildColumns: student column first, assignment groups, sortable subcolumns, visibility metadata, and bold rendering for class average cells.
There are also adjacent course navigation tests:
Course.test.tsx (line 107) checks that /courses/1/class_assignment_overview renders only the nested outlet, not the normal course table.
CourseColumns.test.tsx (line 17) checks the “View Course Report” action button, tooltip, click handler, and styling.
Backend
The backend request spec is course_reports_controller_spec.rb (line 313). It builds a fairly rich dataset with a course, two in-course assignments, one outside-course assignment, teams, participants, review responses, feedback responses, teammate reviews, topics, and due dates.
The successful 200 case validates:
Response shape: course_id, course_name, assignments, students.
Assignment metadata includes assignment_id, assignment_name, has_topics.
Assignments are ordered by final review deadline.
Topic-enabled assignments include topic; non-topic assignments do not.
Outside-course assignments are excluded.
Student rows include expected assignment entries keyed by assignment id.
Calculated fields are correct for multiple participant situations:
peer_grade
instructor_grade
avg_teammate_score
avg_author_feedback_score
topic
It also covers error/auth cases:
404 when the course is missing.
500 when an assignment’s final due date is not a review deadline.
403 for students.
403 for instructors outside the course teaching staff.
401 for invalid tokens.
401 when no bearer token is provided.
In short: backend tests prove the API contract and score calculations from database records; frontend tests prove the page fetches that contract, transforms it into dynamic report rows/columns, and gives users the expected table/toggle behavior.
10. Out of Scope
The following are not part of this implementation:
- Meta-review columns — deprecated in Expertiza
- LLM-based review scores — not yet available in the new system
- Pagination — single unified view only
- Per-assignment field customization — toggles are global by field type, not per assignment
- Avg. Teammate Score (Given) — not returned by the backend in this iteration
