file_path: /var/www/html/architecture.html content: AELA Architecture

AELA Architecture

AELA · Alea Coutinho · Haileybury College · OCI 34.142.152.26
S171
LAST UPDATED
v2.8 · 2026-04-19

SAC 2 · 13 MAY 2026
VCE Biology — SAC 2 SAC 2: 13 May 2026 · Units 3 & 4 AOS2 · Cellular Respiration & Photosynthesis
📖
SAC 2 Master Guide
Full enriched guide — 102 Q&As across KK4/KK5/KK6. Three gap sections added S203: (1) Limiting Factors graph-reading rules + 3 new Q&As; (2) PS↔CR stage-by-stage comparison tables; (3) Experimental Design Scaffold with Key Science Skills definitions, error types, variable template, and 4 HY 2023 Q&As including the accuracy trap. Grounded in VCAA exams 2002–2024, Haileybury workbooks, ACED trial exams.
sac2_bio_master_guide.html
gen_sac2_guide → enrich_sac2_v2 · Claude Sonnet · 2231 lines · last updated S203
🧪
SAC 2 Practicals Cheat Sheet
Dedicated practicals reference — ET₅₀ method, 9-step infiltration protocol, Exp 1B (light distance), Exp 1C (NaHCO₃ conc.), Exp 2 (yeast + temperature). Variable grids, model exam Q&As with mark schemes, Key Science Skills, error types, 12 examiner traps. Built S202.
sac2_practicals.html
static HTML · no LLM · sourced from HY logbook + SAC info doc + examiner reports
🎯
SAC 2 Exam Practice
Standalone practice exam page — two banks selectable by toggle: Exam Practice (46 questions from real HY 2023/2024, ACED 2025/2026, ACED TT3/TT4 2026, TSSM 2026 papers) and Concept Review (193 Qwen-generated questions). Topic filter, show/hide answers, examiner notes per question. No API dependency — loads JSON statically. Built S203.
sac2_exam_practice.html
static HTML · /data/bio_sac2_practice_questions.json (46) + /data/bio_sac2_questions.json (193)
📝
SAC 2 Quick Guide
Lean rendered version of SAC2_MASTER_GUIDE.md. Sidebar TOC, countdown timer, colour-coded sections. Faster to load than the enriched guide — use for quick lookups.
sac2_guide.html
render_sac2_html.py · Playfair Display · 25KB
🧬
Biology Quiz
Adaptive drill engine — SM-2 spaced repetition + Bayesian Knowledge Tracing + FIRe confidence calibration. 193 questions across KK4 (Enzymes), KK5 (Photosynthesis), KK6 (Respiration), KK7 (Bioethics). Socratic hints on demand. Spaced repetition tracks mastery per question.
quiz.html
FastAPI :42121 · mastery.db · SM-2 + BKT + FIRe
📄
Biology Quiz V2
Extended quiz interface — paper selector, PDF upload, "Teach Me" mode for guided walkthroughs, export to PDF. Wraps the same scoring engine as quiz.html.
quiz2.html
FastAPI :42121 · same backend as quiz.html
📚
Dot-Point Study Guides
74 structured guides across all subjects. Inline viewer renders docx→HTML server-side. Missing files auto-generated by Gemini Flash on first view and cached to disk.
guides.html
FastAPI /api/view-guide · python-docx · Gemini Flash
🕑
Guide Version History
All snapshots of every guide — SAC 2 master, quick guide, UCAT, MMI. One snapshot per rebuild event. Compare two versions side-by-side. Each live guide links here in its footer.
versions.html
/api/guide-versions/{topic} · /opt/aela/data/guide_versions/
UCAT Decision Making · Verbal Reasoning · Quantitative Reasoning · SJT · score /2700
🎯
UCAT Master Guide
Full UCAT guide — 9 sections: overview, VR, DM, QR, SJT, Monash specifics, cross-section strategy, plan, benchmarks. Built by guide_builder.py from Khoj KB (r/ucat, r/premed, 750+ harvested files).
ucat_master_guide.html
guide_builder.py v3 · Gemini Flash via DSPy · KB-grounded
💬
Verbal Reasoning
VR deep-dive — passage strategies, inference vs. retrieval, common trap types, timed practice links. All resource links updated to section-specific URLs (S190).
ucat_vr.html
Static sub-page · deep-linked resources
⚖️
Decision Making
DM deep-dive — highest-weight subtest (35 Qs / 37 min). Logical reasoning, syllogisms, Venn diagrams, probabilistic reasoning, statistical interpretation.
ucat_dm.html
Static sub-page · deep-linked resources
🔢
Quantitative Reasoning
QR deep-dive — data interpretation, mental arithmetic, tables and charts. Medify and YouTube resources linked to section-specific pages.
ucat_qr.html
Static sub-page · deep-linked resources
🤝
Situational Judgement
SJT deep-dive — GMC Good Medical Practice framework, professionalism, prioritising patient safety. Resource links to GMC document and Medify SJT section.
ucat_sjt.html
Static sub-page · deep-linked resources
MMI / Medical Interview Ethics · Role Play · Policy · Personal · 6 sections
🏥
MMI Master Guide
Full MMI guide — 6 sections: overview, ethics, roleplay, policy, personal statement, cross-station. Built from r/medicalschool, r/premed, KharmaMedic content in Khoj KB.
mmi_master_guide.html
guide_builder.py v3 · Gemini Flash via DSPy · KB-grounded
⚕️
MMI Ethics
Ethics station — autonomy, beneficence, non-maleficence, justice. GMC Good Medical Practice linked. Ethical analysis framework with worked examples.
mmi_ethics.html
Static sub-page · GMC + KharmaMedic resources
🎭
MMI Role Play
Role-play station — breaking bad news, difficult conversations, empathy frameworks. KharmaMedic YouTube linked. Script and debrief templates.
mmi_roleplay.html
Static sub-page · KharmaMedic resources
🏛️
MMI Health Policy
Policy station — Australian health system, Medicare, PBS, rural health, mental health policy. ABC Health Report and Croakey deep-linked.
mmi_policy.html
Static sub-page · ABC Health Report · Croakey
🙋
MMI Personal
Personal statement and motivations station — why medicine, strengths, work experience, resilience. KharmaMedic YouTube linked.
mmi_personal.html
Static sub-page · KharmaMedic resources
AI Tutors Khoj RAG · Lexi · OpenWebUI · 38k-entry KB
🤖
Khoj AI Tutors
8 RAG agents grounded in Alea's KB (38k entries): Biology Tutor, Socratic Coach, SAC 2 Prep, Chemistry, Psychology, UCAT, MMI, Exam Strategist. All queries run BM25 keyword search over vault + harvested content.
:42110 (Khoj)
Khoj self-hosted · pgvector · BM25 keyword search
💬
Lexi Tutor
Conversational tutor interface. Session-aware, uses Khoj KB for context. Challenger + Synthesizer agents handle misconception detection and knowledge consolidation.
tutor.html
aela_gateway.py · Gemini Flash · challenger + synthesizer agents
🖥️
OpenWebUI
Direct LLM conversation interface. Qwen3.6-27B via Khoj proxy at :42111. Multi-model support. Use for open-ended study conversations outside the structured quiz/tutor flow.
:3000 (OpenWebUI)
OpenWebUI Docker · Qwen3.6-27B :8080 · Khoj proxy :42111
Operations monitoring · session state · ops tooling
🎓
Parent Dashboard
Read-only progress view for parents — recent quiz scores, mastery trend, subjects covered. No login required.
parent.html
FastAPI :42121 · mastery.db read-only
📡
Harvest Monitor
Live view of PC harvester state — last run timestamps, UCAT/MMI/Reddit file counts, KB ledger entries. Refreshes from harvester state JSON.
harvest_monitor.html
Static HTML · reads ucat_harvest_state.json
📄
Session Manifest
Current session combined.md — live system state, services, KB stats, pending tasks. Generated by gen_manifest.py on session end. Upload to claude.ai for session handoff.
snapshots/combined.md
gen_manifest.py · gitignored · regenerated each session
🏗
Architecture (this page)
Living architecture document — portals, API, decisions, bug log, roadmap, recovery playbooks. Updated each session by Claude Code.
architecture.html
Static HTML · updated each session
nginx Routing
/quiz-api/
Proxies to http://127.0.0.1:42121/ — all quiz API, braun-chat, view-guide, session-plan, sol-tip. Port 42121 is firewalled externally; all browser calls must use this path.
//var/www/html/
Static files — quiz.html, guides.html, study.html, architecture.html, downloads/, studyguides/, snapshots/
/scorer-api/
Proxies to http://127.0.0.1:8000/ — Claude Haiku scorer service (service: aela-scorer). Health data available at /scorer-api/ root: calls_today, calls_remaining, model, key_set.
/guides/guides.html
302 redirect. Present in both HTTP (port 80) and HTTPS (port 443) server blocks. Note: was missing from the port-80 block until S161+ — fixed 2026-04-17.
Port 42110 → Khoj container
AI tutor agents. RAG-powered Biology, Chemistry, Psychology tutors. Passes through directly.
Port 3000 → OpenWebUI container
OpenMAIC direct chat interface. HTTP 200 confirmed live.
FastAPI server at port 42121 (systemd service: aela-quiz). Proxied by nginx at /quiz-api/. SQLite mastery database at /opt/aela/data/mastery.db. Two question banks: /opt/aela/data/bio_sac2_questions.json (193 Qwen-generated concept questions — served via quiz API with spaced repetition) and /opt/aela/data/bio_sac2_practice_questions.json (46 verified practice SAC questions — sourced from HY 2023/2024, ACED 2025/2026, ACED TT3/TT4, TSSM 2026 — served statically by sac2_exam_practice.html).

