convert pdf to md, add OVERVIEW and docs

This commit is contained in:
2026-08-17 22:30:18 +00:00
parent 95d5787868
commit 4df62d2609
44 changed files with 3307 additions and 1 deletions

View File

@@ -0,0 +1,90 @@
---
type: object
cluster: gradebook
universe: live
status: verified
entity: app/models/grade_entry.rb
---
# GradeEntry
One student's row in one [grade-book](grade-book.md): two letter grades, attendance days,
a perfect-attendance flag. **This is where every payout amount is defined.**
Verified 2026-08-16 against commit `63732df`.
## Why this shape
The payout table is five Ruby constants on this model, all in **cents**
(`app/models/grade_entry.rb:9-13`):
| Constant | Value | Meaning |
|---|---|---|
| `EARNINGS_PER_DAY_ATTENDANCE` | `20` | $0.20 per day present |
| `EARNINGS_FOR_A_GRADE` | `3_00` | $3.00 for any A |
| `EARNINGS_FOR_B_GRADE` | `2_00` | $2.00 for any B |
| `EARNINGS_FOR_IMPROVED_GRADE` | `2_00` | $2.00 for improving |
| `EARNINGS_FOR_PERFECT_ATTENDANCE` | `1_00` | $1.00 bonus |
They are not configuration. Changing what a student earns is a code change and a deploy —
there is no admin screen and no database row for these.
**`GRADE_OPTIONS` is ordered best-to-worst on purpose** (`:15`). `improved_grade?`
compares array *indices*, treating a lower index as better (`:64-67`). Reordering or
inserting into that array silently changes every improvement bonus in the app.
**There are no validations on this model at all** — grades are constrained only by the
`<select>` in `app/views/grade_books/_grade_entry.html.erb:8,17`, and the controller
permits the values straight through (`app/controllers/grade_books_controller.rb:53-57`).
A value outside `GRADE_OPTIONS` saves fine, then makes `improved_grade?` compare `nil`
indices and raise `NoMethodError` during the next quarter's payout. Grades C through F
earn nothing but are legal; anything not in the list is a latent failure.
## Shape
- Table `grade_entries`, `db/schema.rb:109-121`; unique on `[grade_book_id, user_id]`
(`db/schema.rb:118`) — one row per student per book
- `math_grade`, `reading_grade` — plain strings, nullable, unvalidated
- `attendance_days` — bigint, nullable; `earnings_for_attendance` returns 0 when blank
(`:17-21`)
- `is_perfect_attendance` — boolean, default false, `null: false` (`db/schema.rb:113`)
- `belongs_to :grade_book`, `belongs_to :user` (`:4-5`) — `user`, not `student`
- Earnings readers: `earnings_for_attendance`, `earnings_for_math`,
`earnings_for_reading`, `attendance_perfect_earnings`, `math_improvement_earnings`,
`reading_improvement_earnings` (`:17-49`)
Every earnings method is a **pure reader**. Nothing here writes money —
`DistributeEarnings` calls them and creates the deposits.
## Connected to
- **owns:** —
- **owned-by:** [grade-book](grade-book.md), [user](../identity/user.md)
- **joins:** —
- **looks-like-but-is-not:** `math_grade` is a **letter** (`"A+"`…`"F"`), unrelated to
[grade-level](../org/grade-level.md), which is 5–8.
## If you change this
- **Hits:** [portfolio-transaction](../money/portfolio-transaction.md) amounts — these
constants are the amounts; `DistributeEarnings`
(`app/services/distribute_earnings.rb:54-73`), which sums attendance + math + reading;
the [finalize-gradebook-earnings](../../processes/finalize-gradebook-earnings.md)
movement; `AttendanceEntryPresenter`.
- **Does not hit:** already-paid deposits. Editing an entry after finalize changes
nothing retroactively — the deposits are independent rows with no link back to the
entry that produced them (`db/schema.rb:170-179` has no `grade_entry_id`). Re-paying
would require re-running finalize, which the `completed?` guard blocks.
## Surfaces
| Surface | Role |
|---|---|
| `GradeBooksController#update` (+ autosave Stimulus controller) | teacher writes |
| `DistributeEarnings` | reads |
| `AttendanceEntryPresenter` | reads |
## See
- Source: `app/models/grade_entry.rb`, `db/schema.rb:109-121`
- As-built: `docs/gradebook-earnings.md`