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,107 @@
# How to walk this map
One job: tell a cold agent which parts of the app are in force, which are decoration,
and which words mean two things — before it opens a card.
Verified against commit `63732df` (detached HEAD), 2026-08-16.
## The three universes
| Universe | Meaning |
|---|---|
| **live** | In force. Implement and cite against these. |
| **leftover** | Still present and still wired, but no longer the main path. Touch only if that path is in scope. |
| **ghost** | Named or filed, not wired. **Do not implement against these.** |
Everything in `objects/` and `processes/` is `live` unless its frontmatter says otherwise.
### Ghosts — present in the tree, unreachable
- **`SchoolsController` + `app/views/schools/*` (9 files).** No route reaches them.
`resources :schools` appears only inside `namespace :admin` (`config/routes.rb:58`),
which resolves to `Admin::SchoolsController`. Rails scaffold remnant. If you want to
change school admin, edit `app/controllers/admin/schools_controller.rb`.
- **`admin_v2` / `/admin-new`.** Survives only as a comment (`config/routes.rb:42`).
The in-house admin is the live `namespace :admin` at `/admin`.
### Leftovers — wired, but not the path to build on
- **`users.classroom_id` direct membership.** See the trap below — this one is
load-bearing, not dead.
- **`PortfolioTransaction.reason: grade_earnings`** (enum value 3). Marked
`# Deprecated, will be removed in future` at `app/models/portfolio_transaction.rb:13`
and referenced nowhere else. New earnings use `math_earnings` / `reading_earnings`.
- **`docs/old-site/`.** Training material for the predecessor site.
## The traps
Four places where the obvious reading of the code is wrong. Each one has bitten or is
likely to.
### 1. A student's classroom has two rival sources of truth
Both are wired **right now**:
| Path | Where | Used by |
|---|---|---|
| `users.classroom_id` | `db/schema.rb:374`, `app/models/user.rb:20` | `Classroom#students` (`app/models/classroom.rb:20`), `Order.for_teacher` (`app/models/order.rb:40-42`), `ApplicationController` redirects |
| `classroom_enrollments` | `app/models/classroom_enrollment.rb` | `Student#current_classrooms` (`app/models/student.rb:24`), `Classroom#current_students` (`app/models/classroom.rb:74`) |
`Student#primary_classroom` bridges them and falls back to `classroom_id`
(`app/models/student.rb:37-43`, commented "for backward compatibility"). `Student`
creation writes **both**: `create_initial_enrollment` fires only when `classroom_id` is
present (`app/models/student.rb:10,82-86`).
**Consequence:** a student enrolled only via `ClassroomEnrollment` is invisible to
`Classroom#students` and to teacher order scoping. Changing either path without the
other splits the roster. Start at `objects/org/classroom-enrollment.md`.
### 2. Money is integer cents — except in one method
Every column is cents (`amount_cents`, `price_cents`, `worth_cents`). But
`Portfolio#cash_balance` returns **dollars** as a float
(`app/models/portfolio.rb:16-18` → `cash_on_hand` → `/ 100.0` at `app/models/portfolio.rb:67-69`).
Callers must multiply back: `app/models/order.rb:137` does
`(user.portfolio&.cash_balance || 0) * 100`. Any new caller that forgets is off by 100×.
Detail: `objects/money/portfolio.md`.
### 3. Cash is never stored
`portfolios` has exactly three columns — `id`, `user_id`, timestamps
(`db/schema.rb:181-186`). There is no balance column. Every balance is a live SUM over
`portfolio_transactions`, **plus a subtraction for pending orders and their fee**
(`app/models/portfolio.rb:71-100`). Balance is therefore a function of open orders, not
just settled history.
### 4. One API key, two homes, two different failure modes
| Reader | Source | Missing-key behaviour |
|---|---|---|
| `AlphaVantageApiClient` | `ENV["ALPHA_VANTAGE_API_KEY"]`, default `nil` (`app/services/alpha_vantage_api_client.rb:11`) | logs an error and returns `nil` (`:31-36`) |
| `StockAttributeUpdate` | global `API_KEY` (`app/services/stock_attribute_update.rb:75`) from `config/initializers/api_keys.rb:1`, default `"test-api-key"` | silently queries Alpha Vantage with a junk key |
One fact, two homes. If you consolidate, `objects/trading/stock.md` lists what reads it.
## Name collisions, stated once
| Product word | Code name | Note |
|---|---|---|
| SIF dollars | `PortfolioTransaction#amount_cents` | integer cents; no `Money`/`BigDecimal` wrapper |
| balance / cash on hand | `Portfolio#cash_balance` | derived; returns **dollars** |
| grade (5th–8th) | `Grade`, `grades.level` | `app/models/grade.rb` |
| grade (A+…F) | `GradeEntry#math_grade`, `#reading_grade` | `app/models/grade_entry.rb:15` |
| admin | `users.admin` boolean | **not** an STI type (`app/models/user.rb:39,43`) |
| student / teacher | STI on `users.type` | `Student < User`, `Teacher < User` |
| holding / position | `PortfolioStock` rows vs `PortfolioPosition` | rows are append-only lots; the PORO is the aggregate |
| gradebook "finalize" | `GradeBook#verified!` then `completed!` | two statuses, one button |
| Stocks for Good | `StocksInTheFuture` | `docs/README.md:1` vs `config/application.rb:14` |
## Walking order
1. This file — universes and traps.
2. `objects/_index.md` — find the noun.
3. One card. Follow its `See:` link into the app tree.
4. Before editing: `effects/CONTEXT.md`.
Do not read the whole `objects/` folder. The index exists so you do not have to.