Overview & Architecture
Version: v1.7 | Last Updated: 2026-04-18
1. Scope
This wiki defines the complete Pickatale learning platform β every service, data model, API contract, compliance rule, and build gate. It is the single source of truth for Claude Code agents.
Rule: If it is not in this wiki, it does not exist. Do not infer, assume, or default.
2. Product Overview
Source: (D) Sig Dug | Status: Confirmed
Pickatale is a children's reading platform (ages 4β11) built on an invisible adaptive learning loop:
- Curriculum Mapper (CM) defines what a child should learn per territory/year
- Reader App delivers books the child wants to read
- Telemetry captures behavioral signals (page times, word taps, quiz scores)
- Learner Bot detects gaps, generates reports, recommends next content
- Adaptive Content Engine re-levels any story to a child's exact FK grade
- Teacher Portal shows curriculum progress, flags struggling readers
- Parent Portal delivers warm digests: "Sofia had a great reading day!"
The child just reads stories they love. The platform handles the rest invisibly.
Platform Pillars
| Pillar | Implementation |
|---|---|
| Invisible learning | Reinforcement woven into stories the child already wants to read |
| Per-child AI agent | Learner Bot with RAG memory per learner |
| Cross-nation analytics | School β District β Nation β Global roll-ups |
| All content is adaptive | Static content is an intermediate state only |
| Modular APIs | Every module can be sold/licensed independently |
Key URLs
| Service | URL | Port |
|---|---|---|
| Reader App | app.readingtester.com | 3125 |
| Teacher Portal | teacher.readingtester.com | 3116 |
| Parent Portal | parents.readingtester.com | 3118 |
| Account Center | account.readingtester.com | 3126 |
| Curriculum Mapper | cm.readingtester.com | 3117 |
| Adaptive Engine | adapt.readingtester.com | 3119 |
| Learner Bot | (internal only) | 3120 |
| Telemetry | (internal only) | 3110 |
| LRS | (internal only) | 3111 |
| Content Service | (internal only) | 3112 |
| Analytics | (internal only) | 3114 |
| Wiki | wiki.readingtester.com | β |
3. System Architecture Map
Source: Code audit 2026-04-18 | Status: Confirmed
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CADDY (HTTPS) β
β reverse proxy for all services β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β
ββββββββββββββββΌβββββββββββββββββββ
β β β
[Reader App] [Teacher Portal] [Account Center]
port 3125 port 3116 port 3126
React+tRPC Next.js+Express Node+Express
β β β
β [Parent Portal] [Subscriptions]
β port 3118 Stripe Webhooks
β
[Telemetry]βββββββ[Learner Bot]βββββββ[Teacher Portal]
port 3110 port 3120 (enrichment)
β β
β β
[LRS] [Adaptive Engine]
port 3111 port 3119
(FK leveling)
β
[Content Service]
port 3112
All services share: MySQL 8 (shared Docker instance, internal access only)
All services read identity: Account Center session API
All child identity: UUID learner_id from Teacher Portal students table
Inter-Service Communication Rule (Non-Negotiable)
- β
Services communicate via REST API with
X-Internal-Keyheader - β Services NEVER connect to each other's database directly
- Build the endpoint in Service B first, then wire Service A to call it
4. State Definitions
Source: Code audit | Status: Confirmed
User States (users.state)
| State | Meaning |
|---|---|
| pending_verification | Registered, email not yet verified |
| active | Normal operating state |
| suspended | Admin-suspended, cannot login |
| archived | Soft-deleted, all data retained |
Student States (students.state)
| State | Meaning |
|---|---|
| created | Account created, PIN assigned, not yet activated |
| Under-13 β awaiting parental consent | |
| active | Has logged in at least once |
| inactive | No sessions in 30+ days |
| archived | Soft-deleted or consent revoked |
Subscription States
| State | Meaning |
|---|---|
| trialing | 14-day free trial, full features |
| active | Paid, full features |
| past_due | Payment failed, grace period (7 days), full features |
| expired | Grace period over, locked to free tier |
| cancelled | Voluntarily cancelled, access until period end |
Reading Session States
| State | Meaning |
|---|---|
| open | Book opened, reading in progress |
| completed | session_ended received with all pages |
| abandoned | Closed without completing OR force-abandoned by cron after 24h |
5. Build Order
Source: (D) Sig Dug | Status: Confirmed
Phase 1 β Identity & Auth (Account Center)
userstable + registration + email verification- Login + session (uc_session cookie)
- Forgot password + reset
- School creation
- Membership (teacher β school)
Phase 2 β Roster & Reading
- Class creation (Teacher Portal)
- Student add/bulk-import + PIN generation
- Child login (Reader App)
- Book open β reading session β telemetry
- Session end β miles/tokens update
Phase 3 β Intelligence Loop
- Placement test
- Quiz β Learner Bot feedback
- Telemetry β Bot (vocab gaps + session summary)
- Nightly bot run β teacher report
- Parent Portal digest
Phase 4 β Billing & Compliance
- Stripe integration + subscription lifecycle
- Entitlement enforcement across all services
- GDPR export + deletion
- Admin APIs + impersonation
6. Open Questions
| # | Question | Owner | Status |
|---|---|---|---|
| OQ-1 | Azure Neural TTS β awaiting Sig portal.azure.com signup | Sig | Pending |
| OQ-2 | SpeechAce phoneme API (fluency) β awaiting Borja | Borja | Pending |
| OQ-3 | Staging environment β not yet built | Jixian | MUST BUILD |
| OQ-4 | CI/CD pipeline β not yet built | Jixian | Phase 4 |
| OQ-5 | Night theme in Aa panel β keep or remove? | Sig | Pending decision |
| OQ-6 | Class name display bug (Year3Blue β "Year 3 Red") β root cause unknown | Jixian | Active |
End-to-End System Flow (Authoritative)
This is the single canonical flow from school onboarding to reporting. Claude Code must be able to implement every step from this section alone. For detailed contracts, follow the page references in each step.
Phase 1: School Onboarding
- school_admin registers at teacher.readingtester.com β POST /api/auth/register (Account Center) β creates
usersrow (role=school_admin, state=pending_verification) +schoolsrow β sends EMAIL_VERIFICATION email - school_admin verifies email β GET /api/auth/verify?token=... β users.state=active β DPA acceptance screen shown
- school_admin accepts DPA β POST /api/schools/dpa β INSERT
school_dpa_agreements(school_id, user_id, accepted_at, ip_address) β redirect to dashboard - school_admin invites teacher β POST /api/schools/invite (email, role=teacher) β INSERT
invites(token, email, school_id, role, expires_at=48h) β sends TEACHER_INVITE email - teacher accepts invite β clicks link β POST /api/auth/accept-invite (token, password) β INSERT
users(role=teacher, school_id) β INSERTsubscriptions(state=trialing, trial_ends_at=now()+14d) β redirect to teacher portal
Authoritative detail: Page 11 (Sign-Up Flows), Page 03 (Identity)
Phase 2: Class & Student Setup
- teacher creates class β POST /api/classes (name, year_level, school_id) β INSERT
classes(id, teacher_id, school_id, license_id from teacher_licenses) β return class_id - teacher imports students (CSV) β POST /api/students/import (class_id, rows[]) β for each row: INSERT
students(username=auto-generated, pin=4-digit-random, school_id, teacher_id, class_id, learner_id=UUID) β INSERTclass_memberships(student_id, class_id) β return login cards (username + PIN + QR) - teacher prints login cards β GET /api/classes/:id/login-cards β renders card HTML with username, 4-digit PIN, QR code linking to app.readingtester.com?user=username. PIN shown once only β not retrievable after dismissal.
Authoritative detail: Page 05 (Roster), Page 11 (Sign-Up Flows Flow D)
Phase 3: Child First Login
- child enters username + PIN at app.readingtester.com β POST /api/auth/child-login (username, pin) β verify
studentstable β INSERTdevice_sessions(learner_id, device_id, policy=shared, created_at) β return session JWT - placement test shown β child reads 3 calibration passages β POST /api/placement (session_id, responses[]) β calculate FK grade β UPDATE
students(placement_results JSON, reading_level DECIMAL) β child lands on library - library renders β GET /api/recommendations?learner_id=... β Learner Bot returns personalised book list β fallback: Content Service catalog filtered by child's FK level
Authoritative detail: Page 11 (Sign-Up Flows Flow E), Page 06 (Reading & Telemetry Section 7.1)
Phase 4: Reading Session
- child opens book β Reader App β POST /api/telemetry/events (event_type=book_opened, learner_id, session_id=new UUID, book_id, client_ts) β Telemetry stores in
eventstable (deduplicated by event_id) - child reads pages β each page turn: POST /api/telemetry/events (event_type=page_turned, page_number, time_on_page_ms) β stored offline if no connection, replayed via Service Worker on reconnect
- child taps word β POST /api/telemetry/events (event_type=word_tapped, word) β definition popup shown from Content Service
- child completes book β POST /api/sessions/end (session_id, client_ts) β Reader App calculates miles (words_read / 100) β PATCH /api/progress/:learner_id (books_read+1, total_miles+N) β Telemetry fires two parallel calls (5s timeout each): POST /learner-bot/api/v1/telemetry/vocab-taps AND POST /learner-bot/api/v1/telemetry/session-summary
- Service Worker guarantees delivery: events POST offline β IndexedDB queue β auto-replay on reconnect via BackgroundSync. Server deduplicates on event_id (UUID, 7-day retention).
Authoritative detail: Page 06 (Reading & Telemetry), Section 6 (Events)
Phase 5: Learning Loop (Nightly)
- 04:00 UTC β Learner Bot cron fires β for each active learner: fetch telemetry (90-day window), load RAG memories (vocab_gaps, reading_patterns, curriculum_state), run gap analysis
- bot generates teacher report β POST /api/v1/bot/:learner_id/report β stores in
bot_reportsβ Teacher Portal dashboard updated - bot detects struggling reader β avg quiz score < 65% on 2+ quizzes β INSERT
intervention_queue(learner_id, reason, teacher_id) β Teacher Portal attention list updated - bot recommends next book β UPDATE
bot_memories(type=recommendation, content_id, reason) β Reader App next-session library shows "Picked for You" card - on failure: INSERT
failed_runs(learner_id, error, attempted_at) β 05:00 UTC re-run cron retries failed learners. 3 retries with exponential backoff (1s, 4s, 16s). After 3 failures β alert_log.
Authoritative detail: Page 06 Section 7 (Telemetry β Bot), Learner Bot service (port 3120)
Phase 6: Entitlement Enforcement
- every feature-gated API call β service calls
checkEntitlement({user_id, school_id, student_id})β checks in-process cache (60s TTL) β if miss: GET /api/billing/entitlement (Account Center, X-Internal-Key, 2s timeout) - entitlement resolution order: (1)
entitlement_grantstable (gifted/admin grants) β if found, tier=full. (2)subscriptionstable: trialing+valid β full; active+valid β full; past_due+grace β full; else β free. (3) default β free. - timeout fallback: if Account Center >2s β tier=free (fail-safe). INSERT
audit_log(action=entitlement_check_failed). Child can still read 50 free books. Learner Bot paused. Reports not generated. - subscription expires: hourly cron β subscriptions WHERE state=past_due AND grace_ends_at < now() β UPDATE state=expired β UPDATE students.entitlement_tier=free β INSERT audit_log β send SCHOOL_SUBSCRIPTION_EXPIRED email
- parent linked (optional): teacher invites parent β parent accepts β INSERT
parent_links(parent_id, student_id, school_id, teacher_id, approved_at) β parent can view progress at parents.readingtester.com (read-only). Parent NEVER creates a new child record.
Authoritative detail: Page 04 Section 29.3β29.7 (Billing)
End of authoritative E2E flow. Every step above maps to a specific page and section. If a build question arises that is not answered by following these steps to their referenced sections, it is a spec gap β do not invent behavior.