# School administrator manual

A practical, task-oriented guide for the school administrator who runs AGS Vocab for their school.

> Audience: **non-developer** — you are the operations owner. You sign learners up, configure the system, monitor activity, and keep things running. You do not need to touch code.

---

## 1. Getting started

### 1.1 What AGS Vocab is

AGS Vocab is an adaptive vocabulary assessment. Each learner takes a short, adaptive test; the system measures their working vocabulary on the **Vocabile scale** (0–1500), which maps to grade bands from Kindergarten through Beyond Grade 12. Learners earn **kudos** for correct answers and lose **lives** for wrong ones (except in the Diagnostic test). Three test modes are available — Diagnostic, Skill Practice, and Vocab Path.

You manage the system through the **admin panel** at:

```
https://your-school.allgifted.com/admin
```

(During development, this is `http://127.0.0.1:8000/admin`.)

### 1.2 Signing in to the admin panel

1. Open the admin URL in your browser.
2. Enter your **registered email** (the one your account was set up with).
3. Click **Send code**.
4. Check your inbox for the 6-digit code (subject: *Your AGS Vocab sign-in code*).
5. Enter the code on the screen and click **Sign in**.

> **No passwords.** AGS Vocab uses one-time codes only. If you don't receive the email within 1 minute, check your spam folder, then re-request.

If the system has just been installed and email isn't configured yet, the OTP code is also written to the server log file (`storage/logs/laravel.log`) — ask your developer to read it for you the first time you sign in.

### 1.3 What you'll see

After sign-in you land on the **Dashboard**:

- **5 stat cards** across the top: total learners, total words, active questions, sessions today, average Vocabile score
- **Recent sessions** table: the last 10 test sessions with their scores

The left sidebar groups every admin area by purpose:

- **Content** — Words, Questions
- **Taxonomy** — POS, Vocabile Levels, Bloom's, Skills, Difficulty, Genres, Voices
- **Sessions** — every test session in the system
- **Gamification** — Kudos events, Lives events
- **User Management** — Users
- **System** — Statuses, Configs

---

## 2. Day-one setup checklist

Run through this list the first time you sign in to a new deployment.

| # | Task | Where | Why |
|---|---|---|---|
| 1 | Update branding (school name, colors, logo) | Configs → Branding & Theme | Learners see your school's identity, not the AGS defaults |
| 2 | Choose the school accent | Configs → `default_accent_code` | Picks the family of reader voices learners hear |
| 3 | Decide on Singlish | Configs → `show_singlish` | Turn off if your school doesn't want Singlish vocab |
| 4 | Confirm mail is working | Configs → Mail section + send yourself an OTP | Without working mail, no one can sign in |
| 5 | Create your other admin accounts | User Management → Users | Avoid sharing a single login |
| 6 | Walk through one test as a learner | Open the Flutter app, sign in with a personal email | Confirms the full loop works end-to-end |

---

## 3. Common tasks

### 3.1 Add a new learner

Learners self-register the first time they enter their email or phone in the Flutter app — there is **no manual sign-up step you need to do**. The first OTP request creates the user automatically with the learner role.

If you need to *check* a user exists or update their details:
1. Sidebar → **User Management → Users**
2. Search by email or name
3. Click the row to open

### 3.2 Promote a learner to admin

1. Sidebar → **User Management → Users**
2. Open the user
3. Change **Role** to `admin`
4. Save

They can now sign in to `/admin` and have the same powers you do.

### 3.3 Grant unlimited lives (premium)

For a paid learner or a class trial:
1. Sidebar → **User Management → Users**
2. Open the user
3. Toggle **Unlimited lives** on
4. Save

The change takes effect immediately on their next API call.

### 3.4 Top up a learner's lives manually

Useful for support cases where a learner ran out unfairly:
1. Sidebar → **User Management → Users**
2. Open the user
3. Edit **Lives** (max 99)
4. Save

The change is logged in **Gamification → Lives events** with `kind = admin_adjusted`.

### 3.5 Reset a learner's progress

There is **no destructive reset button** by design. To wipe sessions for a learner:
1. Sidebar → **Sessions → Test sessions**
2. Filter by the user
3. Bulk-delete the rows

