Adaptive learning desk · for coaching centres
Abhyas is where a learner at a coaching centre in Vijayawada reads tonight's lesson, takes a quiz that adapts to how she did, reviews the cards that are due, and follows a plan that bends when life gets busy. Her teacher opens one desk and sees who is slipping before the exam does.
Leave your email once and we send one access PIN and one temporary login that open every OC Apps demo, this one included. The demo runs on synthetic data and reseeds itself; nothing you enter is kept.
| Class 10 Mathematics | 63% | |
| Spoken English | 50% | |
| Tally & GST | 17% |
A coaching centre with sixty students and three teachers runs on a notebook, a WhatsApp group and one teacher's memory. Three things go wrong every term.
Attendance is in the notebook, marks are on loose sheets, and the quiet student who stopped practising three weeks ago is discovered at the exam.
A test on Friday is gone by Monday. There is no schedule that brings a fact back the day before it fades, so revision means re-reading the whole chapter.
A plan written on day one never bends. One busy week and the learner is behind on paper, gives up on the plan, and the plan quietly stops meaning anything.
A learner's desk and a teacher's desk, both server-rendered and fast, with a tutor drawer that only uses what is in the lesson.
A searchable catalogue by category and level; enrol from the course page; ordered lessons with teaching text, an optional video, a progress bar and the next lesson always one click away. Completing a lesson earns XP and keeps the streak.
Five questions built from the lesson itself at four levels. Every wrong answer comes with the sentence that explains it. Mastery is your best score; the next level rises when you pass 80% and eases when you fall under 50%.
Key terms become cards. One card at a time, flip, rate yourself 0–5, and the SM-2 spaced-repetition rule decides when it returns: tomorrow, in six days, then further out each time.
Pick a pace (relaxed, steady, intense) and a finish date; the remaining lessons are spread evenly in order. Change the pace and the plan rebuilds from today without losing anything you have done.
Per course: enrolled, mean completion, mean mastery, work waiting to be graded. A radar flags learners inactive for a week, below 50% mastery, or behind their plan, and links to each one.
Teachers set a brief with a due date; learners submit a written answer; late is flagged. The teacher grades with a score and one line of feedback, helped by a rubric that suggests a mark from coverage, length, structure and specifics.
Ask a question about the open lesson and get an answer quoted from it, or an honest “not covered here”. Ask what to do next and get one step with a reason. Works without AI; with a model key it helps more, and the badge says which.
One FastAPI process, one SQLite file, server-rendered pages with a little JavaScript for the flashcard flip and the tutor drawer. The same code serves the local service and the hosted twin.
Every capability we set out to build, with its status and the test that proved it. This table is generated from the internal build tracker, so it cannot claim more than the tracker shows.
36 of 37 tracked capabilities built and verified · 1 partial · 0 on the roadmap. Generated from the build tracker; each row names the test that proved it.
| Area | Capability | Status | Proof (acceptance test) |
|---|---|---|---|
| Foundations | Schema: courses, lessons, quizzes, attempts, flashcards, enrolments, plans, assignments, submissions, certificates, learners, events | Built | db.init() on an empty file creates exactly those 12 tables plus schema_version; running it twice is a no-op (row counts unchanged) |
| Foundations | Synthetic seed: a coaching centre in Vijayawada with courses, lessons, learners and history | Built | seeded DB holds >=5 courses, >=24 lessons with real teaching text (>=400 chars each), >=10 learners with enrolments, quiz attempts and flashcard reviews spread over the last 30 days; reseed on an empty DB is deterministic (same counts) |
| Foundations | App shell: FastAPI boot, /healthz, base layout with design tokens | Built | curl /healthz returns 200 JSON with ok=true, db path, course and learner counts and the ai provider; GET / signed in returns 200 HTML with the nav and no placeholder text |
| Foundations | Test harness | Built | pytest -q runs >=1 test against a temp DB via TestClient and passes |
| Courses & lessons | Course catalogue with search, category and level filters | Built | GET /courses?q=<seeded title fragment> lists only matching courses; ?category=<cat>&level=<lvl> combine; a filter that excludes everything renders the 'no courses match' state; headless: one card per seeded course, 0 console errors |
| Courses & lessons | Course page: ordered lessons, enrol, progress bar, next lesson | Built | POST /courses/{id}/enrol creates one enrolment (a second POST is a no-op); the page shows lessons in order_index order, a completion percentage equal to done/total and links the first incomplete lesson as 'Next' |
| Courses & lessons | Lesson reader: teaching text, video embed, mark complete → progress, XP and streak | Built | GET /lessons/{id} renders the text and an iframe when video_url is set; POST /lessons/{id}/complete adds the lesson to the enrolment, awards 10 XP once (repeat POST does not double), and a completion the day after the last one raises the streak |
| Courses & lessons | Teacher authoring: create course, add lesson, reorder lessons | Built | a teacher POST /teach/courses creates a course; POST /teach/courses/{id}/lessons appends with the next order_index; POST /teach/lessons/{id}/move up swaps order with the previous lesson; a learner gets 403 on all three |
| Quizzes | Quiz generator: N questions at 4 difficulty levels from the lesson text; LLM path schema-validated | Built | POST /lessons/{id}/quiz with level=basic..expert returns a quiz of exactly N (default 5) questions, each with 4 options, one correct index and an explanation, generated by the heuristic in <2 s offline; a malformed model answer is rejected and the heuristic result is used |
| Quizzes | Take a quiz and get graded with per-question feedback | Built | POST /quiz/{id}/submit with answers returns the score as correct/total, marks each question right or wrong and shows the explanation for wrong ones; a submission with a missing answer is counted wrong, not rejected |
| Quizzes | Attempt history and mastery per lesson | Built | after two attempts on one lesson, GET /lessons/{id} shows both attempts newest first and a mastery equal to the best score; the course page shows mastery per lesson |
| Quizzes | Adaptive difficulty: the next suggested level moves with the score | Built | tests: score >=80% at 'core' suggests 'advanced'; <50% at 'core' suggests 'basic'; between stays; the lesson page's 'Practice again' button carries the suggested level |
| Practice | Flashcards generated from a lesson (term → meaning pairs) | Built | POST /lessons/{id}/flashcards creates >=5 cards for the learner from the lesson text with distinct fronts; calling it again does not duplicate cards |
| Practice | SM-2 review: quality 0–5 updates ease, interval and due date | Built | tests/test_practice.py pins the SM-2 maths: q=5 on a new card → interval 1 then 6 then round(6*ease); q<3 resets the interval to 1; ease never drops below 1.3 |
| Practice | Due queue with a one-card-at-a-time review page | Built | GET /practice lists only cards whose due date is today or earlier, oldest due first; reviewing the last due card renders the 'all caught up' state; headless: flip + rate with 0 console errors |
| Study plans & progress | Study plan generator: pace (relaxed / steady / intense) spreads the remaining lessons to a target date | Built | POST /courses/{id}/plan with pace=steady and a date 14 days out creates daily tasks that cover every incomplete lesson exactly once, in order, ending on or before the target date, with no day carrying more than ceil(lessons/plan-days) lessons; intense finishes earlier than relaxed |
| Study plans & progress | Adjusting the pace regenerates the plan without losing completed lessons | Built | after completing one planned lesson, PATCH pace=relaxed rebuilds the plan from today with the completed lesson absent and every remaining lesson present |
| Study plans & progress | Learner dashboard: enrolled courses, completion, mastery, streak, XP, cards due, today's plan | Built | a test asserts each dashboard tile equals its SQL definition on the seeded DB; headless: the tiles and today's tasks render with 0 console errors |
| Study plans & progress | Certificate on course completion with a unique number and a public verify page | Built | completing the last lesson issues one certificate ABH-<year>-NNNN (a second completion issues none); GET /verify/<number> is public and names the learner and course; an unknown number returns 404 |
| Teaching desk | Assignments per course with due dates; learners submit text answers | Built | a teacher POST /teach/courses/{id}/assignments creates one; a learner POST /assignments/{id}/submit stores the text once (a second submit replaces it); a submission after the due date is flagged late |
| Teaching desk | Grading with score and feedback, plus a rubric-based grade suggestion | Built | POST /teach/submissions/{id}/grade with score > max_score returns 422; a valid grade is visible to the learner; GET /teach/submissions/{id}/suggest returns a suggested score and >=3 feedback points from the heuristic rubric |
| Teaching desk | Class view: per-course enrolments, completion, mean mastery, submissions waiting | Built | a test asserts each class-view figure equals its SQL definition on the seeded DB; the page lists every learner in the course with completion and last activity |
| Teaching desk | At-risk radar: inactive 7+ days, mastery under 50%, behind plan — each flag links to the learner | Built | the seeded DB raises >=3 flags of >=2 kinds; each flag on the teacher dashboard links to /teach/learners/{id}; a learner active today with mastery 90% raises none |
| Tutor | Provider layer: Anthropic → OpenRouter → heuristic; never raises; badge in /healthz | Built | with no keys set /healthz ai=heuristic; a test monkeypatches a failing provider and the tutor still answers with provider='heuristic' |
| Tutor | Ask the lesson: a question about the open lesson gets an answer grounded in its text | Built | POST /tutor/ask with a question whose keywords appear in the lesson returns the matching sentences as the answer with provider='heuristic'; a question with no overlap returns an honest 'not covered in this lesson' reply |
| Tutor | What next: one recommended action (finish lesson / take quiz / review cards / start plan) with a reason | Built | for a learner with cards due, the recommendation is 'review cards'; with none due and an incomplete lesson it is 'continue lesson'; the drawer shows the recommendation with the provider badge equal to /healthz ai |
| Tutor | Tutor drawer UI with an honest provider badge | Built | headless: the drawer opens from any page, the badge text equals /healthz ai, and asking the sample question on a lesson shows an answer with 0 console errors |
| Accounts & access | Roles admin / teacher / learner with signed-cookie sessions; invite-only sign-up | Built | a learner gets 403 on /teach/*; an admin can invite a teacher and the invite link creates that role; the first account on an empty DB becomes admin |
| Accounts & access | Email through Resend with an on-screen fallback; invites and access mails delivered | Built | with no key mail.mode()=='screen' and the invite page shows the link; with a key a test-mode send returns ok |
| Accounts & access | Email-first demo access on the twin: /demo/request issues the PIN and a 2-day guest login | Built | POST /demo/request with an email returns the 'check your inbox' page (or on-screen credentials when no channel); the guest login signs in with the learner role and expires after 2 days |
| Ship | Local service under pm2 on :8361 with an ecosystem file | Built | pm2 list shows abhyas online; curl http://127.0.0.1:8361/healthz returns 200 after pm2 restart abhyas |
| Ship | Hosted twin on Vercel: ephemeral /tmp DB that reseeds, stateless PIN gate | Built | curl https://<twin>/healthz returns 200 with ai provider; GET /app without the PIN cookie redirects to /demo; with the PIN the dashboard renders |
| Ship | Betadoc site served at / of the twin: story, product, architecture, honest feature matrix, roadmap | Built | GET / on the twin returns the site; its feature matrix marks built / partial / roadmap exactly as the task DB reads at publish time; documentation page links resolve |
| Ship | Custom subdomain abhyas.ajaykongala.online with a verified Resend sender | Partial | curl -sI https://abhyas.ajaykongala.online/ returns 200 over TLS and /healthz matches the vercel.app twin; Resend lists the sending domain as verified |
| Ship | Headless walk of every page: 0 console errors, forms submit | Built | tools/walk.py visits dashboard, catalogue, a course, a lesson, a quiz, practice, plan, teach desk, class view and the tutor drawer on :8361 and reports 0 console errors and 0 failed requests |
| Ship | Registry, HOME.md, cockpit :6798 under pm2, memory updated | Built | tools/check_registry.py passes with 8361 and 6798 present; the cockpit's five tabs render with 0 console errors and the architecture boxes reflect task state |
| Ship | Card on OC Apps: apps.json entry, mark, rewrite, measured runs-on, rebuilt and deployed | Built | oc-apps/tools/check_apps.py --probe says schema ok and abhyas live; the hub's walk passes with four cards; https://oc-apps.ajaykongala.online/abhyas/ serves the betadoc |
What comes after the demo, in the order a coaching centre would ask for it.
Two lanes, one codebase, no code changes between them. The twin is flipped with environment variables alone.
Runs on the centre's PC or a small Linux box under a process manager. One SQLite file is the whole database; back it up by copying it. Model keys are optional and stay on that machine.
uv venv .venv && uv pip install -r requirements.txt pm2 start ecosystem.config.js # :8361 # optional: ANTHROPIC_API_KEY or OPENROUTER_API_KEY in .env
The same app on a serverless host behind a PIN gate, with an ephemeral database that reseeds itself on every cold start. Nothing to install, up around the clock, and safe to hand to a prospect because nothing persists.
ABHYAS_DB=/tmp/abhyas.db ABHYAS_SEED=1 BETADOC_PIN=… FEED_TOKEN=… vercel --prod # betadoc at /, app behind /demo
Synthetic learners, real workflows. Read a lesson, take the quiz, rate a card, and watch the teacher's radar move.