# Architecture

The system has two deployable surfaces and one shared database.

```
┌────────────────────────────────┐         ┌────────────────────────────┐
│   Flutter app (web/mobile)     │         │   Filament admin           │
│   - Test-taker UI              │         │   - 15 resources           │
│   - Stickman mascots           │         │   - Crimson AGS theme      │
│   - TTS via window.speechSyn   │         │   - OTP-only sign-in       │
│   - Sound effects              │         │   - Dashboard widgets      │
└─────────────┬──────────────────┘         └─────────────┬──────────────┘
              │ JSON over HTTP                            │ Blade + Livewire
              │ Sanctum bearer tokens                     │ session cookies
              ▼                                           ▼
        ┌─────────────────────────────────────────────────────────────┐
        │  Laravel 11 API + Filament                                  │
        │  - Routes: routes/api.php (API) + Filament panels           │
        │  - 14 API controllers (Auth, Test, Voices, Config, …)       │
        │  - 9 service classes (IRT, OTP, Gamification, Config, …)    │
        │  - 20+ Eloquent models                                      │
        │  - AppServiceProvider boots: mail config from DB, etc.      │
        └─────────────────────┬───────────────────────────────────────┘
                              │
                              ▼
        ┌─────────────────────────────────────────────────────────────┐
        │  MySQL 8 (database `vocabile`)                              │
        │  ~28 tables; see ER section below                           │
        └─────────────────────────────────────────────────────────────┘
                              │
                              ▼
        ┌──────────────────────┐      ┌─────────────────────────────────┐
        │  storage/logs/       │      │  Namecheap Private Email SMTP   │
        │  - laravel.log       │      │  (mail.privateemail.com:465)    │
        │  - OTP fallback      │      │  - OTP delivery                 │
        │  - mail attempt log  │      │  - Notifications                │
        └──────────────────────┘      └─────────────────────────────────┘
```

## Data model — by group

### Content
- **words** — lemma, pos, definition, IPA, respelling, intrinsic difficulty, is_singlish flag, status
- **word_pos_category** — many-to-many: a single word can be both noun and verb
- **word_vocabile_level** — many-to-many: a word can be appropriate at multiple grades
- **word_difficulty_levels** — Common / Familiar / Academic / Advanced / Sophisticated / Rare
- **genre_word** — many-to-many: a word can sit in multiple usage genres (Scientific + Academic Writing)
- **questions** — stem, format, IRT a/b/c parameters, bloom_level, skill, exposure_count
- **question_options** — 4-choice MCQ options, one marked correct
- **pronunciations** — user audio submissions for the Pronunciation skill (when wired)

### Taxonomies (lookup tables)
- **pos_categories** — noun / verb / adj / adv / prep / conj / pron / interj
- **vocabile_levels** — K / G1 / G2 / … / G12 / BEYOND (each with `score_min`, `score_max`, `color_hex`)
- **bloom_levels** — Remember (1) → Create (6), each with `difficulty_offset` added to IRT b
- **skills** — Recognition / Recall / Production / Pronunciation
- **genres** — 20 usage genres (Everyday Conversation, Scientific, Medical, …)
- **statuses** — Only Me / Restricted / Public / Draft / Archived (shared with AGS Math)

### Sessions + results
- **test_types** — Vocab Diagnostic / Skill Practice / Vocab Path
- **test_sessions** — user_id, test_type_id, scope_kind+id (for Skill Practice), theta, theta_se, vocabile_score, status, started/finished timestamps
- **responses** — per-question record: question_id, option_id, is_correct, kudos_awarded, life_deducted, theta_before/after, response_time_ms
- **ability_estimates** — final theta + Vocabile score + level_code for completed sessions

### Identity + gamification
- **users** — name, email, phone, role (admin/user), lives, max_lives, lives_lost_at (JSON queue), is_unlimited_lives, kudos
- **otp_codes** — append-only OTP issuance log (contact, channel, code_hash, expires, consumed, attempts)
- **personal_access_tokens** — Sanctum bearer tokens
- **kudo_events** — immutable ledger: user_id, source_app, source_kind, amount, meta JSON, synced_to_remote_at
- **life_events** — audit log: kind (lost/regen/purchased/admin/granted), delta, lives_after

### Configuration + presentation
- **configs** — admin-editable site settings (branding, theme, mail, feature flags, default accent). `password` type rows encrypted at rest.
- **voices** — admin-managed reader personas: code, name, accent, character, voice_hints, pitch, rate, letter, placard_color, mood, intro, is_active

## Service layer

| Service | Purpose |
|---|---|
| `Auth/OtpService` | Generate / dispatch / verify 6-digit codes. Dispatches via Mail when SMTP is configured, always logs the dev_code in non-prod |
| `Config/SiteConfig` | 60s-cached reader for the `configs` table. Used by AdminPanelProvider, AppServiceProvider, controllers |
| `Gamification/KudosService` | Award kudos for a correct response (formula = `word.difficulty.rank + 1`, 0 for Diagnostic). Writes `kudo_events`. |
| `Gamification/LivesService` | Deduct/regenerate/purchase lives. Suppressed in Diagnostic. Writes `life_events`. |
| `Gamification/OutboundKudosSync` | Ships unsynced `kudo_events` to a central AllGifted account service (cross-product unification, stubbed until URL is set) |
| `Irt/ThreeParameterLogistic` | The IRT math (probability + Fisher information) |
| `Irt/AbilityEstimator` | EAP estimator over 61-point Gauss quadrature |
| `Irt/ItemSelector` | Maximum Fisher Information item picker, honours status + is_calibrated + show_singlish |
| `Irt/VocabileScore` | θ → Vocabile score → level lookup |
| `Irt/Strategies/` | DiagnosticStrategy, SkillPracticeStrategy, VocabPathStrategy + a Resolver |
| `Irt/TestSessionService` | Orchestrates start / nextQuestion / submitAnswer / finalize, dispatching to the right strategy |

## Cross-product design hooks

AGS Vocab is one app in the AGS family (alongside AGS Math). Several patterns are deliberately built to **share with AGS Math**:

- **`statuses` table** uses the same 5 fixed IDs as AGS Math's `statuses` (Only Me / Restricted / Public / Draft / Archived). FKs and any future sync remain compatible.
- **`kudo_events` ledger** has `source_app` so events from `math` and `vocab` can be merged into a unified user kudos total at a central account service. The `OutboundKudosSync` service is the push side; the central service URL is set via the `ALLGIFTED_ACCOUNT_URL` env var.
- **OTP-only auth** uses the same flow shape AGS Math uses (`/api/auth/request-otp` + `/api/auth/verify-otp`).
- **Branding pulled from `configs`** lets every AGS deployment rebrand without code changes.
- **AGS logo asset** is the same PNG as AGS Math, copied from `c:\allgifted\flutter_demo\assets\logo.png`.

## Where things live in the repo

See the project layout section at the bottom of [README.md](../README.md).