Their Vocabile score is recomputed lazily from `ability_estimates`. To clear those too, ask your developer to delete the matching `ability_estimates` rows (it's intentional that this isn't exposed in the UI — it's destructive).

### 3.6 Change the school's reader accent

1. Sidebar → **System → Configs**
2. Find **Reader accent** (`default_accent_code`)
3. Pick one of:
   - `en-US` — American
   - `en-GB` — British
   - `en-AU` — Australian
   - `en-SG` — Singaporean
   - `en-IN` — Indian
   - (blank) — rotate across all accents
4. Save

The Flutter app pulls the new accent from `/api/voices` on next launch (or refresh).

### 3.7 Turn off Singlish vocabulary

1. Sidebar → **System → Configs**
2. Find **Show Singlish vocabulary** (`show_singlish`)
3. Toggle to **No**
4. Save

Every Singlish-tagged word is hidden from all tests instantly (the IRT item selector reads the flag live with a 60-second cache).

### 3.8 Update the school logo

1. Drop your logo file into `public/brand/` on the server (PNG, transparent background recommended, ~256×256)
2. Sidebar → **System → Configs**
3. Edit **Logo path** to `brand/your-logo.png`
4. Save

The admin panel and Flutter app pick it up on next page load.

### 3.9 Reconfigure email sending

If email stops arriving (provider change, password rotation):
1. Sidebar → **System → Configs**, filter category = **Mail**
2. Edit:
   - `mail_host` (e.g. `mail.privateemail.com`)
   - `mail_port` (`465` for SSL, `587` for TLS)
   - `mail_encryption` (`ssl` or `tls`)
   - `mail_username` (full email address)
   - `mail_password` (re-enter; stored encrypted)
   - `mail_from_address` (must be verified with your provider)
   - `mail_from_name`
3. Save
4. Test by signing yourself out and requesting a fresh OTP

### 3.10 Take a word offline temporarily

Maybe a word has a typo, or the answer key is being reviewed:
1. Sidebar → **Content → Words**
2. Find the word
3. Change **Status** to **Draft** (or **Archived** to retire permanently)
4. Save

Tests stop serving questions backed by that word immediately. Reactivate later by switching the status back to **Public**.

### 3.11 Disable a question without disabling the whole word

1. Sidebar → **Content → Questions**
2. Open the question
3. Toggle **Is active** off
4. Save

Other questions for the same word continue to be served.

### 3.12 Disable a specific reader voice

For example, the principal didn't like the "elder coach" voice:
1. Sidebar → **Taxonomy → Voices**
2. Find the voice
3. Toggle **Is active** off
4. Save

The voice drops out of rotation on next Flutter app refresh.

### 3.13 Import a new wordlist (bulk add words)

The system can import any CSV of words via the `vocab:import` command.
This is how you scale the bank beyond what hand-curation can do.

1. Get a wordlist in CSV form with header `lemma,pos,vocabile_level,difficulty,source_rank,definition`. See [`word-sources.md`](word-sources.md) for trusted public sources (NGSL, AVL, Dale-Chall) and where to download them.
2. Drop the file under `database/seeders/data/imports/` on the server.
3. From the project root run:
   ```
   php artisan vocab:import database/seeders/data/imports/your_file.csv \
       --source=ngsl --default-level=G5 --default-difficulty=familiar
   ```
4. Check the summary line — it reports created / updated / protected-curated counts.
5. Re-running is safe; existing curated words are NOT overwritten by default.

Reasoning behind every source the bank pulls from is documented in [`word-sources.md`](word-sources.md). Update that doc whenever you add a new source — it's how you keep the bank defensible to academic reviewers and parents.

### 3.14 Create a classroom and enrol students via QR

The fastest way to get a class of students onboarded:

1. Sidebar → **User Management → Classrooms** → **New classroom**
2. Fill in name (e.g. "7A"), optional grade level + academic year + subject
3. Save — the system auto-generates a join code (format `XXX-XXXX`)
4. From the classroom row, click **Actions → Show join QR** to display the QR + plain-text code in a modal
5. Or click **Actions → Print poster** for an A4-printable poster (classroom name, QR, code, instructions) — open in new tab, Ctrl-P
6. Display the poster in the classroom or share the URL via email
7. Students scan or visit `https://<school>.agsvocab.com/join/<code>`, enter their email/phone, get an OTP, and they're enrolled

**Pausing or rotating the join code**:
- **Pause joins** once your roster is locked: edit the classroom → toggle "Join code enabled" off
- **Rotate the code** if it was leaked: Actions → **Regenerate code** (existing enrolled students stay; new joins via the old code are rejected)
- **Time-limit** the code: edit the classroom → set "Code expires at" to e.g. end-of-class-period

**A student can be in multiple classrooms** — give them multiple QR codes (homeroom + subject class + tutoring group). The same person can be a `learner` in one and a `teacher` (peer tutor) in another; the system handles this naturally.

Full design + edge cases documented in [`classes-and-enrolment.md`](classes-and-enrolment.md).

### 3.15 Add a new genre

If you want to add e.g. *Esports* as a learnable genre:
1. Sidebar → **Taxonomy → Genres**
2. Click **New genre**
3. Fill in:
   - **Code**: `esports` (lowercase, single-word)
   - **Name**: `Esports`
   - **Description**: 1-line description
   - **Color**: hex code
   - **Icon**: Heroicon name (e.g. `heroicon-o-trophy`)
4. Save
5. Open **Content → Words** and assign relevant words to the new genre via the *Genres* relation

The genre appears in the Flutter app's *Skill Practice → choose a genre* picker on next refresh.

---

## 4. Monitoring activity

### 4.1 Who's been testing?

Sidebar → **Sessions → Test sessions** shows every session, newest first. Filter by:
- User
- Status (in_progress, completed, abandoned)
- Test type
- Date range

Each row shows items administered, score, theta, and finished_at.

### 4.2 What did a learner answer?

Click into a test session to see the responses table: question, learner's choice, correct/wrong, theta after, kudos awarded, response time.

### 4.3 Kudos ledger

Sidebar → **Gamification → Kudos events** — every kudos award is logged here, with user, source (which question), amount, and timestamp. Read-only (the ledger is append-only by design).

### 4.4 Lives ledger

Sidebar → **Gamification → Lives events** — every life lost, regenerated, granted, purchased, or admin-adjusted. Useful for support investigations ("the learner says they had 5 lives and now have 1").

---

## 5. Troubleshooting

### 5.1 "I'm not receiving the OTP email"

Try in order:
1. Check spam / junk folder
2. Check **System → Configs → Mail** values are correct
3. Open the server's `storage/logs/laravel.log` — every OTP is also logged there
4. Send yourself a test OTP and watch the log for an `Mail` error line

### 5.2 "Learner says the Flutter app says 'couldn't reach the server'"

The Flutter app needs to know your API host. Check with your developer that `API_BASE_URL` is set correctly in the app build.

If you're testing on a learner's device:
- Same WiFi network as your server? Use the server's LAN IP.
- Public deployment? Use the HTTPS URL (e.g. `https://api.your-school.allgifted.com`).

### 5.3 "The dashboard numbers don't update"

Refresh the page — Filament caches some stats per request. If the numbers still look stale, sign out and back in.

### 5.4 "Read-aloud speaks in a robot voice"

The browser/device uses whichever voices the OS provides. On Windows:
1. **Settings → Time & Language → Speech → Manage voices → Add voices**
2. Install **Aria (Natural)** and **Jenny (Natural)** — these are Microsoft's high-quality voices
3. Restart the browser

On Chrome, the system picks the best available voice that matches the school accent + selected persona. Without Natural voices, it falls back to David/Zira (clearly robotic but functional).

### 5.5 "I changed a config and nothing happened"

Configs are cached for 60 seconds. Wait, then refresh. If still no change, ask your developer to run:
```
php artisan cache:clear
```

### 5.6 "A learner is stuck on a test session that's already 'completed'"

The session row should have `status = completed` and a `vocabile_score`. If the Flutter app is showing the live test screen instead of results, ask the learner to:
1. Sign out and back in
2. Pull-to-refresh on the home screen

The home screen reads the latest session state from `/api/auth/me`.

---

## 6. Privacy + safety notes

- **Email addresses and phone numbers** are stored in `users` and `otp_codes`. They never leave your server except in the body of outbound OTP emails.
- **OTP codes** are stored as bcrypt hashes — even with database access, the codes themselves are not recoverable.
- **Mail passwords** in the Configs table are encrypted at rest using Laravel's `APP_KEY`. Rotate the key only with a backup plan — encrypted rows become unreadable without the original key.
- **Learner audio recordings** (pronunciation skill, when enabled) are stored under `storage/app/pronunciations/`. Plan a retention policy with your developer.
- **Kudos events** include user IDs but no personally identifying info beyond that. The ledger can safely be exported for analytics.

---

## 7. Where to get help

- **Bugs / questions about the system itself**: contact AGS support at pam@allgifted.com
- **Lost OTP / locked out**: ask another admin in your school to look you up in **User Management → Users** and reset your email if it's wrong
- **Detailed feature reference**: see [admin-guide.md](admin-guide.md) (developer-oriented) and [technical-spec.md](technical-spec.md)
- **For your learners**: hand them [student-manual.md](student-manual.md)