Models used: Claude Haiku for scoring · Claude Sonnet for deep explanations · Gemini Flash (gemini-flash-latest) for reasoning, sol-tips, Braun chat, guide generation.
Core Learning Endpoints
EndpointMethodDescription
GET /api/nextGETNext question — SM-2 priority sort + BKT weighting + Prolog KB prereq redirect. Prereqs with BKT≥0.85 or mastery_status=="mastered" are skipped.
POST /api/scorePOSTScore answer with Claude Haiku → update BKT (learn/slip/transit/guess model) + SM-2 (interval, repetitions, next_review, consecutive_correct).
GET /api/progressGETMastery stats per topic: mastered/learning/new counts, weakest questions list, activity chart data.
GET /api/question/{id}GETFetch single question by ID — used by guided session queue to load specific questions.
GET /api/session-planGET3-phase plan: (1) review due ≤5, (2) learn new balanced KK4/5/6, (3) strengthen BKT<0.65 ≤4. Returns phases[], all_ids[], summary{}.
GET /api/session/summaryGETRecent attempts with BKT deltas — populates the Session tab. Rows are clickable (drills that question); each row has an OOS toggle button. OOS-flagged rows are dimmed with a red border.
AI-Powered Endpoints
EndpointModelDescription
GET /api/sol-tipGemini FlashAI-personalised Science of Learning banner per question. Receives BKT, mastery_level, reps, streak, days_until_sac. Returns 2–3 sentence tip with study citation. thinkingBudget:0, 180 tokens, AbortController on client.
POST /api/reasonGemini FlashMulti-turn reasoning dialogue to classify missed points: knowledge gap / expression gap / out of scope. Returns message + suggest_resolution. thinkingBudget:0, 1024 tokens.
POST /api/braun-chatGemini FlashThe Braun study coach. Receives {message, history[8], context{mastered,streak,days,topic}}. VCE tutor persona. thinkingBudget:0, 300 tokens.
GET /api/view-guidepython-docx + Gemini FlashThree-layer resolution: (1) exact docx→HTML, (2) fuzzy Biology_N_* match, (3) Gemini generates full VCE study guide (2048 tokens). Caches to studyguides/_generated/.
POST /api/explainClaude HaikuDeep explanation of missed exam points. Called from "Explain my mistake" button.
POST /api/scoreClaude HaikuScores student answers against model answer using a structured 5-point rubric. Returns marks_awarded, feedback, missed_points[].
Utility Endpoints
EndpointMethodDescription
GET /api/xpGETXP total, streak days, level (Novice/Apprentice/Practitioner/Expert/Master at 0/500/1500/3000/5000).
GET /api/daily-goalGETQuestions due today (next_review ≤ today), avg score today (0-5 scale), streak days.
GET /api/sac-prepGETSAC readiness: days remaining to SAC 2 (13 May 2026), mastery %, pace questions per day needed.
POST /api/flag-oosPOSTFlag a question (or all topic peers) as out of scope — removes from queue.
POST /api/unflag-oosPOSTRestore a flagged question or all topic questions to the queue.
GET /api/oos-flagsGETList of currently flagged questions shown in Progress tab.
GET /healthGETHealth check — questions loaded, version string. Used by smoke tests.
Gemini Mandatory Config
⚠ Global Rule — applies to every Gemini call in the codebase
Every Gemini Flash endpoint must include "thinkingConfig": {"thinkingBudget": 0}. Without it, Flash silently consumes 300–400 tokens on internal reasoning before writing a single character of output — leaving almost nothing for the actual response and causing JSON truncation errors.
The Braun is a persistent brain-avatar study companion introduced in S161. It sits at the bottom-right of quiz.html as a floating button, appears as a welcome overlay on first visit, and is the single coordination point across all AELA portals. Powered by Gemini Flash with a VCE-tutor system prompt.
Welcome Overlay — Mode Selection
Trigger
Shown 800ms after page load, on first visit. Suppressed if localStorage has braun_mode set, or braun_skip_date matches today.
🎮 Go it alone (solo mode)
Sets localStorage braun_mode = "solo". Braun stays quiet in the corner. Floating button accessible at any time for on-demand coaching.
🧠 Let The Braun guide me (guided mode)
Sets localStorage braun_mode = "guided". Opens the Braun panel automatically and calls /api/session-plan to populate the guided queue. Queue wired to _guidedQueue[] and _guidedPhases[]. User clicks Next Question to begin — no race with init()'s nextQuestion().
Skip for now
Sets braun_skip_date to today. Overlay won't show again until tomorrow.
Chat Panel Architecture
Floating button (FAB)
Fixed bottom-right. Inline SVG brain with animated neural sparks (<animate> elements) — no image file, no CDN, works offline. Notification badge appears when Braun sends a message while the panel is closed.
Chat panel
340px wide, slides up from FAB. Last 8 turns of _braunHistory[] sent to Gemini. Context injected on first message: mastered_count, total_count, streak_days, days_until_sac, current_topic.
Quick-links row
Single ▶ Start plan button only. Guides/Dashboard/Khoj/Chat buttons were removed — the Braun's purpose is coaching, not portal navigation. Launching the plan immediately (no extra clicks) is the entire job of this row.
Memory limitation (known issue)
Conversation lives in _braunHistory[] JS array. Page refresh loses it. Server-side session storage is a post-SAC 2 roadmap item.
Interactive Guided Session Flow
braunGreet() → topic chip selection
On panel open, fetches /api/session-plan and renders a greeting showing due question count. Then injects topic chip buttons: All topics + one chip per active topic (KK4/KK5/KK6) with due-question counts. No free-text required — pure button-driven.
braunSelectTopic(topicId) → phase chip selection
Locks topic chips (marks selected, dims others). Renders phase chips: All phases · Review due · Learn new · Strengthen weak. Each chip is only active if the session plan has questions in that phase for the chosen topic.
braunSelectPhase(phaseId) → launch
Filters plan.all_ids by topic and phase. Sets _guidedQueue, _guidedPhases, _inGuidedSession = true. Closes panel, calls showView('quiz') then nextGuidedQuestion(). The showView('quiz') call is mandatory here — without it, if the user was on the Plan tab, nothing visible happens.
▶ Start plan button — fast path
braunStartGuided() bypasses topic/phase selection entirely — uses plan.all_ids directly and launches. For users who want to start immediately without customisation.
Session counter on quiz card
#q-session-prog chip shows N / total in the card header during guided sessions (e.g. "3 / 12"). Hidden during free practice. nextGuidedQuestion() updates it; renderQuestion() hides it when _inGuidedSession === false.
Free practice (Plan tab → "Let me choose")
freePractice() — resets _inGuidedSession = false, clears _guidedQueue, switches to quiz view, calls nextQuestion(). Replaced the broken "Let me choose" button that called showView('quiz') without ever fetching a question.
Gemini Config — braun-chat
ParameterValueWhy
modelgemini-flash-latestFast, cheap conversational responses
thinkingBudget0Mandatory — Flash wastes 300-400 tokens on internal thinking without this
maxOutputTokens300Conversational replies stay brief and punchy
temperature0.7Warm and engaged, but consistent
systemInstructionVCE tutor personaKnows all portal URLs, refers to Alea by name, no generic praise, science-grounded
Global Rules — S161
🔴 Rule 1 — Gemini thinkingBudget
Every Gemini Flash call in the codebase must include "thinkingConfig": {"thinkingBudget": 0}. Without it, Flash uses ~380 thinking tokens silently, leaving almost nothing for actual output → JSON truncation, "I hit a snag" errors.
🔴 Rule 2 — GUIDE_API path
Browser JS must call guides via /quiz-api/api/view-guide?path=... (nginx proxy), NOT http://34.142.152.26:42121/... directly. Port 42121 is firewalled externally — direct calls will fail silently from the browser.
🔴 Rule 3 — renderQuestion() hides quiz-empty
renderQuestion() must explicitly call $("quiz-empty").style.display = "none". Any time quiz-card is shown, quiz-empty must be hidden. Failing to do this causes the error state to persist visually below the question card.
🔴 Rule 4 — No auto nextQuestion() from startup code
Do not call nextQuestion() from async startup code that runs concurrently with init(). Wire guided queues and let the user trigger progression. Race conditions between concurrent nextQuestion() calls cause unpredictable state.
🔴 Rule 5 — Verify modal CSS after every guides.html edit
After any edit that replaces a <script> tag or modifies the <style> block in guides.html, verify .guide-modal-overlay.open { display:flex } and .view-btn CSS are still present. This has been accidentally deleted before.
UX & Language Decisions
DECISION
No jargon shown to Alea
"BKT" is never displayed. Instead: "Memory X%". mastery_level maps to New / Learning / Getting there / Almost there / Mastered. Grade labels are Excellent / Strong / Adequate / Partial / Weak / Missed — not 5/5 fractions alone.
DECISION
Context phrase alongside every metric
A number alone means nothing. Every metric has a plain-English sentence: "Nearly locked in — one more solid recall should do it" (BKT 0.65-0.81). XP has level labels. Streak has motivational callouts. Due count has action callouts.
DECISION
Sol-tip is AI-personalised per question, not static
Static quotes ("Be a cognitive weightlifter") are identical every time and lose meaning. Gemini writes a fresh 2-3 sentence tip per question referencing Alea's actual BKT, reps, streak, and days-to-SAC, with a cited study. Falls back to static on failure.
DECISION
The Braun is the integration hub, not a sidebar widget
quiz.html, guides.html, study.html, Khoj, and OpenWebUI are siloed — no shared state or navigation. The Braun is the one component aware of all portals simultaneously and can direct Alea to the right tool for the right task.
DECISION
Brain avatar is inline SVG
No image file, no CDN, no HTTP request. Animated with SVG <animate> elements. Works offline and loads instantly. Zero dependency.
DECISION
AI guide generation cached to disk
First generation ~10s (Gemini Flash). Subsequent views ~25ms (local file read). Cache path: /var/www/html/studyguides/_generated/{stem}.html. A purple AI badge tells Alea the content was generated, not human-written.
DECISION
Prereq redirect skips BKT ≥ 0.85
The Prolog KB prereq redirect runs after priority sort and can override it. A question with BKT ≥ 0.85 or mastery_status == "mastered" is solid enough to be skipped as a prerequisite — it won't be surfaced just because it appears in the prereq graph.
Mastery Language Mapping
Internal valueShown asContext phrase
mastery_level = 0NewYou haven't tried this one yet
mastery_level = 1LearningEarly days — your brain is just starting to build this
mastery_level = 2Getting thereSeen before but still shaky — keep practising
mastery_level = 3Almost thereGetting familiar — recall is improving
mastery_level = 4MasteredStrong recall — this concept is well locked in
BKT < 0.25Memory X%Early days — your brain is just starting to build this
BKT 0.25–0.44Memory X%Seen before but still shaky — keep practising
BKT 0.45–0.64Memory X%Getting familiar — recall is improving
BKT 0.65–0.81Memory X%Nearly locked in — one more solid recall should do it
BKT ≥ 0.82Memory X%Strong recall — this concept is well locked in
Bug Log — S161+ 15 bugs found · 15 fixed
Mastered question (Q20 / Q43) looping — would not advance FIXED
Root cause: Prolog KB prereq redirect (server ~line 737) runs after priority sort and overrides it. Q20/Q43 were listed as prerequisites for other questions, so the redirect kept selecting them even when BKT = 0.94–0.99.
Fix: if prereq_bkt >= 0.85 or prereq_status == "mastered": continue Server: aela_quiz_server.py ~line 742
"Reason with AELA" returns "I hit a snag" every time FIXED
Root cause: Gemini Flash uses ~380 internal thinking tokens by default. With maxOutputTokens=400, only ~20 tokens remained for the actual JSON response → truncated mid-string → JSONDecodeError "Unterminated string at char 12".
Fix: thinkingConfig:{thinkingBudget:0} on ALL Gemini calls maxOutputTokens raised to 1024+ Now a global mandatory rule
"Holding steady to 22%" — grammatical error FIXED
Root cause: memoryDeltaSentence() used "to" for all movements including flat/no-change deltas.
Fix: const prep = (|delta| > 0.02) ? "to" : "at"
"Could not reach quiz API" persisting below question card FIXED
Root cause: renderQuestion() showed quiz-card but never hid quiz-empty. When Braun guided startup called nextQuestion() concurrently with init(), the brief error state was never cleared, leaving the warning visible below the rendered question.
Fix 1: $("quiz-empty").style.display="none" added to top of renderQuestion() Fix 2: Removed auto nextQuestion() from braunStartGuided
mammoth.js "mammoth is not defined" in guide viewer FIXED
Root cause: CDN download via curl returned only 50 bytes — the server has no outbound internet access. The script tag loaded an empty file, so the mammoth global was never defined.
Fix: Replaced with server-side /api/view-guide using python-docx No CDN dependency
Guide viewer HTTP 404 for most of the 74 guides FIXED
Root cause: Only 5 of 74 guide docx files exist on disk (Biology_3–7, dated 2026-04-16). guides.html links use old date 2026-03-31 and slightly different slugs. All other links returned 404.
Fix 1: Fuzzy match by guide number prefix (Biology_N_*) Fix 2: Gemini Flash generates missing guides on demand Cache: studyguides/_generated/
guides.html modal invisible — View button does nothing FIXED
Root cause: During a script-tag replacement edit, the entire modal CSS block was accidentally deleted from the <style> section. The modal overlay existed in the DOM but was always display:none — the .guide-modal-overlay.open { display:flex } rule that makes it visible was gone.
Fix: Re-added full modal CSS block before </style> Critical rule: verify this CSS survives every edit
_ureq not defined in braun_chat endpoint FIXED
Root cause: _ureq was imported locally inside other endpoint functions but not in scope inside braun_chat(). NameError at runtime.
Fix: Added import urllib.request as _ureq_braun inside braun_chat function
"Could not reach quiz API" — no questions presented FIXED
Root cause: solBannerHtml(ml, bkt) was called inside renderQuestion() but never defined. A ReferenceError propagated up through nextQuestion()'s try/catch and was misreported as a network failure. The quiz-card appeared (display was set before the throw) but contained stale default content.
Fix: Added solBannerHtml(ml, bkt) function before renderQuestion() Lesson: ReferenceError in renderQuestion() surfaces as "API unreachable" to the user
"Start plan does nothing" — Braun panel FIXED
Root cause: braunStartGuided() fetched the session plan and set _inGuidedSession = true but never called nextGuidedQuestion(). The guided queue was populated but nothing triggered progression.
Fix: Added braunTogglePanel() + nextGuidedQuestion() after setting queue
"Let me choose does nothing" — Plan tab FIXED
Root cause: Button called showView('quiz') only. If the quiz already had an error state and no question was loaded, switching the view did nothing visible — no question appeared.
Fix: Replaced with freePractice() — resets guided state, calls nextQuestion()
"Start plan keeps cycling, no action" FIXED
Root cause: braunStartGuided() was resetting the chat and calling braunGreet() again on every click — re-displaying topic chips instead of launching the session. User could click Start plan indefinitely with no effect.
Fix: Rewrote braunStartGuided() to skip topic/phase selection and launch immediately
"No action" after topic + phase chip selection FIXED
Root cause: braunSelectPhase() called nextGuidedQuestion() without first calling showView('quiz'). If the user was on the Plan tab when they selected a phase, the quiz view was never activated — nothing appeared.
Fix: Added showView('quiz') inside the setTimeout before nextGuidedQuestion() Rule: Any function that starts a guided session MUST call showView('quiz') first
dashboard.html returning HTTP 403 FIXED
Root cause: File permissions were -rw------- (600) — owner-only read. nginx runs as www-data and could not read the file.
Fix: chmod 644 /var/www/html/dashboard.html All /var/www/html/*.html files should be 644
Open Issues as of S169 · 2026-04-19
Quiz BKT not synced to study.html mastery FIXED S169
GET /api/guide-mastery maps quiz topic BKT (KK4→dp67,68; KK5→dp3,4; KK6→dp5,6,7) to dot-point IDs. study.html overlays BKT-derived mastery level (takes max of SM-2 and BKT). BroadcastChannel('aela-mastery') re-fetches live on every quiz score — no page refresh needed.
The Braun memory is session-only FIXED S169
localStorage serialises last 60 messages on every push and restores on page load. braun_sessions SQLite table + GET/POST /api/braun-history provides true server-side persistence — server history wins if longer than localStorage on startup.
guides.html progress bars are hardcoded FIXED S169
DOMContentLoaded JS counts real .dp-done / .dp-row elements per subject-block and updates counts, fill widths, and tab badges dynamically. Fix is embedded in the content watcher generator so it survives hourly rebuilds.
AI-generated guides are unverified MEDIUM
Gemini Flash writes guides from VCE context but they are not human-reviewed. Purple AI badge warns Alea of the source. All SAC 2 dot-points (dp3-7, dp67-68) now served from gold-standard docx — no AI fallback needed for these.
No HTTPS (external access) FIXED S170
OCI VCN Security List TCP 443 ingress rule added S170. Let's Encrypt cert (34-142-152-26.sslip.io, valid Jul 2026) + nginx HTTPS block already live. curl -I https://34-142-152-26.sslip.io/ → 200 OK.
Before SAC 2 — 13 May 2026
Keep quiz system stable DONE
SM-2 algorithm verified correct. quiz_attempts INSERT verified working. 157/157 tests passing. No regressions from S169 changes.
Verify spaced repetition scheduling DONE
SM-2 logic audited: quality≥3 → interval progression (1→6→×E), quality<3 → reset to 1. Consecutive-correct mastery gating verified. quiz_attempts INSERT confirmed functional via direct DB test.
Ensure all Biology KK4/KK5/KK6 guides are viewable DONE
All 7 SAC 2 dot-points (dp3-7, 67, 68) served from gold-standard docx at /var/www/html/studyguides/Biology/StudyGuides/. HTML cache pre-warmed via /api/view-guide. dp67+68 docx copied from ~/Projects to webroot (6KB AI → 15KB gold-standard).
Add "How am I tracking?" to The Braun DONE
GET /api/braun-tracking pulls live BKT + streak + attempt stats and calls Gemini for a 2-sentence SAC-readiness summary. "📊 How am I tracking?" button added to Braun panel — opens panel, sends user bubble, shows spinner, displays Gemini reply.
Post-SAC 2 Roadmap
Server-side Braun memory DONE S169
braun_sessions SQLite table. GET/POST /api/braun-history. localStorage (60 msgs) + server (80 msgs) dual persistence. Server wins on load if longer.
study.html ↔ quiz.html mastery sync DONE S169
GET /api/guide-mastery maps KK4→dp67,68 / KK5→dp3,4 / KK6→dp5,6,7. BroadcastChannel('aela-mastery') re-fetches live after every quiz score. study.html takes Math.max(sm2_level, bkt_level).
HTTPS — Let's Encrypt certificate DONE S170
OCI VCN TCP 443 ingress opened S170. Cert valid Jul 2026. https://34-142-152-26.sslip.io/ live.
Braun cross-portal awareness DONE S169
Last guide viewed stored in aela_guide_ctx localStorage. Injected into Braun system context on every chat turn via last_guide_viewed.
Model routing — smart tier selection DONE S170
aela_model_router.py wired into gateway, quiz server, study guide maker. LITE for drills/nudges, FLASH for scoring/examiner, FLASH25 for synthesis. See Model Routing section.
Pre-generate all 74 AI study guides
Batch Gemini calls at server startup to fill _generated/ cache. Eliminates the 10s wait for first-time views.
Extend question bank to Chemistry and Psychology
Same SM-2 + BKT adaptive engine. Chemistry SAC and Psychology SAC question sets.
Three independent backup layers run automatically. If the OCI instance goes offline, the fastest path to recovery is a new OCI Always Free VM + a single git clone. Estimated rebuild time: 45–60 minutes for full system, ~10 minutes for quiz+guides only.
Automated Backup Layers 3 layers · all running · last verified 2026-04-18
GitHub — aela-backup repo (hourly · cron 0 * * * *)
Full code push to github.com/clydecoutinho06/aela-backup. Includes: all Python services, HTML pages, nginx config, systemd units, mastery.db, study guides (DOCX), Obsidian vault, and the REBUILD_RUNBOOK. This is the primary recovery source — a single git clone gives you everything needed to rebuild.
script: /opt/aela/scripts/aela_github_backup.sh log: /var/log/aela/github_backup.log retention: full git history
Google Drive — full system snapshot (every 4h · cron 0 */4 * * *)
Complete /opt/aela/ tar.gz uploaded to drive:AELA/backups/daily/ via rclone. Also syncs PDFs, model weights, study guides, Obsidian vault, and all systemd/nginx rebuild assets to Drive separately. 30-day rotation.
script: /opt/aela/scripts/aela_drive_backup.sh log: /var/log/aela/drive_backup.log retention: 30 days rolling
Local DB snapshots — mastery.db + Khoj (every 4h · cron 0 */4 * * *)
mastery.db (SM-2 intervals, BKT scores, all quiz history) copied to /home/ubuntu/backups/db/ with timestamp. Khoj PostgreSQL dumped via pg_dump. 7-day rolling retention. Most recent: checked healthy 2026-04-18.
script: /opt/aela/backup_db.sh log: /var/log/aela/backup.log retention: 7 days rolling
Local tar snapshot — point-in-time stable states
Manual snapshots taken at stable milestones. Stored at /opt/aela/data/snapshots/snapshot_LABEL_DATE.tar.gz. Current stable snapshot: snapshot_S161plus_20260418_0008.tar.gz (196K — covers quiz.html, guides.html, architecture.html, AI notes cache, quiz server, nginx config).
restore: tar -xzf snapshot.tar.gz -C / then: sudo systemctl restart aela-quiz nginx
What's Backed Up vs Re-downloadable
AssetBacked Up?Recovery source
aela_quiz_server.py + all services✓ GitHub + Drivegit clone aela-backup → services/
quiz.html, guides.html, architecture.html✓ GitHub + Drivegit clone → web/html/
74 AI Notes pages (_notes/*.html)✓ GitHub + Drivegit clone → web/html/studyguides/_notes/ OR re-run pre-gen script (~5 min)
mastery.db (quiz history, SM-2 intervals)✓ All 3 layersLocal backups/db/ → most recent .db file
Study guides DOCX (Biology/Chem/Psych)✓ GitHub + Drivegit clone → studyguides/
nginx config + systemd units✓ GitHub + Drivegit clone → web/nginx-sites/ + systemd/
Obsidian vault (581 .md KB files)✓ GitHub + Drivegit clone → obsidian-vault/
Khoj PostgreSQL (8 agent configs)✓ Drive (pg_dump)drive:AELA/backups/db/ → docker restore
Qwen3-8B model weights (4.7GB)Drive onlyHuggingFace download (~20 min on fast link)
Raw PDFs (442MB)Drive onlyWindows laptop copy
API secrets (.env_aela)Never in gitRe-enter manually from password manager
Docker images (Khoj, OpenWebUI)Not storeddocker pull — free public registries (~5 min)
Quiz + Guides Fast Rebuild (~10 min)
Use this if only the quiz/guides need to be restored on a running server, or after a git pull wipes the quiz server endpoints.
1. Restore quiz server
git -C /opt/aela checkout HEAD -- services/aela_quiz_server.py or tar -xzf /opt/aela/data/snapshots/snapshot_S161plus_20260418_0008.tar.gz -C / opt/aela/services/aela_quiz_server.py
2. Restore HTML pages
tar -xzf /opt/aela/data/snapshots/snapshot_S161plus_20260418_0008.tar.gz -C / var/www/html/
3. Restart services
sudo systemctl restart aela-quiz nginx
4. Verify
curl http://127.0.0.1:42121/health → should return {"status":"ok","questions":192}
Full Server Rebuild (~45–60 min)
If the OCI instance is lost entirely. Full runbook lives at deploy/REBUILD_RUNBOOK.md in the aela-backup repo. Summary below.
#StepCommand / ActionTime
1Provision OCI VMVM.Standard.A1.Flex · 4 OCPU · 24GB RAM · Ubuntu 24.04 ARM64 · open ports 22/80/4435 min
2Base packagessudo apt-get install -y python3 python3-pip git nginx rsync sqlite3 curl3 min
3Dockercurl -fsSL https://get.docker.com | sh && sudo usermod -aG docker ubuntu3 min
4Clone backup repogit clone git@github.com:clydecoutinho06/aela-backup.git /tmp/aela-restore2 min
5Restore /opt/aelasudo rsync -a /tmp/aela-restore/{services,agents,scripts,docs,data,obsidian-vault}/ /opt/aela/ && sudo chown -R ubuntu:ubuntu /opt/aela3 min
6Restore websudo rsync -a /tmp/aela-restore/web/html/ /var/www/html/ && sudo chmod 644 /var/www/html/*.html1 min
7Restore nginxsudo rsync -a /tmp/aela-restore/web/nginx-sites/ /etc/nginx/sites-available/ && sudo nginx -t && sudo systemctl reload nginx2 min
8Restore systemdsudo cp /tmp/aela-restore/systemd/aela-*.{service,timer} /etc/systemd/system/ && sudo systemctl daemon-reload1 min
9SecretsCopy deploy/env.template/home/ubuntu/.env_aela, fill in all API keys from password manager5 min
10Python depspip3 install fastapi uvicorn anthropic mammoth python-docx3 min
11Start servicessudo systemctl enable --now aela-quiz aela-gateway aela-scorer aela-dashboard-api aela-mcp-http aela-khoj-proxy aela-queue-worker aela-journal aela-ingest aela-daemon aela-content-watcher aela-llm-server aela-tts aela-healthwatch aela-cloudflare-tunnel aela-watcher && sudo systemctl enable --now aela-snapshot.timer aela-backup.timer2 min
12Khoj (Docker)cd /opt/aela/khoj && docker compose up -d5 min
13Restore crontabcrontab /tmp/aela-restore/deploy/crontab.txt (if present) or re-add manually2 min
14Verifycurl http://127.0.0.1:42121/health + open http://NEW_IP/quiz.html1 min
Post-Rebuild Manual Steps
Update IP in guides.html View buttons
The Google Docs Viewer URL is hardcoded to http://34.142.152.26. On a new server, find and replace in guides.html: sed -i 's/34.142.152.26/NEW_IP/g' /var/www/html/guides.html
rclone config (Drive access)
rclone OAuth tokens are not backed up (security). Re-run rclone config and re-authenticate with Google Drive to restore the 4-hourly Drive backup.
Let's Encrypt TLS certificate
sudo certbot --nginx -d NEW_DOMAIN if using sslip.io. Current cert renews automatically via cron (0 3 * * * certbot renew).
Qwen3-8B model weights (LLM server)
4.7GB download from HuggingFace. Only needed for the local LLM server — quiz, guides, and Braun all use cloud APIs (Gemini/Claude) and work immediately without this.
Re-generate AI notes if not restored from backup
Run the pre-generation script to rebuild all 74 cached notes pages: python3 /opt/aela/scripts/pregen_notes.py (~5 min, hits Gemini API).
S171
Lexi ITS · Exam Sim · Error Taxonomy · Parent Dashboard
2026-04-19
CURRENT
ITS Interface Model completed (Lexi): Persistent companion widget across all 5 pages (32px quiz / 56px FAB / 84px tutor). 5 expressions via SVG path-swap. Blink animation. Quiz reactions via MutationObserver. lexi.js self-contained IIFE.

/tutor home page: Daily brief from live BKT data. SAC countdown. Session planner card (PLAN→IN_PROGRESS→DEBRIEF state machine via GET /api/tutor/session). Confidence calibration card. Today's Focus driven by weakest BKT topic.

Quiz engine additions: Confidence calibration (1–5 pre-answer, stored in quiz_attempts.confidence_before). Show Answer escape hatch (POST /api/view-answer, SM-2 reset, 2-day interval, Kapur 2016). Sub-80% score → forced 1-day interval + growth-mindset framing. Error taxonomy (misconception/forgotten/never_learned/correct) returned by scorer and stored in quiz_attempts.error_type.

Socratic hints: GET /api/hints/{id} — Claude Haiku generates 3-tier hints on first request (~2s), cached in question_hints table forever. Tier 1: concept nudge → Tier 2: key term → Tier 3: answer structure. Never reveals the answer.

Exam simulation (/exam): Topic + mark budget selector. Countdown timer (1.2 min/mark). No hints, no Show Answer, no confidence rating. Background parallel scoring (Promise.all). Results with SAC-equivalent grade A–E and per-question model answers.

Parent dashboard (/parent): GET /api/parent/summary — 7-day activity chart, topic mastery bars (learning/consolidating/mastered), error taxonomy breakdown, recent exam results. Auto-refreshes every 60s. Linked from tutor.html nav.

URL topic pre-selection: /quiz?topic=KK4 pre-filters sidebar and loads first question from that topic on boot.

Melbourne timezone: GET /api/tutor/greet time_of_day uses AEST (UTC+10) not server UTC.

Cleanup: quiz_errors.jsonl consolidated to /opt/aela/data/. /exam and /parent nginx routes added. Exam nav link in quiz.html and tutor.html.

DB changes: quiz_attempts + confidence_before, viewed_answer, exam_mode, error_type. New question_hints table.

Files changed: aela_quiz_server.py, aela_sac2_agent.py, web/quiz.html, web/tutor.html, web/exam.html (new), web/parent.html (new), web/lexi.js (new), nginx-default.conf. Commits: fe74e01, 1dc596a, 3ef4ab6.
S170
HTTPS Live · Model Routing Wired
2026-04-19
PREVIOUS
HTTPS activated: OCI VCN Security List TCP 443 ingress rule added. https://34-142-152-26.sslip.io/ returns 200 OK. Cert (Let's Encrypt, sslip.io wildcard) valid Jul 2026. HTTP→HTTPS redirect active on port 80.

Model routing wired + hardened (aela_model_router.py):
All services route through the central router. Gemini alias strategy documented — -latest rolling aliases used for LITE/FLASH (Google-maintained, stable); gemini-2.5-flash pinned for FLASH25 (implicit caching requires specific generation). To update a model, change one constant in aela_model_router.py.
Local Qwen3.5-9B wired via local_complete() with enable_thinking=False (required — thinking tokens otherwise consume all n-predict budget returning empty string). Benchmarked: LITE=1.5s, FLASH=2.3s, LOCAL=5s — LOCAL reserved for batch/background and API fallback.
  • LITE (gemini-flash-lite-latest, ~1.5s): drill PASS/FAIL, nudges, status, braun-tracking 2-sentence summary
  • FLASH (gemini-flash-latest, ~2.3s): 6-class error scoring, tutor remediation, Braun dialogue, examiner/challenger
  • FLASH25 (gemini-2.5-flash, ~4s): study guide synthesis — implicit prompt caching on repeated dot-point synthesis
  • Claude Haiku (claude-haiku-4-5): quiz answer scoring — structured JSON rubric, VCAA-precise marks/feedback
  • Claude Sonnet (claude-sonnet-4-6): deep mistake explanations — Mayer cognitive theory, 4-5 sentence personalised analysis
  • LOCAL Qwen3.5-9B (port 8080, $0): batch tasks, /deepsolve, /deepq, API fallback via local_complete()
Files changed: aela_model_router.py (rewrite), aela_gateway.py, aela_quiz_server.py, study_guide_maker.py. 157/157 tests pass. Commits: 406a7aa, 72ac1a2.
S169
Open Issues Cleared · Pre-SAC Roadmap Complete · Braun Upgrades
2026-04-19
PREV
Open Issues (all 3 HIGH/MEDIUM resolved):
  • Quiz BKT → study.html sync: GET /api/guide-mastery maps quiz topics to SAC 2 dot-point IDs. study.html fetches this in parallel with scorer data and takes Math.max(sm2_level, bkt_level). BroadcastChannel('aela-mastery') re-fetches live after every quiz score.
  • Braun memory: localStorage serialises last 60 messages on every push. braun_sessions SQLite table + GET/POST /api/braun-history provides server-side persistence — server history wins on load if longer.
  • guides.html progress bars: DOMContentLoaded JS counts real .dp-done rows dynamically. Fix embedded in content watcher generator — survives hourly rebuilds.
Pre-SAC roadmap complete:
  • SM-2 algorithm audited — correct. quiz_attempts INSERT verified via direct DB test.
  • All 7 SAC 2 guides (dp3-7, 67, 68) verified viewable from gold-standard docx. dp67+68 docx copied to webroot (AI fallback replaced with full 15KB guide).
  • GET /api/braun-tracking + "📊 How am I tracking?" button: pulls live BKT+streak+attempt stats, calls Gemini, returns 2-sentence SAC-readiness summary.
Post-SAC roadmap progress: server-side Braun memory (SQLite), BroadcastChannel real-time mastery sync, Braun cross-portal awareness (guide context in localStorage → injected into Braun chat), 107 study guides generated (exceeds 74-guide target).

Smoke test results (S169 stable): 33/33 API checks pass. 157/157 pytest tests pass. All 10 services + 5 timers active. All 7 SAC 2 view-guide endpoints return full HTML (14–23KB). quiz_attempts INSERT verified live. SM-2 scheduling verified (no zero-interval regressions). guides.html dynamic JS confirmed in content watcher generator (survives hourly rebuilds).
S168
Vision Fallback for Unreadable PDFs · Smarter Table Quality Checking
2026-04-18
The problem this solves: AELA ingests PDFs — past exams, textbooks, study resources — and converts them to Markdown so Khoj can index and search them. Some PDFs are scanned images with no text layer. Others are exported with broken fonts. Before S168, if Docling's text extraction failed and Tesseract OCR also failed, AELA logged an error and the document was silently lost — never indexed, never searchable, never available for study guides or quiz generation. For a VCE student with limited study resources, a lost document is a real gap.

What changed — the third attempt: When both text extraction and OCR fail, AELA now has a third option. It renders each page of the PDF as a high-resolution image, slices wide or tall pages into smaller tiles so nothing gets cropped or distorted, then sends those images to Gemini with a precise structured prompt. Gemini reads the page visually — the same way a human would — and outputs clean, structured Markdown. The result is written to the knowledge base exactly like any other converted document. No document is silently lost anymore.

What the structured prompt enforces: The Gemini prompt isn't just "describe this page." It instructs the model to output math as proper LaTeX ($...$ inline, $$...$$ for equations), tables as HTML with merged cells preserved, and figures with their position on the page. This matters because a Biology diagram described as "a circle with arrows" is useless — but a properly captioned figure with spatial context is searchable and referenceable.

What changed — table quality checking: Previously, AELA checked tables by comparing row counts and loosely matching cell text. A table could pass the quality check even if columns had silently collapsed — for example, a 3-column enzyme kinetics table being converted to 2 columns, losing a whole data dimension. The upgraded checker now compares the full shape of a table: rows, columns, and content together. A column-count regression that the old scorer would miss is now caught and flagged before the document enters the knowledge base.

Why this matters for study: Khoj's answer quality is only as good as what's in the knowledge base. A failed ingest means Khoj can't answer questions from that source. A silently degraded table means Khoj gives incomplete answers about enzyme rates, genetic crosses, or experimental data — without either system knowing something is wrong. S168 closes both gaps: more documents get in, and the ones that do get in are structurally verified.

Tests: 54 new tests — 103 → 157 passing. Every new function is tested in isolation including all edge cases (empty tables, missing API key, network failures, malformed HTML, page tile counts).
S167
VaultResolver · Resolver Audit · Trigger Evals · S167 Cleanup
2026-04-18
VaultResolver (vault_resolver.py): single source of truth for where ingested content lands in the Obsidian vault. resolve(subject, content_type) → absolute vault path; resolve_auto(path) auto-detects subject and content type from filename. 17 content types mapped across Biology/Psychology/Chemistry. Subject aliases (bio/psych/chem). Inbox fallback for unknowns. Full audit trail to ingest_resolve.jsonl (gbrain ingest_log pattern).

RESOLVER.md (docs/): 4-part routing document: (1) Telegram input → agent/endpoint routing table (28 trigger patterns). (2) Vault filing rules and misfiling prevention. (3) Full service registry — FastAPI, Khoj, systemd, timers. (4) Context injection preamble for Khoj agents.

check_resolvable.py: weekly service graph audit across 5 check categories — FastAPI endpoints, Khoj KB context gate (≥100 chars), systemd active/timer/parked/oneshot, vault directory structure, deprecated file notices. Outputs to resolvable_report.jsonl. --fix-hints for remediation.

run_trigger_evals.py + trigger_evals.json: 25-item routing smoke test (critical/high/medium/low priority). Tests all quiz API endpoints, Khoj agents, gateway routing. Pass threshold: 80%. Results to trigger_eval_results.jsonl.

snapshot_resolver_hook.sh: wired into snapshot.sh — runs resolver audit + trigger evals (critical only) + vault spot-check + RESOLVER.md staleness check on every snapshot.

Wiring: study_guide_maker.py routes via VaultResolver instead of hardcoded path. aela_convert.py logs every conversion to VaultResolver audit trail. 30-test suite for vault_resolver added to CI.
S162–S166
Session Transfer System · Manifest Consolidation · Home Cleanup
2026-04-18
Session context transfer system: End-to-end Claude Code ↔ Claude.ai context sharing. Counter file at /opt/aela/data/aela_session_number.txt replaces fragile dir-scanning for session numbering. gen_manifest.py v3 generates 482-line manifest from live system state — services, DB counts, feature verification, MA gap status, model routing, learning science principles, all locked decisions, all gotchas.

Windows folder watcher: aela_watcher.ps1 (FileSystemWatcher) monitors AELA-Downloads\ on laptop. Auto-SCPs .md manifests and .zip payloads to OCI. Server-side process_upload.sh handles routing: .md → /var/www/html/snapshots/combined.md + Telegram notify; .zip → /tmp/aela_staged/ + Telegram notify. Started via Windows Startup folder shortcut (no admin required).

Manifest consolidation: 54 server manifests + 121 laptop session files absorbed. All missing context (MA gaps, model routing, ContextGuard, temporal decay, typed failures, OCR bugs) permanently embedded in gen_manifest.py. CLAUDE.md updated: first action every session is to read combined.md as ground truth.

Home root cleanup: 150+ files → 8 items. Pre-cleanup snapshot (8.1GB) at /opt/aela/data/snapshots/pre_cleanup_20260418_020252.tar.gz. All stale/temp files archived to ~/.archive/. All services verified active after cleanup.

MA Gap 23 — Spacing optimisation: Topic difficulty spacing matrix implemented in sm2_update(). Computes per-topic avg score from quiz_attempts (min 5 attempts to activate), maps to a [0.75, 1.25] interval multiplier applied on the SM-2 success path only. Hard topics → shorter intervals; easy topics → longer. Dormant until quiz sessions accumulate. MA gap count: 46/51.

PDF quality pipeline (ParseBench + LiteParse patterns): New aela_parse_quality.py module combining two open-source repos.
ParseBench patterns: BaselineRule — empty/near-empty/repetitive detection. GriTS table checker — markdown tables vs Docling native export; flags fidelity <0.75. Layout bbox sidecar.layout.json per PDF with label/page/bbox/text for every item. LaTeX normalization — strips LaTeX + fenced code before quality checks.
LiteParse patterns: GarbledTextRule — detects PUA unicode (broken font CMap) and box-drawing chars that fool BaselineRule into thinking text was extracted cleanly. fitz pre-check — PyMuPDF counts chars per page in milliseconds before loading Docling; if sparse pages detected, skips the wasted do_ocr=False pass and goes straight to Tesseract. tesseract_page_confidence — pytesseract per-page confidence (0–1) stored in layout sidecar; pages <0.3 logged as warnings. All quality metrics in convert_decisions.jsonl.
S161+
Quiz UX Complete · Smoke Tested · Stats Reset
2026-04-17
Interactive Braun guided session: Topic chip selection (All / KK4 / KK5 / KK6 with due counts) → phase chip selection (All / Review / Learn / Strengthen) → launches filtered guided queue. braunStartGuided() fast-path bypasses selection and launches full plan immediately.

Session counter: q-session-prog chip ("3 / 12") on quiz card header during guided sessions. Hidden in free practice. Updated by nextGuidedQuestion(), cleared by renderQuestion().

Session tab enhancements: Clickable rows — click any session history row to drill that exact question. OOS flag button per row — toggles Out of Scope, dims row with red border, updates mastery.db. Topic label shown on each row.

Sidebar topic labels: Now show "X% · N due · M total q" — question count added alongside pct and due.

Stats reset: mastery.db wiped clean (quiz_attempts, quiz_question_mastery, xp_events, review_sessions, study_streak). Alea starts fresh with a clean slate from 2026-04-18.

Infrastructure fixes: dashboard.html chmod 644 (was 600, nginx couldn't read it). /guides redirect added to HTTP (port 80) server block — was only in HTTPS block.

Smoke tested: 83-check quiz app smoke test (all passing) + 52-check ecosystem smoke test across all 14 systemd services. All services active/running confirmed.

Bugs fixed: solBannerHtml undefined (masked as API error) · braunStartGuided() not calling nextGuidedQuestion() · freePractice() replacing broken "Let me choose" · braunStartGuided() cycling by re-calling braunGreet() · missing showView('quiz') in braunSelectPhase() · dashboard.html 403 · /guides 404 on HTTP.
S161
Web Portal Layer Complete
2026-04-17
The Braun: Brain-avatar orchestration agent. Welcome overlay with solo/guided mode choice. Floating FAB with animated inline SVG. Gemini Flash chat panel with mastery context injection. Guided session queue wired to /api/session-plan.

Guide viewer rebuilt: mammoth.js CDN abandoned (server has no outbound internet). Server-side python-docx conversion. Fuzzy file matching (Biology_N_* prefix). Gemini Flash AI generation for all missing guides (~10s first visit, 25ms cached).

Session plan orchestrator: /api/session-plan — 3-phase plan (review due / learn new / strengthen weak). Plan tab in quiz UI. Guided session mode with phase transition banners.

Science of Learning banner: AI-personalised per question via Gemini Flash. AbortController cancels in-flight requests on question change. Static fallback pool with study citations.

Jargon removed: BKT → Memory X%. All mastery labels plain English. Context phrases on every metric. Sidebar metrics with callout phrases.

Bugs fixed: Mastered-question looping (Prolog prereq BKT≥0.85 skip) · Gemini truncation (thinkingBudget:0 now mandatory globally) · quiz-empty persistence (renderQuestion() hides it) · modal CSS deletion · _ureq scope error · guide 404s (fuzzy match + AI gen).

Architecture document: Rebuilt as architecture.html in AELA design system (this page). Replaces the messy docx approach.
S140–S160
Quiz Engine & Platform Foundation
2026-03 → 2026-04
Built the core quiz engine: SM-2 spaced repetition, BKT Bayesian Knowledge Tracing, Prolog KB prerequisite graph, interleaving, mastery tracking. Deployed 192 Biology SAC 2 questions across KK4/KK5/KK6. guides.html with 74 study guides. Khoj agent fleet (8 agents). Prolog KB layer. OOS flagging. XP/streak/level system. Claude Haiku scoring. Session summary view. Progress dashboard.
S50–S139
Infrastructure, Agents & Knowledge Base
2025 → 2026-03
OCI instance provisioned. Discord + Telegram bots. Agent fleet architecture (LaneQueue, ContextGuard, SessionStore). Khoj knowledge base with RAG. Content pipeline for guide generation. SVG diagram system. OpenMAIC/OpenWebUI setup. Crontab automation. Backup system (Google Drive + GitHub). Cost architecture.
AELA Architecture · Updated S168 · 2026-04-18 · v2.5 · Download original Blueprint.docx