ClassLensPortal / QUESTION_CATEGORIZATION.md
taboola
UI redesign to light theme, question bank creation, report redesign, info tooltips, and icon labels
e2fcda8
|
Raw
History Blame Contribute Delete
6.83 kB
# ClassLens — Question Categorization & Bank System
## Overview
When a teacher uploads a questions PDF, ClassLens automatically classifies every question into a pre-defined taxonomy using an LLM. This classification feeds three downstream features:
1. **Richer student reports** — wrong-answer blocks show the specific grammar/vocabulary tag that was tested, and the Weakness Summary groups errors by tag.
2. **Shared question bank** — classified questions accumulate across all uploads and all teachers, organized by category and tag.
3. **Practice question generation** — teachers can generate new questions by selecting a category and tag; the system rephrases questions already in the bank rather than hallucinating new ones.
---
## Taxonomy
The taxonomy has **6 main categories**. Two of them (字詞理解 and 語法結構) have sub-tags; the other four are used as-is.
### 字詞理解 (Vocabulary & Word Usage)
| Sub-group | Tags |
|---|---|
| 詞性 | 形容詞用法, 情狀副詞, 頻率副詞, 數量形容詞, 介系詞 |
| 花費動詞 | spend/cost/take/pay 用法 |
| 字彙 | 動詞與動詞片語, 名詞與名詞片語, 形容詞與副詞, 介系詞與連接詞片語, 情境字彙推斷 |
### 語法結構 (Grammar Structures)
| Sub-group | Tags |
|---|---|
| 時態 | 現在簡單式, 現在簡單式第三人稱單數, 現在進行式, 現在完成式, 過去式be動詞, 過去簡單式, 過去進行式, 過去完成式, 未來式 |
| 語態 | 被動語態 |
| 代名詞 | 人稱代名詞主格, 人稱代名詞受格, 所有格代名詞, 反身代名詞, 不定代名詞, 指示代名詞 |
| 連接詞 | 對等連接詞, when/before/after, if/although, why/because/so, as long as/as soon as, when/while, 相關連接詞 |
| 關係子句 | 關係代名詞, 關係代名詞省略 |
| 動詞型態 | 動名詞, 不定詞, 感官動詞, 使役動詞, 連綴動詞, 授與動詞, 助動詞, used to |
| 句型結構 | 祈使句, 附加問句, 附和句, 間接問句, 比較級, 最高級, There be 句型, It takes 句型, Too...to 句型, So...that 句型, Enough...to 句型, 動名詞當主詞, 虛主詞it |
| 疑問句型 | Who/What/Where/When/Which/How/Why/How often/How much/How long 問答句 |
### 文意推論 (Inference)
No sub-tags. Covers questions that require reading between the lines.
### 篇章大意 (Main Idea)
No sub-tags. Covers questions about the overall topic or gist of a passage.
### 篇章細節 (Reading for Detail)
No sub-tags. Covers questions about specific facts stated in a passage.
### 篇章結構 (Text Organization)
No sub-tags. Covers questions about how a passage is organized (e.g., ordering paragraphs, identifying transitions).
---
## How Categorization Works
### Trigger
Categorization runs **once per questions PDF upload**, in the background after the upload response is returned to the client. It does not block the upload or report generation.
### Flow
```
Teacher uploads questions PDF
Parser extracts [{number, text}, ...] from PDF
process_uploaded_files() saves rows to ParsedData (main_category = "", tags = [])
Upload response returned to client immediately
▼ (asyncio.create_task — background)
categorize_questions(session_id, teacher_id, questions)
├── Builds LLM prompt with full taxonomy + all question texts
├── Calls gpt-4o-mini → returns JSON [{question_num, main_category, tags}, ...]
├── Validates: main_category must be one of the 6; tags must match that category
├── update_parsed_data_categories() — writes main_category + tags to ParsedData rows
└── save_question_bank_batch() — inserts into shared question_bank table
```
If the LLM call or DB write fails, the error is logged and the upload is unaffected.
### Files
| File | Purpose |
|---|---|
| `chatkit/backend/app/taxonomy.py` | Single source of truth for all categories and tags |
| `chatkit/backend/app/question_categorizer.py` | LLM categorization + DB writes |
| `chatkit/backend/app/models.py` | `QuestionBank` ORM model, `ParsedData` columns `main_category` + `tags` |
| `chatkit/backend/app/database.py` | `update_parsed_data_categories`, `save_question_bank_batch`, `query_question_bank` |
| `chatkit/backend/alembic/versions/0003_question_bank.py` | Migration: creates the `question_bank` table |
---
## Question Bank
The `question_bank` table is **shared across all teachers**. Questions persist even if the original quiz or teacher account is deleted (FK uses `ON DELETE SET NULL`).
### Schema
| Column | Type | Notes |
|---|---|---|
| `id` | integer PK | |
| `quiz_id` | integer, nullable | Source quiz (SET NULL on delete) |
| `teacher_id` | integer, nullable | Uploading teacher (SET NULL on delete) |
| `question_text` | text | Full question as extracted from PDF |
| `answer` | varchar(10) | Correct answer (may be empty if uploaded without answer key) |
| `main_category` | varchar(64), indexed | One of the 6 taxonomy categories |
| `tags` | JSON array | Tags from the category's sub-list |
| `created_at` | timestamptz | |
### Querying the bank
`query_question_bank(main_category, tags, limit)` in `database.py`:
- Fetches up to `limit×3` candidates by category (most recent first)
- If tags are specified, filters in Python to rows that share at least one tag
- Falls back to category-only results if no tag match is found
---
## Practice Question Generation
When a teacher requests practice questions for a category/tag combination:
1. `query_question_bank(category, tags)` fetches matching source questions from the bank.
2. If no bank questions match the filter, the endpoint returns an empty list — no AI fallback.
3. If matches are found, `gpt-4o-mini` is asked to **rephrase** the source questions (same grammar point, varied vocabulary and context) — not to generate new topics.
This ensures generated questions always test real skills from past exams and never drift outside the taxonomy.
---
## How Categories Appear in Reports
When a student report is generated, `main.py` fetches the `ParsedData` rows for the session and builds a `categories` dict (`question_num → (main_category, tags)`). This is passed to `generate_student_report`.
Each wrong-answer block in the LLM prompt includes a **分類** line:
```
**分類**: 語法結構 > 時態, 過去簡單式
```
Or for tag-free categories:
```
**分類**: 篇章細節
```
The **Weakness Summary** section of the report instructs the LLM to:
- Name the specific **tags** that recurred most (e.g., 過去簡單式, 被動語態)
- Fall back to the **category name** for the four tag-free categories
- Group errors by these tags — no question numbers, no vague summaries