Feat/v2 display drill output - #1
Closed
TDXu wants to merge 14 commits into
Closed
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
- scripts/studylib/render_html.py: parse_options + render_quiz_html - templates/quiz.html.j2: inline CSS+JS, reveal toggle, per-question grading - tests/test_render_html.py: 6 tests (half/full-width, none, continuation, embed, reveal off) parse_options accepts both half-width (A-H/a-h) and full-width (A-Ha-h) option letters, normalizing to ASCII uppercase via NFKC so downstream rendering and tuples always get A/B/…. Co-Authored-By: Claude <noreply@anthropic.com>
In _question_for_render, option letters were normalized to ASCII uppercase
(via unicodedata.normalize('NFKC', ...).upper()), but the answer letters used
for grading (data-answer) and the displayed answer were not. A question whose
answer was lowercase (e.g. 'abc') or full-width would silently grade wrong:
the JS compares uppercase option letters against un-normalized answer letters.
Normalize answer letters the same way, and join them so the displayed
'答案:<b>{{ q.answer }}</b>' is consistent with the uppercase option letters
and the data-answer grading attribute.
Adds regression test test_lowercase_answer_normalized_for_grading.
Co-Authored-By: Claude <noreply@anthropic.com>
Thin typer wrapper around studylib.render_html.render_quiz_html: reads a manifest JSON via ioutils.read_json, renders the interactive HTML quiz, and writes to --out (default <manifest>.html). Co-Authored-By: Claude <noreply@anthropic.com>
- studylib/render_paper.py: manifest_to_markdown(variant) + markdown_to_pdf(fonts_dir) with CID-fallback font strategy (no font files needed for default path). - scripts/render_paper.py: CLI --manifest [--variant questions|answers|both] [--out-dir] [--fonts-dir]. - requirements.txt: +reportlab>=4.0. - tests/test_render_paper.py: pure-function + PDF smoke (CID fallback). - CHANGELOG: F3 entry; remove scripts/md_to_pdf.py (folded into render_paper). Co-Authored-By: Claude <noreply@anthropic.com>
Implements weighted sampling without replacement via the Efraimidis-Spirakis keyed method (key = u**(1/w), take top-k): - syllabus mode weights by exam_weight - diagnostic mode weights by WEAKNESS_SCORE when any KC has a learning record, falling back to exam_weight when all KCs are unseen - result is deterministic given seed; count is capped to available KCs; unknown modes raise ValueError Co-Authored-By: Claude <noreply@anthropic.com>
… not certainty Replace the pinned seed=0 assertion (which claimed select_kcs returns ["weak"] with certainty) with a multi-seed distribution test. The Efraimidis-Spirakis sampler only guarantees higher probability for higher weights, not certainty — the old test was deterministic but dishonest. The new test sweeps 400 seeds and asserts the frequency ordering weak > unseen > confirmed that the weights (1.0/0.4/0.0) actually imply, plus a >200 majority threshold for weak (observed 274, theoretical ~284). Also drop the unused `from studylib.nextstep import WEAKNESS_SCORE` module-level import. Production code (scripts/studylib/drill.py) untouched — test-honesty fix only. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
A question tagged with N selected KCs was placed into N by_kc buckets, then emitted once per bucket by the round-robin, producing duplicate stems in the drill output. Track seen question_ids during round-robin and skip already-picked questions while still advancing the slot. Co-Authored-By: Claude <noreply@anthropic.com>
…outing Co-Authored-By: Claude <noreply@anthropic.com>
C1: render_quiz_html now uses Environment(autoescape=True); user-controlled stem/option/solution text can no longer inject raw HTML. Verified by test_user_text_is_html_escaped. Minors: - test: 2-question distinct radio groups (name="q1"/name="q2") - render_paper: drop unused Spacer/Table/TableStyle/PageBreak imports - test: assert %PDF- magic bytes in PDF smoke test - drill: simplify w > 0 guard in _weighted_sample_no_replace - drill: clarify select_kcs diagnostic comment - drill CLI: mode-aware summary header (按考纲权重 / 按弱点(自适应)) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a V2 “drill output” pipeline: selecting KCs (syllabus-weighted or diagnostic-adaptive), gathering questions, building a render-friendly manifest, and rendering either an interactive HTML quiz page or printable PDFs—while standardizing user-facing KC display as kc_id(中文名).
Changes:
- Introduces drill selection + question gathering (
studylib.drill) and a one-stopscripts/drill.pyCLI that outputs HTML/PDF/Markdown plus a manifest. - Adds a manifest contract (
studylib.manifest) and two renderers: HTML (studylib.render_html+templates/quiz.html.j2) and PDF (studylib.render_paper, ReportLab). - Unifies KC display via
studylib.display.kc_labelacross next-step, dashboard, evidence, misconception outputs; addsread_jsonutility and updates changelog/requirements.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_render_paper.py | Adds coverage for paper markdown variants + PDF generation smoke test. |
| tests/test_render_html.py | Adds coverage for option parsing, HTML rendering, escaping, grading attributes. |
| tests/test_nextstep.py | Verifies next-step includes kc_label while keeping back-compat fields. |
| tests/test_manifest.py | Adds tests for manifest shape, meta fields, and kc_labels. |
| tests/test_ioutils.py | Adds tests for read_json roundtrip and missing-file behavior. |
| tests/test_drill_select.py | Tests KC selection behavior (modes, determinism, distribution). |
| tests/test_drill_gather.py | Tests question gathering, shortfall, de-duplication. |
| tests/test_display.py | Tests kc_label formatting and fallbacks. |
| tests/test_cli_smoke.py | Extends CLI smoke tests for label output, HTML rendering, and drill CLI. |
| templates/quiz.html.j2 | Adds self-contained interactive quiz HTML template with reveal/grade UX. |
| templates/dashboard.md.j2 | Switches dashboard next-step display to prefer kc_label. |
| SKILL.md | Updates skill routing to require KC label format and adds drill intent routing. |
| scripts/studylib/render_paper.py | Adds ReportLab-based manifest→PDF pipeline with CJK font fallback. |
| scripts/studylib/render_html.py | Adds manifest→HTML rendering, option parsing, and answer normalization. |
| scripts/studylib/nextstep.py | Emits kc_label in next-step recommendations. |
| scripts/studylib/manifest.py | Adds manifest builder that strips unneeded fields and computes kc_labels. |
| scripts/studylib/ioutils.py | Adds read_json helper for JSON files. |
| scripts/studylib/drill.py | Adds KC selection + question gathering utilities for drill. |
| scripts/studylib/display.py | Adds kc_label helper for consistent user-facing KC formatting. |
| scripts/studylib/dashboard.py | Uses kc_label in risk summaries. |
| scripts/studylib/cli_common.py | CLI next-step output now prefers kc_label. |
| scripts/render_quiz_html.py | Adds CLI to render quiz HTML from a manifest JSON. |
| scripts/render_paper.py | Adds CLI to render PDFs from a manifest JSON (questions/answers/both). |
| scripts/misconception.py | Shows KC labels by loading .study/kc.json and using kc_label. |
| scripts/md_to_pdf.py | Removes legacy one-off PDF renderer (superseded by render_paper). |
| scripts/evidence.py | Uses kc_label in evidence listing header and empty-state messages. |
| scripts/drill.py | Adds end-to-end drill CLI (select→gather→manifest→render→summary). |
| requirements.txt | Adds reportlab>=4.0 dependency for PDF rendering. |
| CHANGELOG.md | Records new V2 features and CLI/rendering additions under Unreleased/rc1. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+34
to
+35
| ans = q.get("answer", "") or "" | ||
| letters = sorted({unicodedata.normalize("NFKC", ch).upper() for ch in ans if ch.strip()}) |
Comment on lines
+67
to
+68
| shortfall = {k: per_kc - len(by_kc[k]) for k in kc_ids if len(by_kc[k]) < per_kc} | ||
| return picked, shortfall |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.