diff --git a/docs/TODO.md b/docs/TODO.md index 46c440c..1fd7704 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -301,6 +301,61 @@ Captured so nothing is lost while the article writing runs. toggle, "Continue your study", and a study-analysis donut. The current dashboard becomes this; a separate signed-out landing page comes later. +## Analysis, as specified 2026-09-11 (evening) + +Sessions is just sessions. **Every general measure of performance lives on +Analysis**, which has three tabs. + +### Performance + +- [ ] **Readiness** — two cards. One is the headline score (AMBOSS calls it + EPC, an equated percent correct); the other is peer comparison. Both say + what is still needed before they mean anything — "complete 32 more + questions" — rather than showing a number built on four answers. +- [ ] **Next step: adaptive session** — a card with a question-count select and + one button. It repeats on the Recommendations tab; it is the thing the + whole page is for. +- [ ] **Performance over time** — a line of the headline score by date, with an + honest empty state ("complete more questions to unlock this chart") + rather than a chart drawn through two points. +- [ ] **Analysis panel beside it**, with **All attempts / Latest attempt** + tabs: a donut split correct / correct-with-hints / incorrect, a sentence + naming attempts and unique questions, and the three figures written out + beneath. This is the pattern to copy for our own session analysis. +- [ ] **Completion** — four figures (questions answered n/total, answered + correctly, time per question, total time spent) with an **Edit time + range** control, so "this month" and "all time" are different questions. + +### Recommendations + +- [ ] **Your knowledge profile** — topics ranked by score, lowest marked + **FOCUS AREA**, under three tabs: **Articles, Systems, Disciplines**. + Columns: Topic, score, **Relevance**, Status, Action. A row expands to + questions completed with a bar, answered-correctly with a + correct/hints/incorrect bar, and a Start Qbank button for that topic. +- [ ] **Relevance is the ABP content specification weight.** This is the part + we can do properly and AMBOSS cannot explain: a topic's relevance is the + share of the real paper its domain accounts for, which + `exam_blueprints.weight` already holds. Cardiology at 5% and + rheumatology at 2% are not equally worth an hour, and the page should + say so from the board's own numbers rather than from a guess. + +### Session Analysis + +- [ ] **A rail of latest sessions** on the left, collapsible — mode, title, + answered/total and a progress bar each — with that session's results in + the main area: the same four figures, the same donut, and study + recommendations under Articles / Disciplines / Systems tabs. +- [ ] **Remove the overall figures from the session view.** Now that the + Performance tab holds them, a session page showing lifetime totals + answers a question nobody asked while standing in front of one session. + +### Not us + +- [ ] **No AI Mode for clinical content.** PedsHub is for learning; the + clinical tool is app.pedshub.com. AI Mode answers about the corpus a + learner is studying, and nothing here is for use at a bedside. + ## Collections, as shown 2026-09-11 (evening) - [ ] **A collections page.** Favorites and the question libraries in one diff --git a/docs/study-recommendations.md b/docs/study-recommendations.md index fc9befd..f6367ad 100644 --- a/docs/study-recommendations.md +++ b/docs/study-recommendations.md @@ -132,3 +132,83 @@ Four steps: The first two are defects and are worth fixing. The last two are honest limitations of the current design and would be the substance of a better one. + +--- + +## 3. What the adaptive session should be + +Written 2026-09-11, after the exam blueprint landed. The design below is not +built; this is the argument for it, so the decision is on paper before the code +is. + +### The thing that was missing + +Every version of this so far has ranked questions by **how badly you are doing** +and nothing else. That is half a question. The other half is **how much it +matters**, and until today there was nothing in the database that could answer +it — so the code guessed, by treating every category as equally worth an hour. + +`exam_blueprints.weight` answers it now. The ABP publishes that preventive care +is 12% of a general paediatrics paper and rheumatology is 2%. Six points of +weakness in preventive care costs six times what the same weakness costs in +rheumatology, and a session that does not know this will spend your evening in +the wrong place while looking perfectly reasonable. + +### The score + +For each topic, one number — the marks you would expect to gain by studying it: + +``` +expected_gain = relevance × headroom × confidence +``` + +* **relevance** — the domain's published weight, divided among the topics under + it. From the blueprint. This is the part that is a fact rather than a model. +* **headroom** — `1 − readiness`. How much of that share you are currently + losing. Readiness is the shrunk accuracy the recommendations page already + computes, *not* raw accuracy: one wrong answer out of one must not read as + "you know nothing about neonatology". +* **confidence** — how much the estimate can be trusted, `n / (n + k)`. A topic + you have answered twice cannot outrank one you have answered forty times on + the strength of a bad afternoon. This is what stops the session chasing noise. + +Sort topics by expected gain; fill the session from the top, damped as now so +one topic cannot take the whole session. + +### Then, within a topic: difficulty that moves + +The current session filters by difficulty and then ignores it. It should walk: +start near the learner's demonstrated level for that topic, step up after two +right, step down after one wrong. The point is not to be hard, it is to sit +where the information is — a question you would get right nine times in ten +teaches nothing, and neither does one you would get right once in ten. + +### And a floor on coverage + +A pure gain ranking will never show you a topic you are already good at, which +is how people arrive at an exam having forgotten something they knew in March. +Reserve a share of every session — a fifth, say — for **spaced return**: topics +you were right about, longest ago first. This is the one part that should not be +optimised, because its whole purpose is to be unwelcome. + +### What has to be fixed first + +The two defects named in §2 are load-bearing here, not tidying: + +* **`.limit(2000)`** means a third of the bank is invisible to selection. A + scheme that reasons carefully about which question matters most, over an + arbitrary two-thirds of the questions, is a scheme that reasons carefully + about the wrong set. +* **The O(answers × candidates) accuracy scan** is already the slowest part of + building a session. This design asks for per-topic readiness *and* per-topic + difficulty, which makes it worse. It needs to become one grouped query. + +### What this deliberately does not do + +No item-response theory, no per-question difficulty estimated from other +learners' answers. Both would be better with enough data and worse without it, +and a bank this size with a handful of learners does not have it. The weights +are published, the readiness is measured, and the arithmetic above can be +explained to a learner in two sentences — which is the point, because a session +that cannot say why it chose a question is asking to be trusted rather than +earning it. diff --git a/frontend/src/components/AnalysisShell.css b/frontend/src/components/AnalysisShell.css index 066b749..4d1f955 100644 --- a/frontend/src/components/AnalysisShell.css +++ b/frontend/src/components/AnalysisShell.css @@ -22,7 +22,13 @@ /* ── Rail ─────────────────────────────────────────────────────────── */ .ax-rail { - position: sticky; top: 0; height: 100vh; + /* Stuck below the header, exactly as tall as what is left of the window. + At top:0 and 100vh it began underneath the header and ran past the bottom + of the screen, so the end of the list — and the scroll that would reach + it — were never on screen. The list inside scrolls on its own; the page + behind it scrolls separately. */ + position: sticky; top: var(--app-header, 98px); + height: calc(100dvh - var(--app-header, 98px)); display: flex; flex-direction: column; min-width: 0; background: var(--card-bg); border-right: 1px solid var(--border); /* Reach the left edge of the window without leaving the grid. */ @@ -74,7 +80,14 @@ } @media (min-width: 900px) { .ax-rail-search { font-size: 0.84rem; } } -.ax-rail-list { list-style: none; margin: 0; padding: 0; flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; } +.ax-rail-list { + list-style: none; margin: 0; padding: 0; + flex: 1; min-height: 0; + overflow-y: auto; -webkit-overflow-scrolling: touch; + /* A wheel over the list moves the list and stops there, rather than + carrying on into the page once the list reaches its end. */ + overscroll-behavior: contain; +} .ax-rail-list > li { border-bottom: 1px solid var(--border); } .ax-rail-list > li:last-child { border-bottom: 0; } .ax-rail-list a { diff --git a/frontend/src/index.css b/frontend/src/index.css index 37bf012..8a8c06c 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -122,6 +122,11 @@ html, body { overflow-x: hidden; max-width: 100%; } /* The footer belongs at the bottom of the window, not wherever the content happens to stop. A loading page is a spinner, and without this the footer sat mid-screen with the page background below it. */ +:root { + /* The sticky header: the navbar plus the section bar beneath it. Anything + that pins itself below the header measures from here rather than guessing. */ + --app-header: 98px; +} .app-shell { display: flex; flex-direction: column; min-height: 100dvh; } .app-main { flex: 1 0 auto; width: 100%; } .app-shell > .site-footer { flex: none; } diff --git a/frontend/src/pages/AnalysisSessionPage.css b/frontend/src/pages/AnalysisSessionPage.css index f2b74d5..6233fdf 100644 --- a/frontend/src/pages/AnalysisSessionPage.css +++ b/frontend/src/pages/AnalysisSessionPage.css @@ -104,3 +104,31 @@ .an-plan a { color: var(--primary); text-decoration: none; font-weight: 600; } .an-plan a:hover { text-decoration: underline; } .an-plan-links { display: flex; gap: 16px; } + +/* ── What to do about the result, beneath the result ────────────────── + These were a row of buttons beside the page heading — a blue Resume and + a red Delete session floating above everything, as far from the figures + they follow from as the layout allowed. */ +.an-result { display: flex; flex-direction: column; } +.an-actions { + display: flex; align-items: center; gap: 8px; flex-wrap: wrap; + margin-top: auto; padding-top: 18px; +} +.an-actions .btn { flex: 0 1 auto; } +.an-confirm { display: inline-flex; gap: 8px; flex-wrap: wrap; } + +/* Offered, not urged: a quiet control at the end of the row rather than a + red button competing with Resume. */ +.an-remove { + margin-left: auto; padding: 6px 4px; + font: inherit; font-size: 0.79rem; + background: none; border: 0; cursor: pointer; + color: var(--text-subtle); text-decoration: underline; + text-underline-offset: 3px; +} +.an-remove:hover { color: var(--wrong-fg); } + +@media (max-width: 560px) { + .an-actions .btn { flex: 1 1 auto; text-align: center; } + .an-remove { margin-left: 0; width: 100%; text-align: center; } +} diff --git a/frontend/src/pages/AnalysisSessionPage.jsx b/frontend/src/pages/AnalysisSessionPage.jsx index 8d85fdf..0e5184d 100644 --- a/frontend/src/pages/AnalysisSessionPage.jsx +++ b/frontend/src/pages/AnalysisSessionPage.jsx @@ -131,36 +131,6 @@ export default function AnalysisSessionPage() {
- We will save your current answers before leaving, so you can resume from here.
- {timeLeft !== null && (
- <>
- Timer will pause while you are away and resume when you return.
-
- Closing the tab without suspending keeps the clock running. When it runs out, the exam is submitted with what you have answered and you will see your score.
-
- >
- )}
-