Lincoln Gombedza commited on
Commit Β·
27cf88f
0
Parent(s):
Initial commit: NCLEX Practice Question Generator
Browse files- ._.github +0 -0
- ._.gitignore +0 -0
- ._README.md +0 -0
- ._questions +0 -0
- ._requirements.txt +0 -0
- ._scorer.py +0 -0
- ._streamlit_app.py +0 -0
- .github/._workflows +0 -0
- .github/workflows/._sync-to-hf.yml +0 -0
- .github/workflows/sync-to-hf.yml +25 -0
- .gitignore +7 -0
- README.md +141 -0
- questions/.___init__.py +0 -0
- questions/._bank.py +0 -0
- questions/._calculations.py +0 -0
- questions/__init__.py +0 -0
- questions/bank.py +704 -0
- questions/calculations.py +254 -0
- requirements.txt +2 -0
- scorer.py +96 -0
- streamlit_app.py +435 -0
._.github
ADDED
|
Binary file (4.1 kB). View file
|
|
|
._.gitignore
ADDED
|
Binary file (4.1 kB). View file
|
|
|
._README.md
ADDED
|
Binary file (4.1 kB). View file
|
|
|
._questions
ADDED
|
Binary file (4.1 kB). View file
|
|
|
._requirements.txt
ADDED
|
Binary file (4.1 kB). View file
|
|
|
._scorer.py
ADDED
|
Binary file (4.1 kB). View file
|
|
|
._streamlit_app.py
ADDED
|
Binary file (4.1 kB). View file
|
|
|
.github/._workflows
ADDED
|
Binary file (4.1 kB). View file
|
|
|
.github/workflows/._sync-to-hf.yml
ADDED
|
Binary file (4.1 kB). View file
|
|
|
.github/workflows/sync-to-hf.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [main]
|
| 6 |
+
|
| 7 |
+
jobs:
|
| 8 |
+
sync-to-hub:
|
| 9 |
+
runs-on: ubuntu-latest
|
| 10 |
+
steps:
|
| 11 |
+
- name: Checkout repo
|
| 12 |
+
uses: actions/checkout@v4
|
| 13 |
+
with:
|
| 14 |
+
fetch-depth: 0
|
| 15 |
+
lfs: true
|
| 16 |
+
|
| 17 |
+
- name: Push to Hugging Face Space
|
| 18 |
+
env:
|
| 19 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 20 |
+
run: |
|
| 21 |
+
git config --global user.email "ci@github.com"
|
| 22 |
+
git config --global user.name "GitHub Actions"
|
| 23 |
+
git push \
|
| 24 |
+
https://NurseCitizenDeveloper:$HF_TOKEN@huggingface.co/spaces/NurseCitizenDeveloper/nclex-prep \
|
| 25 |
+
main --force
|
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
.env
|
| 4 |
+
.venv/
|
| 5 |
+
venv/
|
| 6 |
+
.DS_Store
|
| 7 |
+
*.log
|
README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: NCLEX Practice Question Generator
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
sdk_version: 1.32.0
|
| 8 |
+
app_file: streamlit_app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
<div align="center">
|
| 14 |
+
|
| 15 |
+
# π NCLEX Practice Question Generator
|
| 16 |
+
|
| 17 |
+
**Clinically accurate NCLEX-style questions Β· Full rationales Β· Unlimited dosage calculations**
|
| 18 |
+
|
| 19 |
+
[](https://huggingface.co/spaces/NurseCitizenDeveloper/nclex-prep)
|
| 20 |
+
[](https://github.com/Clinical-Quality-Artifical-Intelligence/nclex-prep)
|
| 21 |
+
[](LICENSE)
|
| 22 |
+
[](https://www.ncsbn.org/nclex-rn)
|
| 23 |
+
|
| 24 |
+
*No login Β· No API key Β· No cost Β· Built by a nurse, for nurses*
|
| 25 |
+
|
| 26 |
+
</div>
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## Overview
|
| 31 |
+
|
| 32 |
+
The **NCLEX Practice Question Generator** provides student nurses with high-quality, evidence-based practice questions aligned to the **2023 NCLEX-RN Test Plan** (NCSBN). Every question includes a detailed clinical rationale β teaching students not just the correct answer, but the critical thinking process behind it.
|
| 33 |
+
|
| 34 |
+
Dosage calculation questions are dynamically generated, providing unlimited unique math practice so students never memorise the same problem twice.
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## Features
|
| 39 |
+
|
| 40 |
+
### π― Practice Quiz
|
| 41 |
+
- **30+ high-quality questions** across all NCLEX Client Needs categories
|
| 42 |
+
- **Multiple Choice (MCQ)** β single best answer format
|
| 43 |
+
- **Select All That Apply (SATA)** β the most challenging NCLEX question type
|
| 44 |
+
- Filter by **category**, **difficulty**, and **question type**
|
| 45 |
+
- Instant answer reveal with detailed **clinical rationale**
|
| 46 |
+
|
| 47 |
+
### π Dosage Calculations
|
| 48 |
+
- **Dynamically generated** β unlimited unique problems, never the same set twice
|
| 49 |
+
- Five question types:
|
| 50 |
+
- Oral tablet calculations
|
| 51 |
+
- IV infusion rate (mL/hr)
|
| 52 |
+
- Manual drip rate (gtt/min)
|
| 53 |
+
- Weight-based IV infusions (mcg/kg/min)
|
| 54 |
+
- Paediatric oral liquid dosing
|
| 55 |
+
- Step-by-step solution shown in the rationale
|
| 56 |
+
|
| 57 |
+
### π Results & Performance Tracking
|
| 58 |
+
- Overall score and performance band
|
| 59 |
+
- **Category breakdown** β identify weak areas by NCLEX domain
|
| 60 |
+
- **Difficulty breakdown** β performance across beginner / intermediate / advanced
|
| 61 |
+
|
| 62 |
+
### π Wrong Answer Review
|
| 63 |
+
- Every missed question available for focused review
|
| 64 |
+
- NCLEX framework category shown for each question
|
| 65 |
+
- Rationale emphasised to build clinical reasoning
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## NCLEX Client Needs Categories Covered
|
| 70 |
+
|
| 71 |
+
| Category | Sub-categories |
|
| 72 |
+
|----------|---------------|
|
| 73 |
+
| **Safe & Effective Care Environment** | Management of Care Β· Safety & Infection Control |
|
| 74 |
+
| **Health Promotion & Maintenance** | Growth & Development Β· Preventive Care |
|
| 75 |
+
| **Psychosocial Integrity** | Mental Health Β· Therapeutic Communication |
|
| 76 |
+
| **Physiological Integrity** | Basic Care Β· Pharmacology Β· Risk Reduction Β· Physiological Adaptation |
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
## Question Types
|
| 81 |
+
|
| 82 |
+
| Type | Description | NCLEX Relevance |
|
| 83 |
+
|------|-------------|-----------------|
|
| 84 |
+
| MCQ | Single best answer from 4 options | ~50% of NCLEX-RN |
|
| 85 |
+
| SATA | Select all correct options from 5β6 | ~20% of NCLEX-RN |
|
| 86 |
+
| Calculation | Dosage math with step-by-step workings | ~10% of NCLEX-RN |
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## Tech Stack
|
| 91 |
+
|
| 92 |
+
| Layer | Technology |
|
| 93 |
+
|-------|-----------|
|
| 94 |
+
| Frontend | [Streamlit](https://streamlit.io/) (Python) |
|
| 95 |
+
| Question bank | Curated clinical question library (Python data structures) |
|
| 96 |
+
| Calc generator | Dynamic random question generation (no LLM required) |
|
| 97 |
+
| Hosting | [Hugging Face Spaces](https://huggingface.co/spaces) β free CPU tier |
|
| 98 |
+
| CI/CD | GitHub Actions β auto-deploy on push |
|
| 99 |
+
|
| 100 |
+
> **No API keys. No paid services. Fully open-source.**
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Local Development
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
git clone https://github.com/Clinical-Quality-Artifical-Intelligence/nclex-prep.git
|
| 108 |
+
cd nclex-prep
|
| 109 |
+
pip install -r requirements.txt
|
| 110 |
+
streamlit run streamlit_app.py
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
Requirements: Python 3.10+ Β· `streamlit` Β· `requests`
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
## About β Nursing Citizen Development
|
| 118 |
+
|
| 119 |
+
Part of the **Nursing Citizen Development** initiative β clinicians building the tools their profession needs.
|
| 120 |
+
|
| 121 |
+
**Related tools:**
|
| 122 |
+
- [EBP Research Tool](https://huggingface.co/spaces/NurseCitizenDeveloper/nursing-ebp-tool) β PubMed search with nursing summaries & APA citations
|
| 123 |
+
- [Drug Card Generator](https://huggingface.co/spaces/NurseCitizenDeveloper/nursing-drug-cards) β Instant FDA drug reference cards
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## Disclaimer
|
| 128 |
+
|
| 129 |
+
For educational purposes only. Questions are designed to simulate NCLEX-style critical thinking but do not represent actual NCLEX examination items. Always study from NCSBN-approved resources and consult your nursing programme's curriculum.
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## License
|
| 134 |
+
|
| 135 |
+
[MIT](LICENSE) Β© Nursing Citizen Development / Clinical Quality Artificial Intelligence
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
<div align="center">
|
| 140 |
+
<sub>Built with π©Ί by <a href="https://huggingface.co/NurseCitizenDeveloper">Nursing Citizen Development</a></sub>
|
| 141 |
+
</div>
|
questions/.___init__.py
ADDED
|
Binary file (4.1 kB). View file
|
|
|
questions/._bank.py
ADDED
|
Binary file (4.1 kB). View file
|
|
|
questions/._calculations.py
ADDED
|
Binary file (4.1 kB). View file
|
|
|
questions/__init__.py
ADDED
|
File without changes
|
questions/bank.py
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
NCLEX Question Bank
|
| 3 |
+
Structured by NCLEX Client Needs framework.
|
| 4 |
+
Each question follows NCSBN item-writing guidelines.
|
| 5 |
+
Types: mcq (single answer), sata (select all that apply), priority
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
QUESTIONS = [
|
| 9 |
+
|
| 10 |
+
# =========================================================
|
| 11 |
+
# PHARMACOLOGICAL & PARENTERAL THERAPIES
|
| 12 |
+
# =========================================================
|
| 13 |
+
{
|
| 14 |
+
"id": "pharm_001",
|
| 15 |
+
"category": "Pharmacology",
|
| 16 |
+
"subcategory": "Cardiac Medications",
|
| 17 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 18 |
+
"difficulty": "intermediate",
|
| 19 |
+
"type": "mcq",
|
| 20 |
+
"stem": "A nurse is preparing to administer digoxin 0.125 mg PO to a patient with heart failure. Before administration, the nurse assesses the apical pulse and finds it is 54 beats/min. What is the nurse's priority action?",
|
| 21 |
+
"options": [
|
| 22 |
+
"Administer the medication as ordered",
|
| 23 |
+
"Hold the medication and notify the provider",
|
| 24 |
+
"Administer half the prescribed dose",
|
| 25 |
+
"Recheck the pulse in 30 minutes before administering"
|
| 26 |
+
],
|
| 27 |
+
"correct": 1,
|
| 28 |
+
"rationale": "Digoxin should be held if the apical pulse is below 60 bpm in an adult and the provider notified immediately. Digoxin slows heart rate and conduction β administration with an already bradycardic rate could precipitate life-threatening dysrhythmias. The nurse should NEVER alter doses without a provider order.",
|
| 29 |
+
"tags": ["digoxin", "cardiac-glycoside", "bradycardia", "medication-safety"]
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"id": "pharm_002",
|
| 33 |
+
"category": "Pharmacology",
|
| 34 |
+
"subcategory": "Anticoagulants",
|
| 35 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 36 |
+
"difficulty": "beginner",
|
| 37 |
+
"type": "mcq",
|
| 38 |
+
"stem": "A patient is prescribed warfarin (Coumadin). Which laboratory value does the nurse use to monitor therapeutic effectiveness of this medication?",
|
| 39 |
+
"options": [
|
| 40 |
+
"Activated partial thromboplastin time (aPTT)",
|
| 41 |
+
"Complete blood count (CBC)",
|
| 42 |
+
"International normalised ratio (INR)",
|
| 43 |
+
"Serum potassium level"
|
| 44 |
+
],
|
| 45 |
+
"correct": 2,
|
| 46 |
+
"rationale": "INR (target 2β3 for most indications, 2.5β3.5 for mechanical valves) is the standard monitoring parameter for warfarin. aPTT monitors unfractionated heparin. CBC monitors for bleeding complications but not therapeutic level. Serum potassium is unrelated to warfarin therapy.",
|
| 47 |
+
"tags": ["warfarin", "anticoagulant", "INR", "lab-monitoring"]
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"id": "pharm_003",
|
| 51 |
+
"category": "Pharmacology",
|
| 52 |
+
"subcategory": "Beta Blockers",
|
| 53 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 54 |
+
"difficulty": "intermediate",
|
| 55 |
+
"type": "sata",
|
| 56 |
+
"stem": "A nurse is teaching a patient newly prescribed metoprolol succinate (Toprol-XL). Which instructions should the nurse include? Select all that apply.",
|
| 57 |
+
"options": [
|
| 58 |
+
"Do not stop this medication abruptly",
|
| 59 |
+
"Monitor your pulse before each dose",
|
| 60 |
+
"This medication may mask signs of hypoglycemia",
|
| 61 |
+
"Take the medication only when you feel your heart racing",
|
| 62 |
+
"Report dizziness or shortness of breath to your provider",
|
| 63 |
+
"You may double the dose if you miss one"
|
| 64 |
+
],
|
| 65 |
+
"correct": [0, 1, 2, 4],
|
| 66 |
+
"rationale": "Correct: Abrupt discontinuation can cause rebound hypertension or angina. Daily pulse monitoring detects bradycardia. Beta-blockers blunt the tachycardia response to hypoglycemia, masking a key symptom β important for diabetic patients. Dizziness/SOB may indicate dose is too high. Incorrect: Metoprolol is a scheduled daily medication, not PRN. Doubling doses is dangerous and not indicated.",
|
| 67 |
+
"tags": ["metoprolol", "beta-blocker", "patient-education", "SATA"]
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"id": "pharm_004",
|
| 71 |
+
"category": "Pharmacology",
|
| 72 |
+
"subcategory": "Diuretics",
|
| 73 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 74 |
+
"difficulty": "intermediate",
|
| 75 |
+
"type": "mcq",
|
| 76 |
+
"stem": "A nurse is caring for a patient receiving furosemide (Lasix) 40 mg IV for acute pulmonary oedema. Which assessment finding requires immediate intervention?",
|
| 77 |
+
"options": [
|
| 78 |
+
"Urine output of 200 mL in the first hour",
|
| 79 |
+
"Serum potassium of 2.8 mEq/L",
|
| 80 |
+
"Blood pressure decreased from 160/100 to 138/86 mmHg",
|
| 81 |
+
"Mild thirst reported by the patient"
|
| 82 |
+
],
|
| 83 |
+
"correct": 1,
|
| 84 |
+
"rationale": "A serum potassium of 2.8 mEq/L (normal 3.5β5.0 mEq/L) indicates hypokalaemia, a dangerous complication of loop diuretics. Hypokalaemia increases the risk of life-threatening dysrhythmias and potentiates digoxin toxicity. This requires immediate provider notification and potassium replacement. The other findings β diuresis, modest BP reduction, and thirst β are expected responses to furosemide.",
|
| 85 |
+
"tags": ["furosemide", "loop-diuretic", "hypokalaemia", "electrolytes"]
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"id": "pharm_005",
|
| 89 |
+
"category": "Pharmacology",
|
| 90 |
+
"subcategory": "Opioids",
|
| 91 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 92 |
+
"difficulty": "intermediate",
|
| 93 |
+
"type": "mcq",
|
| 94 |
+
"stem": "A nurse administers morphine sulphate 4 mg IV to a post-operative patient. Thirty minutes later, the patient's respiratory rate is 8 breaths/min and they are difficult to arouse. What is the nurse's first action?",
|
| 95 |
+
"options": [
|
| 96 |
+
"Administer naloxone (Narcan) as per protocol",
|
| 97 |
+
"Stimulate the patient and encourage deep breathing",
|
| 98 |
+
"Apply oxygen via non-rebreather mask",
|
| 99 |
+
"Notify the provider and document the findings"
|
| 100 |
+
],
|
| 101 |
+
"correct": 0,
|
| 102 |
+
"rationale": "The patient is showing signs of opioid-induced respiratory depression (RR < 12, difficult to arouse). The priority action is reversal with naloxone, an opioid antagonist. While stimulation and oxygen are supportive measures, they do not address the underlying cause. Naloxone rapidly reverses opioid effects. Provider notification occurs concurrently but is not the first action.",
|
| 103 |
+
"tags": ["morphine", "opioid", "respiratory-depression", "naloxone", "emergency"]
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"id": "pharm_006",
|
| 107 |
+
"category": "Pharmacology",
|
| 108 |
+
"subcategory": "Antidiabetics",
|
| 109 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 110 |
+
"difficulty": "beginner",
|
| 111 |
+
"type": "mcq",
|
| 112 |
+
"stem": "A nurse is preparing to administer insulin lispro (Humalog) to a patient before breakfast. The patient's blood glucose is 68 mg/dL. What is the nurse's priority action?",
|
| 113 |
+
"options": [
|
| 114 |
+
"Administer the insulin as scheduled",
|
| 115 |
+
"Hold the insulin and provide a carbohydrate snack",
|
| 116 |
+
"Administer half the prescribed insulin dose",
|
| 117 |
+
"Recheck the blood glucose in 15 minutes before deciding"
|
| 118 |
+
],
|
| 119 |
+
"correct": 1,
|
| 120 |
+
"rationale": "A blood glucose of 68 mg/dL is below the normal fasting range (70β100 mg/dL) and indicates hypoglycaemia. Administering insulin would drive the glucose further down, potentially causing a severe hypoglycaemic event. The nurse must hold the rapid-acting insulin, treat the hypoglycaemia with 15g of fast-acting carbohydrates, recheck in 15 minutes, and notify the provider. Never alter insulin doses without an order.",
|
| 121 |
+
"tags": ["insulin", "hypoglycaemia", "blood-glucose", "medication-safety"]
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"id": "pharm_007",
|
| 125 |
+
"category": "Pharmacology",
|
| 126 |
+
"subcategory": "Antibiotics",
|
| 127 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 128 |
+
"difficulty": "advanced",
|
| 129 |
+
"type": "mcq",
|
| 130 |
+
"stem": "A patient is receiving vancomycin 1g IV q12h for MRSA bacteraemia. The nurse notes the patient's serum creatinine has risen from 0.9 to 2.1 mg/dL over 48 hours. Which action is most appropriate?",
|
| 131 |
+
"options": [
|
| 132 |
+
"Continue the current dose β creatinine fluctuations are normal",
|
| 133 |
+
"Increase the dosing interval and notify the provider",
|
| 134 |
+
"Hold the vancomycin and notify the provider",
|
| 135 |
+
"Switch to an oral antibiotic per facility protocol"
|
| 136 |
+
],
|
| 137 |
+
"correct": 2,
|
| 138 |
+
"rationale": "A rise in creatinine from 0.9 to 2.1 mg/dL represents a >50% increase, suggesting vancomycin-induced nephrotoxicity. The nurse should hold the dose and immediately notify the provider. Vancomycin is renally cleared β continued dosing without adjustment in renal impairment leads to toxic drug accumulation. Switching antibiotics requires a provider order and is not the nurse's first action.",
|
| 139 |
+
"tags": ["vancomycin", "nephrotoxicity", "renal-function", "MRSA"]
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"id": "pharm_008",
|
| 143 |
+
"category": "Pharmacology",
|
| 144 |
+
"subcategory": "Corticosteroids",
|
| 145 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 146 |
+
"difficulty": "intermediate",
|
| 147 |
+
"type": "sata",
|
| 148 |
+
"stem": "A nurse is caring for a patient on long-term prednisone therapy. Which nursing assessments are priorities? Select all that apply.",
|
| 149 |
+
"options": [
|
| 150 |
+
"Blood glucose levels",
|
| 151 |
+
"Signs and symptoms of infection",
|
| 152 |
+
"Blood pressure and fluid status",
|
| 153 |
+
"Serum sodium levels for hyponatraemia",
|
| 154 |
+
"Bone density and fall risk",
|
| 155 |
+
"Urine output for polyuria"
|
| 156 |
+
],
|
| 157 |
+
"correct": [0, 1, 2, 4],
|
| 158 |
+
"rationale": "Correct: Prednisone causes hyperglycaemia (monitor glucose), immunosuppression (increases infection risk), fluid retention and hypertension (monitor BP/fluid status), and bone loss with long-term use (osteoporosis risk). Incorrect: Corticosteroids cause sodium retention (hypernatraemia risk, not hyponatraemia) and polyuria is more associated with diabetes insipidus, not corticosteroid use.",
|
| 159 |
+
"tags": ["prednisone", "corticosteroid", "long-term", "side-effects", "SATA"]
|
| 160 |
+
},
|
| 161 |
+
|
| 162 |
+
# =========================================================
|
| 163 |
+
# SAFETY AND INFECTION CONTROL
|
| 164 |
+
# =========================================================
|
| 165 |
+
{
|
| 166 |
+
"id": "safety_001",
|
| 167 |
+
"category": "Safety & Infection Control",
|
| 168 |
+
"subcategory": "Medication Safety",
|
| 169 |
+
"nclex_framework": "Safety and Infection Control",
|
| 170 |
+
"difficulty": "beginner",
|
| 171 |
+
"type": "mcq",
|
| 172 |
+
"stem": "The nurse is preparing to administer medications. Which action best reflects the standard of safe medication administration?",
|
| 173 |
+
"options": [
|
| 174 |
+
"Prepare all patients' medications together to save time",
|
| 175 |
+
"Verify the patient's name verbally without checking the armband",
|
| 176 |
+
"Compare the medication administration record against the original order at the bedside",
|
| 177 |
+
"Administer a medication that the patient identifies as their usual tablet"
|
| 178 |
+
],
|
| 179 |
+
"correct": 2,
|
| 180 |
+
"rationale": "Comparing the MAR against the original order at the bedside is part of the 10 Rights of Medication Administration and represents best practice. Preparing multiple patients' medications together risks mix-ups. Verbal identification alone is insufficient β two identifiers and armband checking are required. Patient identification of a tablet is unreliable and does not satisfy safety standards.",
|
| 181 |
+
"tags": ["medication-safety", "10-rights", "administration"]
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"id": "safety_002",
|
| 185 |
+
"category": "Safety & Infection Control",
|
| 186 |
+
"subcategory": "Infection Control",
|
| 187 |
+
"nclex_framework": "Safety and Infection Control",
|
| 188 |
+
"difficulty": "beginner",
|
| 189 |
+
"type": "mcq",
|
| 190 |
+
"stem": "A patient is admitted with confirmed Clostridioides difficile (C. diff) infection. Which personal protective equipment (PPE) should the nurse don before entering the room?",
|
| 191 |
+
"options": [
|
| 192 |
+
"Surgical mask and gloves",
|
| 193 |
+
"N95 respirator, gown, and gloves",
|
| 194 |
+
"Gown and gloves",
|
| 195 |
+
"Gloves only β alcohol hand gel is sufficient after removal"
|
| 196 |
+
],
|
| 197 |
+
"correct": 2,
|
| 198 |
+
"rationale": "C. difficile requires Contact Precautions: gown and gloves. An N95 is not required as C. diff is not airborne. Crucially, alcohol-based hand sanitiser is INEFFECTIVE against C. diff spores β soap and water (friction) is required for hand hygiene. Gloves alone do not protect clothing/skin sufficiently.",
|
| 199 |
+
"tags": ["C-diff", "contact-precautions", "PPE", "infection-control"]
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"id": "safety_003",
|
| 203 |
+
"category": "Safety & Infection Control",
|
| 204 |
+
"subcategory": "Fall Prevention",
|
| 205 |
+
"nclex_framework": "Safety and Infection Control",
|
| 206 |
+
"difficulty": "beginner",
|
| 207 |
+
"type": "sata",
|
| 208 |
+
"stem": "A nurse completes a fall risk assessment and identifies a patient as high risk for falls. Which interventions should the nurse implement? Select all that apply.",
|
| 209 |
+
"options": [
|
| 210 |
+
"Apply a yellow fall-risk armband",
|
| 211 |
+
"Ensure the call light is within reach at all times",
|
| 212 |
+
"Keep all four side rails raised at all times",
|
| 213 |
+
"Place a fall-risk sign on the patient's door",
|
| 214 |
+
"Educate the patient and family about fall prevention",
|
| 215 |
+
"Offer toileting every 2 hours"
|
| 216 |
+
],
|
| 217 |
+
"correct": [0, 1, 3, 4, 5],
|
| 218 |
+
"rationale": "Correct: Armband, signage, call light access, patient/family education, and scheduled toileting are all evidence-based fall prevention strategies. Incorrect: Keeping all four side rails raised constitutes a restraint and is not standard fall prevention β it may cause the patient to climb over rails, increasing injury risk. Two rails may be raised for positioning support per facility policy.",
|
| 219 |
+
"tags": ["fall-prevention", "safety", "high-risk", "SATA"]
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"id": "safety_004",
|
| 223 |
+
"category": "Safety & Infection Control",
|
| 224 |
+
"subcategory": "Airborne Precautions",
|
| 225 |
+
"nclex_framework": "Safety and Infection Control",
|
| 226 |
+
"difficulty": "intermediate",
|
| 227 |
+
"type": "mcq",
|
| 228 |
+
"stem": "A patient is admitted with suspected active pulmonary tuberculosis (TB). Which precautions should the nurse implement?",
|
| 229 |
+
"options": [
|
| 230 |
+
"Contact precautions: gown and gloves in a private room",
|
| 231 |
+
"Droplet precautions: surgical mask within 3 feet of patient",
|
| 232 |
+
"Airborne precautions: N95 respirator in a negative pressure room",
|
| 233 |
+
"Standard precautions only until diagnosis is confirmed"
|
| 234 |
+
],
|
| 235 |
+
"correct": 2,
|
| 236 |
+
"rationale": "Active pulmonary TB requires Airborne Precautions: N95 respirator (fit-tested), placement in a negative-pressure room, and door kept closed. TB is transmitted via airborne droplet nuclei <5 microns that remain suspended in air. A surgical mask does not filter particles this small. Standard precautions alone are insufficient given the high transmission risk.",
|
| 237 |
+
"tags": ["tuberculosis", "airborne-precautions", "N95", "negative-pressure"]
|
| 238 |
+
},
|
| 239 |
+
|
| 240 |
+
# =========================================================
|
| 241 |
+
# MANAGEMENT OF CARE / LEADERSHIP
|
| 242 |
+
# =========================================================
|
| 243 |
+
{
|
| 244 |
+
"id": "mgmt_001",
|
| 245 |
+
"category": "Leadership & Management",
|
| 246 |
+
"subcategory": "Delegation",
|
| 247 |
+
"nclex_framework": "Management of Care",
|
| 248 |
+
"difficulty": "intermediate",
|
| 249 |
+
"type": "mcq",
|
| 250 |
+
"stem": "A registered nurse (RN) is delegating tasks to a nursing assistant (NA). Which task is appropriate for the RN to delegate?",
|
| 251 |
+
"options": [
|
| 252 |
+
"Performing an admission assessment on a new patient",
|
| 253 |
+
"Educating a patient about a new diabetes diagnosis",
|
| 254 |
+
"Measuring and recording intake and output for a stable patient",
|
| 255 |
+
"Administering a prescribed oral pain medication"
|
| 256 |
+
],
|
| 257 |
+
"correct": 2,
|
| 258 |
+
"rationale": "Measuring and recording I&O for a stable patient is within the NA's scope of practice and is an appropriate delegation. Assessment, patient education, and medication administration are within the RN's scope and cannot be delegated to an NA. The RN must use the NCSBN delegation framework: Right Task, Right Circumstance, Right Person, Right Direction, Right Supervision.",
|
| 259 |
+
"tags": ["delegation", "NA", "scope-of-practice", "management"]
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"id": "mgmt_002",
|
| 263 |
+
"category": "Leadership & Management",
|
| 264 |
+
"subcategory": "Priority Setting",
|
| 265 |
+
"nclex_framework": "Management of Care",
|
| 266 |
+
"difficulty": "advanced",
|
| 267 |
+
"type": "mcq",
|
| 268 |
+
"stem": "A nurse is assigned four patients. Using priority principles, which patient should the nurse assess first?",
|
| 269 |
+
"options": [
|
| 270 |
+
"A patient with COPD reporting SOB rated 4/10, Oβ sat 94% on 2L NC",
|
| 271 |
+
"A post-op patient 4 hours after hip replacement requesting pain medication",
|
| 272 |
+
"A patient with heart failure whose urine output was 20 mL over the past 2 hours",
|
| 273 |
+
"A diabetic patient whose breakfast blood glucose was 210 mg/dL"
|
| 274 |
+
],
|
| 275 |
+
"correct": 2,
|
| 276 |
+
"rationale": "The patient with heart failure producing only 20 mL urine in 2 hours has oliguria (< 30 mL/hr is critical) β this may indicate worsening cardiac output, acute kidney injury, or haemodynamic compromise requiring immediate assessment. The COPD patient with 94% sat on 2L is stable but requires monitoring. The post-op patient's pain is a priority but not life-threatening. The elevated glucose of 210 mg/dL requires intervention but is not immediately life-threatening.",
|
| 277 |
+
"tags": ["priority-setting", "ABC", "Maslow", "oliguria", "heart-failure"]
|
| 278 |
+
},
|
| 279 |
+
{
|
| 280 |
+
"id": "mgmt_003",
|
| 281 |
+
"category": "Leadership & Management",
|
| 282 |
+
"subcategory": "Advocacy",
|
| 283 |
+
"nclex_framework": "Management of Care",
|
| 284 |
+
"difficulty": "intermediate",
|
| 285 |
+
"type": "mcq",
|
| 286 |
+
"stem": "A patient tells the nurse they do not understand why they are having surgery tomorrow and feel pressured to sign the consent form. What is the nurse's priority action?",
|
| 287 |
+
"options": [
|
| 288 |
+
"Explain the surgical procedure to the patient in detail",
|
| 289 |
+
"Reassure the patient that the surgeon is highly experienced",
|
| 290 |
+
"Notify the surgeon that the patient requires further explanation before signing",
|
| 291 |
+
"Witness the consent signature, as the surgeon has already explained the procedure"
|
| 292 |
+
],
|
| 293 |
+
"correct": 2,
|
| 294 |
+
"rationale": "Informed consent requires that the patient understands the procedure, risks, benefits, and alternatives β this is the surgeon's legal and ethical responsibility. The nurse's role is patient advocacy: if the patient does not understand or feels coerced, the nurse must notify the surgeon to provide further explanation before consent is obtained. Witnessing a consent that has not been properly informed is unethical and potentially illegal.",
|
| 295 |
+
"tags": ["informed-consent", "advocacy", "patient-rights", "ethics"]
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"id": "mgmt_004",
|
| 299 |
+
"category": "Leadership & Management",
|
| 300 |
+
"subcategory": "SBAR Communication",
|
| 301 |
+
"nclex_framework": "Management of Care",
|
| 302 |
+
"difficulty": "beginner",
|
| 303 |
+
"type": "mcq",
|
| 304 |
+
"stem": "A nurse uses SBAR to communicate a change in a patient's condition to the provider. Which component of SBAR includes the nurse's clinical interpretation of the problem?",
|
| 305 |
+
"options": [
|
| 306 |
+
"Situation",
|
| 307 |
+
"Background",
|
| 308 |
+
"Assessment",
|
| 309 |
+
"Recommendation"
|
| 310 |
+
],
|
| 311 |
+
"correct": 2,
|
| 312 |
+
"rationale": "Assessment in SBAR is where the nurse communicates their clinical interpretation of what is happening with the patient (e.g., 'I think the patient may be developing sepsis'). Situation describes what is happening now. Background provides relevant history. Recommendation states what the nurse believes is needed.",
|
| 313 |
+
"tags": ["SBAR", "communication", "handoff", "clinical-judgement"]
|
| 314 |
+
},
|
| 315 |
+
|
| 316 |
+
# =========================================================
|
| 317 |
+
# PHYSIOLOGICAL ADAPTATION / MED-SURG
|
| 318 |
+
# =========================================================
|
| 319 |
+
{
|
| 320 |
+
"id": "medsurg_001",
|
| 321 |
+
"category": "Medical-Surgical",
|
| 322 |
+
"subcategory": "Cardiac",
|
| 323 |
+
"nclex_framework": "Physiological Adaptation",
|
| 324 |
+
"difficulty": "advanced",
|
| 325 |
+
"type": "mcq",
|
| 326 |
+
"stem": "A nurse is caring for a patient who suddenly develops chest pain, diaphoresis, and ST-segment elevation on the monitor. After calling for help, what is the nurse's next priority action?",
|
| 327 |
+
"options": [
|
| 328 |
+
"Administer oxygen at 4L via nasal cannula",
|
| 329 |
+
"Obtain a 12-lead ECG",
|
| 330 |
+
"Start a second IV line",
|
| 331 |
+
"Prepare the patient for immediate coronary angiography"
|
| 332 |
+
],
|
| 333 |
+
"correct": 1,
|
| 334 |
+
"rationale": "Obtaining a 12-lead ECG is the priority β it provides definitive data to confirm STEMI, guide treatment decisions, and establish a baseline time (door-to-balloon time targets <90 minutes). Oxygen is only indicated if SpOβ < 90% β routine high-flow oxygen in STEMI can be harmful. IV access and cath lab preparation are important but follow ECG confirmation per the ACLS STEMI algorithm.",
|
| 335 |
+
"tags": ["STEMI", "chest-pain", "ECG", "cardiac-emergency", "ACLS"]
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"id": "medsurg_002",
|
| 339 |
+
"category": "Medical-Surgical",
|
| 340 |
+
"subcategory": "Respiratory",
|
| 341 |
+
"nclex_framework": "Physiological Adaptation",
|
| 342 |
+
"difficulty": "intermediate",
|
| 343 |
+
"type": "mcq",
|
| 344 |
+
"stem": "A patient with COPD is receiving 4L Oβ via nasal cannula. The nurse notes the patient is increasingly somnolent with a respiratory rate of 8/min. What is the most likely cause?",
|
| 345 |
+
"options": [
|
| 346 |
+
"Oxygen toxicity causing lung damage",
|
| 347 |
+
"Hypercapnia from suppressed hypoxic drive",
|
| 348 |
+
"COβ narcosis from excessive oxygen",
|
| 349 |
+
"Both B and C are correct"
|
| 350 |
+
],
|
| 351 |
+
"correct": 3,
|
| 352 |
+
"rationale": "In COPD patients who are chronic COβ retainers, the primary respiratory drive shifts to hypoxia (hypoxic drive). High-flow oxygen removes this drive, leading to respiratory depression, COβ retention (hypercapnia), and eventually COβ narcosis β a state of somnolence and CNS depression. Both B and C describe the same pathophysiological process. Target SpOβ in COPD is typically 88β92%.",
|
| 353 |
+
"tags": ["COPD", "hypoxic-drive", "O2-therapy", "hypercapnia", "CO2-narcosis"]
|
| 354 |
+
},
|
| 355 |
+
{
|
| 356 |
+
"id": "medsurg_003",
|
| 357 |
+
"category": "Medical-Surgical",
|
| 358 |
+
"subcategory": "Fluid & Electrolytes",
|
| 359 |
+
"nclex_framework": "Physiological Adaptation",
|
| 360 |
+
"difficulty": "intermediate",
|
| 361 |
+
"type": "mcq",
|
| 362 |
+
"stem": "A patient's serum sodium is 122 mEq/L. The nurse anticipates which clinical manifestation?",
|
| 363 |
+
"options": [
|
| 364 |
+
"Dry mucous membranes and intense thirst",
|
| 365 |
+
"Peaked T waves on ECG",
|
| 366 |
+
"Confusion and seizure risk",
|
| 367 |
+
"Kussmaul breathing"
|
| 368 |
+
],
|
| 369 |
+
"correct": 2,
|
| 370 |
+
"rationale": "Severe hyponatraemia (Na < 125 mEq/L) causes cerebral oedema as water shifts into brain cells, leading to neurological manifestations: confusion, headache, lethargy, and seizure risk. Dry mucous membranes/thirst indicate hypernatraemia. Peaked T waves indicate hyperkalaemia. Kussmaul breathing indicates metabolic acidosis.",
|
| 371 |
+
"tags": ["hyponatraemia", "electrolytes", "neuro-manifestations", "sodium"]
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"id": "medsurg_004",
|
| 375 |
+
"category": "Medical-Surgical",
|
| 376 |
+
"subcategory": "Post-operative Care",
|
| 377 |
+
"nclex_framework": "Reduction of Risk Potential",
|
| 378 |
+
"difficulty": "intermediate",
|
| 379 |
+
"type": "sata",
|
| 380 |
+
"stem": "A nurse is caring for a patient 6 hours post-abdominal surgery. Which findings require immediate intervention? Select all that apply.",
|
| 381 |
+
"options": [
|
| 382 |
+
"Blood pressure 88/54 mmHg, HR 118 bpm",
|
| 383 |
+
"Urine output 25 mL over the past hour",
|
| 384 |
+
"Patient rates pain as 6/10",
|
| 385 |
+
"Temperature 38.9Β°C (102Β°F)",
|
| 386 |
+
"Wound dressing with a 2 cm bloodstain unchanged from 1 hour ago",
|
| 387 |
+
"Absent bowel sounds"
|
| 388 |
+
],
|
| 389 |
+
"correct": [0, 1, 3],
|
| 390 |
+
"rationale": "Immediate: BP 88/54 with HR 118 suggests haemodynamic instability/hypovolaemic shock. Urine output of 25 mL/hr indicates oliguria (<30 mL/hr) suggesting inadequate perfusion. Temp 38.9Β°C suggests early infection or systemic response. Not immediate: Pain 6/10 needs management but is not immediately life-threatening. A small stable bloodstain indicates the bleed is not active/expanding. Absent bowel sounds are expected in the first 24β72 hours post-abdominal surgery (post-op ileus).",
|
| 391 |
+
"tags": ["post-operative", "haemodynamic", "oliguria", "SATA", "shock"]
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"id": "medsurg_005",
|
| 395 |
+
"category": "Medical-Surgical",
|
| 396 |
+
"subcategory": "Neurological",
|
| 397 |
+
"nclex_framework": "Physiological Adaptation",
|
| 398 |
+
"difficulty": "advanced",
|
| 399 |
+
"type": "mcq",
|
| 400 |
+
"stem": "A patient with a closed head injury has a GCS score that has dropped from 14 to 9 over 2 hours. The nurse notes Cushing's triad: hypertension, bradycardia, and irregular respirations. What does this indicate?",
|
| 401 |
+
"options": [
|
| 402 |
+
"Neurogenic shock developing",
|
| 403 |
+
"Increased intracranial pressure with brain herniation",
|
| 404 |
+
"Expected improvement in level of consciousness",
|
| 405 |
+
"Autonomic dysreflexia from a spinal injury"
|
| 406 |
+
],
|
| 407 |
+
"correct": 1,
|
| 408 |
+
"rationale": "Cushing's triad (hypertension, bradycardia, irregular/Cheyne-Stokes respirations) is a late, ominous sign of severely increased intracranial pressure (ICP) and impending brain herniation. The falling GCS confirms deteriorating neurological status. This is a neurological emergency requiring immediate provider notification and intervention (head of bed 30Β°, hyperventilation, mannitol, possible surgical decompression). Neurogenic shock causes hypotension, not hypertension.",
|
| 409 |
+
"tags": ["ICP", "Cushing-triad", "brain-herniation", "GCS", "neurological-emergency"]
|
| 410 |
+
},
|
| 411 |
+
|
| 412 |
+
# =========================================================
|
| 413 |
+
# MENTAL HEALTH
|
| 414 |
+
# =========================================================
|
| 415 |
+
{
|
| 416 |
+
"id": "mh_001",
|
| 417 |
+
"category": "Mental Health",
|
| 418 |
+
"subcategory": "Therapeutic Communication",
|
| 419 |
+
"nclex_framework": "Psychosocial Integrity",
|
| 420 |
+
"difficulty": "beginner",
|
| 421 |
+
"type": "mcq",
|
| 422 |
+
"stem": "A patient with depression tells the nurse, 'There's no point in anything anymore. I don't see why I bother.' Which response by the nurse is most therapeutic?",
|
| 423 |
+
"options": [
|
| 424 |
+
"'I'm sure things will get better soon.'",
|
| 425 |
+
"'You sound like you're feeling hopeless. Can you tell me more about what you mean?'",
|
| 426 |
+
"'Everyone feels that way sometimes β you just need to stay positive.'",
|
| 427 |
+
"'Have you thought about joining our group therapy sessions?'"
|
| 428 |
+
],
|
| 429 |
+
"correct": 1,
|
| 430 |
+
"rationale": "Reflecting the patient's feelings and using an open-ended question demonstrates active listening, validates the patient's experience, and invites further communication. This therapeutic response may also allow the nurse to assess for suicidal ideation. The other responses use false reassurance ('things will get better'), minimisation ('everyone feels that way'), or redirect conversation before the patient feels heard β all non-therapeutic techniques.",
|
| 431 |
+
"tags": ["therapeutic-communication", "depression", "open-ended", "active-listening"]
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"id": "mh_002",
|
| 435 |
+
"category": "Mental Health",
|
| 436 |
+
"subcategory": "Suicide Risk",
|
| 437 |
+
"nclex_framework": "Psychosocial Integrity",
|
| 438 |
+
"difficulty": "advanced",
|
| 439 |
+
"type": "mcq",
|
| 440 |
+
"stem": "A patient tells the nurse, 'I've decided to give all my belongings away. I feel very peaceful now.' The nurse recognises this as which warning sign?",
|
| 441 |
+
"options": [
|
| 442 |
+
"Improved coping and acceptance of illness",
|
| 443 |
+
"Possible completion of a suicide plan and calm before lethal action",
|
| 444 |
+
"Spiritual growth and acceptance of mortality",
|
| 445 |
+
"A positive sign indicating therapeutic progress"
|
| 446 |
+
],
|
| 447 |
+
"correct": 1,
|
| 448 |
+
"rationale": "Giving away possessions and a sudden sense of calm or peace in a previously depressed/suicidal patient is a critical warning sign β it may indicate the patient has made a decision to act and has resolved the ambivalence around suicide. The calm may reflect relief that a plan has been made. This requires immediate safety assessment (ask directly about suicidal ideation/plan/intent/means) and escalation. This is NOT a sign of improvement.",
|
| 449 |
+
"tags": ["suicide-risk", "warning-signs", "safety-assessment", "crisis"]
|
| 450 |
+
},
|
| 451 |
+
{
|
| 452 |
+
"id": "mh_003",
|
| 453 |
+
"category": "Mental Health",
|
| 454 |
+
"subcategory": "Antipsychotic Medications",
|
| 455 |
+
"nclex_framework": "Pharmacological and Parenteral Therapies",
|
| 456 |
+
"difficulty": "advanced",
|
| 457 |
+
"type": "mcq",
|
| 458 |
+
"stem": "A patient on haloperidol (Haldol) develops sudden severe muscle rigidity, high fever (40.1Β°C), diaphoresis, and altered mental status. The nurse suspects which complication?",
|
| 459 |
+
"options": [
|
| 460 |
+
"Tardive dyskinesia",
|
| 461 |
+
"Serotonin syndrome",
|
| 462 |
+
"Neuroleptic malignant syndrome (NMS)",
|
| 463 |
+
"Acute dystonia"
|
| 464 |
+
],
|
| 465 |
+
"correct": 2,
|
| 466 |
+
"rationale": "Neuroleptic Malignant Syndrome (NMS) is a rare but life-threatening reaction to antipsychotics. The classic tetrad is: hyperthermia, severe muscle rigidity, autonomic instability (diaphoresis, tachycardia), and altered consciousness. Treatment: stop the antipsychotic immediately, supportive care, dantrolene. Tardive dyskinesia involves involuntary movements after long-term use. Serotonin syndrome involves serotonergic agents. Acute dystonia involves muscle spasms, not systemic crisis.",
|
| 467 |
+
"tags": ["NMS", "haloperidol", "antipsychotic", "emergency", "hyperthermia"]
|
| 468 |
+
},
|
| 469 |
+
|
| 470 |
+
# =========================================================
|
| 471 |
+
# MATERNAL / NEWBORN
|
| 472 |
+
# =========================================================
|
| 473 |
+
{
|
| 474 |
+
"id": "ob_001",
|
| 475 |
+
"category": "Maternal / Newborn",
|
| 476 |
+
"subcategory": "Labour & Delivery",
|
| 477 |
+
"nclex_framework": "Physiological Adaptation",
|
| 478 |
+
"difficulty": "advanced",
|
| 479 |
+
"type": "mcq",
|
| 480 |
+
"stem": "A nurse monitoring a patient in labour notes a late deceleration pattern on the fetal monitor. What is the nurse's immediate action?",
|
| 481 |
+
"options": [
|
| 482 |
+
"Increase the oxytocin infusion rate",
|
| 483 |
+
"Reposition the patient to left lateral position and apply oxygen",
|
| 484 |
+
"Prepare for immediate caesarean section",
|
| 485 |
+
"Document the finding and continue monitoring"
|
| 486 |
+
],
|
| 487 |
+
"correct": 1,
|
| 488 |
+
"rationale": "Late decelerations indicate uteroplacental insufficiency and potential fetal hypoxia. Immediate nursing actions: reposition to left lateral (relieve aorto-caval compression, improving placental perfusion), apply Oβ via face mask (8β10 L), stop oxytocin if infusing, increase IV fluids, and notify the provider. Increasing oxytocin would worsen contractions and deepen the deceleration. C-section may be needed but is a provider decision, not the nurse's first action.",
|
| 489 |
+
"tags": ["fetal-monitoring", "late-deceleration", "uteroplacental", "labour", "priority"]
|
| 490 |
+
},
|
| 491 |
+
{
|
| 492 |
+
"id": "ob_002",
|
| 493 |
+
"category": "Maternal / Newborn",
|
| 494 |
+
"subcategory": "Post-partum",
|
| 495 |
+
"nclex_framework": "Reduction of Risk Potential",
|
| 496 |
+
"difficulty": "intermediate",
|
| 497 |
+
"type": "mcq",
|
| 498 |
+
"stem": "A nurse assesses a patient 1 hour post-vaginal delivery. The fundus is displaced to the right of the midline and the patient has saturated a perineal pad in 30 minutes. What is the priority intervention?",
|
| 499 |
+
"options": [
|
| 500 |
+
"Administer oxytocin as prescribed",
|
| 501 |
+
"Assist the patient to urinate or insert a urinary catheter",
|
| 502 |
+
"Perform uterine fundal massage",
|
| 503 |
+
"Notify the provider of haemorrhage"
|
| 504 |
+
],
|
| 505 |
+
"correct": 1,
|
| 506 |
+
"rationale": "A fundus deviated to the right and a full bladder (the most common cause of uterine displacement) inhibits uterine contraction and causes post-partum haemorrhage. The first intervention is to empty the bladder β this allows the uterus to contract and descend to the midline. Fundal massage is appropriate for a boggy uterus at midline; massaging a displaced uterus without first emptying the bladder is ineffective. Oxytocin and provider notification follow if haemorrhage continues after bladder emptying.",
|
| 507 |
+
"tags": ["post-partum-haemorrhage", "uterine-atony", "bladder-displacement", "fundus"]
|
| 508 |
+
},
|
| 509 |
+
|
| 510 |
+
# =========================================================
|
| 511 |
+
# PAEDIATRICS
|
| 512 |
+
# =========================================================
|
| 513 |
+
{
|
| 514 |
+
"id": "peds_001",
|
| 515 |
+
"category": "Paediatrics",
|
| 516 |
+
"subcategory": "Respiratory",
|
| 517 |
+
"nclex_framework": "Physiological Adaptation",
|
| 518 |
+
"difficulty": "advanced",
|
| 519 |
+
"type": "mcq",
|
| 520 |
+
"stem": "A 2-year-old presents with sudden onset of high-pitched inspiratory stridor, drooling, tripod positioning, and refusal to lie down. The child is afebrile and has no cough. What condition does the nurse suspect and what is the priority action?",
|
| 521 |
+
"options": [
|
| 522 |
+
"Croup β prepare a cool mist tent and administer nebulised racemic epinephrine",
|
| 523 |
+
"Epiglottitis β do not examine the throat; notify the provider and keep child calm",
|
| 524 |
+
"Asthma β administer albuterol nebulisation immediately",
|
| 525 |
+
"Foreign body aspiration β prepare for emergency Heimlich manoeuvre"
|
| 526 |
+
],
|
| 527 |
+
"correct": 1,
|
| 528 |
+
"rationale": "The presentation β sudden stridor, drooling, tripod positioning, refusal to lie flat, and absence of cough β is classic for epiglottitis, a life-threatening emergency. NEVER attempt to visualise the throat (can trigger complete obstruction). Keep the child calm and in the position of comfort (sitting upright). Notify the provider/anaesthesia immediately β the airway may need emergency intubation. Croup presents with a barking cough, gradual onset, and low-grade fever. Epiglottitis is now less common due to Hib vaccine.",
|
| 529 |
+
"tags": ["epiglottitis", "stridor", "paediatric-emergency", "airway", "priority"]
|
| 530 |
+
},
|
| 531 |
+
{
|
| 532 |
+
"id": "peds_002",
|
| 533 |
+
"category": "Paediatrics",
|
| 534 |
+
"subcategory": "Growth & Development",
|
| 535 |
+
"nclex_framework": "Health Promotion and Maintenance",
|
| 536 |
+
"difficulty": "beginner",
|
| 537 |
+
"type": "mcq",
|
| 538 |
+
"stem": "A parent asks the nurse when their child should walk independently. Which milestone response is developmentally appropriate?",
|
| 539 |
+
"options": [
|
| 540 |
+
"6β8 months",
|
| 541 |
+
"9β11 months",
|
| 542 |
+
"12β15 months",
|
| 543 |
+
"18β24 months"
|
| 544 |
+
],
|
| 545 |
+
"correct": 2,
|
| 546 |
+
"rationale": "Independent walking typically occurs between 12β15 months of age. The range is 9β15 months (some sources extend to 18 months). Walking before 9 months is unusual. Not walking by 18 months warrants further developmental evaluation. Understanding developmental milestones helps the nurse provide accurate anticipatory guidance and identify delays early.",
|
| 547 |
+
"tags": ["developmental-milestones", "walking", "paediatric", "health-promotion"]
|
| 548 |
+
},
|
| 549 |
+
|
| 550 |
+
# =========================================================
|
| 551 |
+
# HEALTH PROMOTION / FUNDAMENTALS
|
| 552 |
+
# =========================================================
|
| 553 |
+
{
|
| 554 |
+
"id": "fund_001",
|
| 555 |
+
"category": "Fundamentals",
|
| 556 |
+
"subcategory": "Wound Care",
|
| 557 |
+
"nclex_framework": "Basic Care and Comfort",
|
| 558 |
+
"difficulty": "intermediate",
|
| 559 |
+
"type": "mcq",
|
| 560 |
+
"stem": "A nurse is assessing a patient's pressure injury and notes a shallow open ulcer with a red-pink wound bed and no slough. There is no tunnelling or undermining. How should this wound be staged?",
|
| 561 |
+
"options": [
|
| 562 |
+
"Stage I",
|
| 563 |
+
"Stage II",
|
| 564 |
+
"Stage III",
|
| 565 |
+
"Stage IV"
|
| 566 |
+
],
|
| 567 |
+
"correct": 1,
|
| 568 |
+
"rationale": "A Stage II pressure injury presents as a shallow open ulcer with a red-pink wound bed, without slough or eschar β the dermis is partially exposed. Stage I is intact skin with non-blanchable redness. Stage III involves full-thickness tissue loss (subcutaneous tissue visible). Stage IV involves exposed muscle, tendon, or bone. Accurate staging guides dressing selection and care planning.",
|
| 569 |
+
"tags": ["pressure-injury", "wound-staging", "NPUAP", "wound-care"]
|
| 570 |
+
},
|
| 571 |
+
{
|
| 572 |
+
"id": "fund_002",
|
| 573 |
+
"category": "Fundamentals",
|
| 574 |
+
"subcategory": "Vital Signs",
|
| 575 |
+
"nclex_framework": "Basic Care and Comfort",
|
| 576 |
+
"difficulty": "beginner",
|
| 577 |
+
"type": "mcq",
|
| 578 |
+
"stem": "A nurse obtains the following vital signs on an adult patient: BP 94/60, HR 122, RR 24, Temp 38.9Β°C, SpOβ 96%. Which finding requires the nurse's most immediate attention?",
|
| 579 |
+
"options": [
|
| 580 |
+
"Temperature 38.9Β°C",
|
| 581 |
+
"SpOβ 96%",
|
| 582 |
+
"BP 94/60 with HR 122",
|
| 583 |
+
"Respiratory rate 24"
|
| 584 |
+
],
|
| 585 |
+
"correct": 2,
|
| 586 |
+
"rationale": "BP 94/60 with compensatory tachycardia (HR 122) represents haemodynamic instability β potential shock. This combination is the most immediately life-threatening finding as it indicates inadequate tissue perfusion. Using the ABCs/Maslow framework: circulation is compromised. Temperature of 38.9Β°C combined with tachycardia and hypotension suggests sepsis β a medical emergency. SpOβ 96% is acceptable. RR 24 is elevated but manageable.",
|
| 587 |
+
"tags": ["vital-signs", "hypotension", "shock", "sepsis", "priority"]
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"id": "fund_003",
|
| 591 |
+
"category": "Fundamentals",
|
| 592 |
+
"subcategory": "NG Tube",
|
| 593 |
+
"nclex_framework": "Basic Care and Comfort",
|
| 594 |
+
"difficulty": "intermediate",
|
| 595 |
+
"type": "mcq",
|
| 596 |
+
"stem": "A nurse is verifying placement of a nasogastric tube before administering a feeding. Which method is most reliable for confirming tube placement?",
|
| 597 |
+
"options": [
|
| 598 |
+
"Auscultating for a 'whoosh' sound when injecting 20 mL of air",
|
| 599 |
+
"Observing for bubbling when the tube end is submerged in water",
|
| 600 |
+
"Measuring the pH of aspirated fluid (pH β€ 5)",
|
| 601 |
+
"Confirming placement by X-ray"
|
| 602 |
+
],
|
| 603 |
+
"correct": 3,
|
| 604 |
+
"rationale": "X-ray is the gold standard for confirming NG tube placement, especially for initial placement and when placement is uncertain. pH testing of aspirate (pH β€ 5.5 suggests gastric placement) is acceptable for ongoing verification per some guidelines. The auscultation method ('whoosh' test) and water submersion are unreliable and no longer recommended by NPSG. Only X-ray provides definitive confirmation of tube position.",
|
| 605 |
+
"tags": ["NG-tube", "tube-placement", "enteral-feeding", "patient-safety"]
|
| 606 |
+
},
|
| 607 |
+
{
|
| 608 |
+
"id": "fund_004",
|
| 609 |
+
"category": "Fundamentals",
|
| 610 |
+
"subcategory": "Hand Hygiene",
|
| 611 |
+
"nclex_framework": "Safety and Infection Control",
|
| 612 |
+
"difficulty": "beginner",
|
| 613 |
+
"type": "sata",
|
| 614 |
+
"stem": "According to the WHO 'Five Moments for Hand Hygiene', the nurse must perform hand hygiene in which situations? Select all that apply.",
|
| 615 |
+
"options": [
|
| 616 |
+
"Before touching a patient",
|
| 617 |
+
"Before a clean or aseptic procedure",
|
| 618 |
+
"After body fluid exposure risk",
|
| 619 |
+
"After touching a patient",
|
| 620 |
+
"After touching patient surroundings",
|
| 621 |
+
"Before entering the hospital building"
|
| 622 |
+
],
|
| 623 |
+
"correct": [0, 1, 2, 3, 4],
|
| 624 |
+
"rationale": "The WHO Five Moments for Hand Hygiene are: (1) Before patient contact, (2) Before a clean/aseptic procedure, (3) After body fluid exposure risk, (4) After patient contact, (5) After contact with patient surroundings. Entering the building is not one of the five moments. Hand hygiene at these five points breaks the chain of infection transmission.",
|
| 625 |
+
"tags": ["hand-hygiene", "WHO-5-moments", "infection-control", "SATA"]
|
| 626 |
+
},
|
| 627 |
+
|
| 628 |
+
# =========================================================
|
| 629 |
+
# REDUCTION OF RISK POTENTIAL
|
| 630 |
+
# =========================================================
|
| 631 |
+
{
|
| 632 |
+
"id": "risk_001",
|
| 633 |
+
"category": "Reduction of Risk Potential",
|
| 634 |
+
"subcategory": "Lab Values",
|
| 635 |
+
"nclex_framework": "Reduction of Risk Potential",
|
| 636 |
+
"difficulty": "intermediate",
|
| 637 |
+
"type": "mcq",
|
| 638 |
+
"stem": "A patient receiving heparin infusion has an aPTT result of 180 seconds (therapeutic range 60β100 seconds). What is the nurse's priority action?",
|
| 639 |
+
"options": [
|
| 640 |
+
"Continue the infusion and recheck in 6 hours",
|
| 641 |
+
"Reduce the infusion rate by 25% per protocol",
|
| 642 |
+
"Stop the heparin infusion and notify the provider",
|
| 643 |
+
"Administer protamine sulphate immediately"
|
| 644 |
+
],
|
| 645 |
+
"correct": 2,
|
| 646 |
+
"rationale": "An aPTT of 180 seconds is nearly double the upper therapeutic limit (100 seconds), indicating supratherapeutic anticoagulation and significantly elevated bleeding risk. The nurse should stop the infusion and immediately notify the provider. Protamine sulphate (heparin reversal agent) is administered by provider order if active bleeding is present or clinically indicated β the nurse does not administer it without an order. Protocol-based rate reduction applies within therapeutic ranges, not in supratherapeutic situations.",
|
| 647 |
+
"tags": ["heparin", "aPTT", "supratherapeutic", "anticoagulant", "bleeding-risk"]
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"id": "risk_002",
|
| 651 |
+
"category": "Reduction of Risk Potential",
|
| 652 |
+
"subcategory": "DVT Prevention",
|
| 653 |
+
"nclex_framework": "Reduction of Risk Potential",
|
| 654 |
+
"difficulty": "beginner",
|
| 655 |
+
"type": "sata",
|
| 656 |
+
"stem": "A nurse is planning care for a patient recovering from total knee replacement surgery. Which interventions reduce the risk of deep vein thrombosis (DVT)? Select all that apply.",
|
| 657 |
+
"options": [
|
| 658 |
+
"Apply sequential compression devices (SCDs) when patient is in bed",
|
| 659 |
+
"Encourage early ambulation as prescribed",
|
| 660 |
+
"Administer prescribed anticoagulant therapy",
|
| 661 |
+
"Keep the patient on strict bed rest for 72 hours",
|
| 662 |
+
"Encourage adequate hydration",
|
| 663 |
+
"Perform calf massage to promote circulation"
|
| 664 |
+
],
|
| 665 |
+
"correct": [0, 1, 2, 4],
|
| 666 |
+
"rationale": "Correct: SCDs promote venous return, early ambulation prevents venous stasis, anticoagulants (e.g., enoxaparin) prevent clot formation, and hydration prevents haemoconcentration. Incorrect: Strict bed rest promotes venous stasis and increases DVT risk. Calf massage is CONTRAINDICATED if DVT is suspected β it can dislodge a clot, causing pulmonary embolism.",
|
| 667 |
+
"tags": ["DVT", "post-operative", "SCDs", "anticoagulation", "SATA"]
|
| 668 |
+
},
|
| 669 |
+
]
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
def get_all() -> list[dict]:
|
| 673 |
+
return QUESTIONS
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def get_by_category(category: str) -> list[dict]:
|
| 677 |
+
return [q for q in QUESTIONS if q["category"] == category]
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
def get_by_difficulty(difficulty: str) -> list[dict]:
|
| 681 |
+
return [q for q in QUESTIONS if q["difficulty"] == difficulty]
|
| 682 |
+
|
| 683 |
+
|
| 684 |
+
def get_by_type(qtype: str) -> list[dict]:
|
| 685 |
+
return [q for q in QUESTIONS if q["type"] == qtype]
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
def get_categories() -> list[str]:
|
| 689 |
+
return sorted(set(q["category"] for q in QUESTIONS))
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
def filter_questions(
|
| 693 |
+
categories: list[str] | None = None,
|
| 694 |
+
difficulty: str | None = None,
|
| 695 |
+
qtype: str | None = None,
|
| 696 |
+
) -> list[dict]:
|
| 697 |
+
qs = QUESTIONS
|
| 698 |
+
if categories:
|
| 699 |
+
qs = [q for q in qs if q["category"] in categories]
|
| 700 |
+
if difficulty and difficulty != "Any":
|
| 701 |
+
qs = [q for q in qs if q["difficulty"] == difficulty]
|
| 702 |
+
if qtype and qtype != "Any":
|
| 703 |
+
qs = [q for q in qs if q["type"] == qtype]
|
| 704 |
+
return qs
|
questions/calculations.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Dynamic dosage calculation question generator.
|
| 3 |
+
Generates unlimited unique, clinically accurate dosage math problems.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import random
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# ---------------------------------------------------------------------------
|
| 10 |
+
# Question templates β (stem_template, unit, solve_fn, distractor_fn)
|
| 11 |
+
# ---------------------------------------------------------------------------
|
| 12 |
+
|
| 13 |
+
def _round2(n: float) -> float:
|
| 14 |
+
return round(n, 2)
|
| 15 |
+
|
| 16 |
+
def _round1(n: float) -> float:
|
| 17 |
+
return round(n, 1)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def gen_oral_tablets() -> dict:
|
| 21 |
+
"""Dose on hand / tablet dose β tablets to give."""
|
| 22 |
+
drug = random.choice(["metoprolol", "lisinopril", "metformin", "atorvastatin", "furosemide"])
|
| 23 |
+
tablet_mg = random.choice([25, 50, 100, 125, 250, 500])
|
| 24 |
+
tablets = random.choice([0.5, 1, 1.5, 2, 2.5, 3])
|
| 25 |
+
ordered = _round1(tablet_mg * tablets)
|
| 26 |
+
|
| 27 |
+
correct = tablets
|
| 28 |
+
distractors = [
|
| 29 |
+
_round2(tablet_mg / ordered) if ordered != 0 else 2,
|
| 30 |
+
_round2(ordered / tablet_mg * 2),
|
| 31 |
+
_round2(ordered * tablet_mg / 100),
|
| 32 |
+
]
|
| 33 |
+
distractors = [d for d in distractors if d != correct][:3]
|
| 34 |
+
|
| 35 |
+
options, correct_idx = _build_options(correct, distractors, unit="tablet(s)")
|
| 36 |
+
|
| 37 |
+
return {
|
| 38 |
+
"id": f"calc_tablet_{random.randint(1000,9999)}",
|
| 39 |
+
"category": "Dosage Calculations",
|
| 40 |
+
"subcategory": "Oral Medications",
|
| 41 |
+
"difficulty": "beginner",
|
| 42 |
+
"type": "calculation",
|
| 43 |
+
"stem": (
|
| 44 |
+
f"A provider orders {drug} {ordered} mg PO daily. "
|
| 45 |
+
f"The medication is available as {tablet_mg} mg tablets. "
|
| 46 |
+
f"How many tablets should the nurse administer?"
|
| 47 |
+
),
|
| 48 |
+
"options": options,
|
| 49 |
+
"correct": correct_idx,
|
| 50 |
+
"rationale": (
|
| 51 |
+
f"Formula: (Ordered dose Γ· Dose on hand) Γ Quantity\n"
|
| 52 |
+
f"= ({ordered} mg Γ· {tablet_mg} mg) Γ 1 tablet\n"
|
| 53 |
+
f"= **{correct} tablet(s)**"
|
| 54 |
+
),
|
| 55 |
+
"tags": ["oral", "tablets", "calculation"],
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def gen_weight_based_iv() -> dict:
|
| 60 |
+
"""Weight-based IV infusion: mg/kg β mL/hr."""
|
| 61 |
+
drug = random.choice(["dopamine", "norepinephrine", "heparin", "amiodarone"])
|
| 62 |
+
weight = random.choice(range(50, 101, 5)) # 50β100 kg
|
| 63 |
+
dose_mcg_kg_min = random.choice([2, 3, 5, 7, 10]) # mcg/kg/min
|
| 64 |
+
conc_mg_per_ml = random.choice([1.6, 3.2, 4.0]) # mg/mL standard concentrations
|
| 65 |
+
|
| 66 |
+
# Convert: (mcg/kg/min Γ kg Γ 60 min) / (conc in mcg/mL) = mL/hr
|
| 67 |
+
conc_mcg_per_ml = conc_mg_per_ml * 1000
|
| 68 |
+
rate = _round1((dose_mcg_kg_min * weight * 60) / conc_mcg_per_ml)
|
| 69 |
+
|
| 70 |
+
distractors = [
|
| 71 |
+
_round1(rate * 2),
|
| 72 |
+
_round1(rate / 2),
|
| 73 |
+
_round1(dose_mcg_kg_min * weight * conc_mg_per_ml / 60),
|
| 74 |
+
]
|
| 75 |
+
distractors = [d for d in distractors if d != rate][:3]
|
| 76 |
+
options, correct_idx = _build_options(rate, distractors, unit="mL/hr")
|
| 77 |
+
|
| 78 |
+
return {
|
| 79 |
+
"id": f"calc_iv_{random.randint(1000,9999)}",
|
| 80 |
+
"category": "Dosage Calculations",
|
| 81 |
+
"subcategory": "IV Infusions",
|
| 82 |
+
"difficulty": "advanced",
|
| 83 |
+
"type": "calculation",
|
| 84 |
+
"stem": (
|
| 85 |
+
f"A patient weighing {weight} kg requires {drug} at "
|
| 86 |
+
f"{dose_mcg_kg_min} mcg/kg/min. The available solution is "
|
| 87 |
+
f"{conc_mg_per_ml} mg/mL. At what rate (mL/hr) should the nurse set the infusion pump?"
|
| 88 |
+
),
|
| 89 |
+
"options": options,
|
| 90 |
+
"correct": correct_idx,
|
| 91 |
+
"rationale": (
|
| 92 |
+
f"Step 1 β Convert concentration: {conc_mg_per_ml} mg/mL Γ 1000 = {conc_mcg_per_ml} mcg/mL\n"
|
| 93 |
+
f"Step 2 β Calculate dose/hr: {dose_mcg_kg_min} mcg/kg/min Γ {weight} kg Γ 60 min = "
|
| 94 |
+
f"{dose_mcg_kg_min * weight * 60} mcg/hr\n"
|
| 95 |
+
f"Step 3 β Calculate rate: {dose_mcg_kg_min * weight * 60} mcg/hr Γ· {conc_mcg_per_ml} mcg/mL\n"
|
| 96 |
+
f"= **{rate} mL/hr**"
|
| 97 |
+
),
|
| 98 |
+
"tags": ["IV", "weight-based", "infusion-rate", "calculation"],
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def gen_iv_drip_rate() -> dict:
|
| 103 |
+
"""Simple IV drip: volume / time β mL/hr."""
|
| 104 |
+
drug = random.choice(["Normal Saline", "Lactated Ringer's", "D5W", "0.45% NaCl"])
|
| 105 |
+
volume = random.choice([250, 500, 1000])
|
| 106 |
+
hours = random.choice([4, 6, 8, 10, 12])
|
| 107 |
+
rate = _round1(volume / hours)
|
| 108 |
+
|
| 109 |
+
distractors = [
|
| 110 |
+
_round1(rate * 1.5),
|
| 111 |
+
_round1(rate * 0.5),
|
| 112 |
+
_round1(volume * hours / 1000),
|
| 113 |
+
]
|
| 114 |
+
distractors = [d for d in distractors if d != rate][:3]
|
| 115 |
+
options, correct_idx = _build_options(rate, distractors, unit="mL/hr")
|
| 116 |
+
|
| 117 |
+
return {
|
| 118 |
+
"id": f"calc_drip_{random.randint(1000,9999)}",
|
| 119 |
+
"category": "Dosage Calculations",
|
| 120 |
+
"subcategory": "IV Rate",
|
| 121 |
+
"difficulty": "beginner",
|
| 122 |
+
"type": "calculation",
|
| 123 |
+
"stem": (
|
| 124 |
+
f"A provider orders {volume} mL of {drug} to infuse over {hours} hours. "
|
| 125 |
+
f"At what rate should the nurse set the infusion pump?"
|
| 126 |
+
),
|
| 127 |
+
"options": options,
|
| 128 |
+
"correct": correct_idx,
|
| 129 |
+
"rationale": (
|
| 130 |
+
f"Formula: Volume (mL) Γ· Time (hr)\n"
|
| 131 |
+
f"= {volume} mL Γ· {hours} hr\n"
|
| 132 |
+
f"= **{rate} mL/hr**"
|
| 133 |
+
),
|
| 134 |
+
"tags": ["IV-rate", "infusion", "calculation"],
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def gen_paediatric_weight() -> dict:
|
| 139 |
+
"""Paediatric weight-based dose β mL to give from available solution."""
|
| 140 |
+
drug = random.choice(["amoxicillin", "ibuprofen", "paracetamol", "azithromycin"])
|
| 141 |
+
weight = round(random.uniform(10, 30), 1)
|
| 142 |
+
dose_mg_kg = random.choice([10, 15, 20, 25])
|
| 143 |
+
conc_mg_ml = random.choice([50, 100, 125, 200, 250])
|
| 144 |
+
|
| 145 |
+
ordered_mg = _round1(dose_mg_kg * weight)
|
| 146 |
+
volume_ml = _round2(ordered_mg / conc_mg_ml)
|
| 147 |
+
|
| 148 |
+
distractors = [
|
| 149 |
+
_round2(volume_ml * 2),
|
| 150 |
+
_round2(conc_mg_ml / ordered_mg),
|
| 151 |
+
_round2(ordered_mg * conc_mg_ml / 1000),
|
| 152 |
+
]
|
| 153 |
+
distractors = [d for d in distractors if d != volume_ml][:3]
|
| 154 |
+
options, correct_idx = _build_options(volume_ml, distractors, unit="mL")
|
| 155 |
+
|
| 156 |
+
return {
|
| 157 |
+
"id": f"calc_peds_{random.randint(1000,9999)}",
|
| 158 |
+
"category": "Dosage Calculations",
|
| 159 |
+
"subcategory": "Paediatric",
|
| 160 |
+
"difficulty": "intermediate",
|
| 161 |
+
"type": "calculation",
|
| 162 |
+
"stem": (
|
| 163 |
+
f"A child weighs {weight} kg. The provider orders {drug} "
|
| 164 |
+
f"{dose_mg_kg} mg/kg PO. The suspension available is {conc_mg_ml} mg/5 mL. "
|
| 165 |
+
f"How many mL should the nurse administer?"
|
| 166 |
+
),
|
| 167 |
+
"options": options,
|
| 168 |
+
"correct": correct_idx,
|
| 169 |
+
"rationale": (
|
| 170 |
+
f"Step 1 β Calculate ordered dose: {dose_mg_kg} mg/kg Γ {weight} kg = {ordered_mg} mg\n"
|
| 171 |
+
f"Step 2 β Convert concentration: {conc_mg_ml} mg/5 mL = {conc_mg_ml/5} mg/mL\n"
|
| 172 |
+
f"Step 3 β Calculate volume: {ordered_mg} mg Γ· {conc_mg_ml/5} mg/mL\n"
|
| 173 |
+
f"= **{volume_ml} mL**"
|
| 174 |
+
),
|
| 175 |
+
"tags": ["paediatric", "weight-based", "oral-liquid", "calculation"],
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def gen_gtts_per_min() -> dict:
|
| 180 |
+
"""IV drip rate in gtt/min using drop factor."""
|
| 181 |
+
volume = random.choice([100, 250, 500, 1000])
|
| 182 |
+
hours = random.choice([1, 2, 4, 6, 8])
|
| 183 |
+
drop_factor = random.choice([10, 15, 20, 60])
|
| 184 |
+
rate_ml_hr = volume / hours
|
| 185 |
+
rate_gtt_min = _round1(rate_ml_hr * drop_factor / 60)
|
| 186 |
+
|
| 187 |
+
distractors = [
|
| 188 |
+
_round1(rate_gtt_min * 2),
|
| 189 |
+
_round1(rate_ml_hr / drop_factor),
|
| 190 |
+
_round1(volume / (hours * drop_factor)),
|
| 191 |
+
]
|
| 192 |
+
distractors = [d for d in distractors if d != rate_gtt_min][:3]
|
| 193 |
+
options, correct_idx = _build_options(rate_gtt_min, distractors, unit="gtt/min")
|
| 194 |
+
|
| 195 |
+
return {
|
| 196 |
+
"id": f"calc_gtt_{random.randint(1000,9999)}",
|
| 197 |
+
"category": "Dosage Calculations",
|
| 198 |
+
"subcategory": "Drip Rate (Manual)",
|
| 199 |
+
"difficulty": "intermediate",
|
| 200 |
+
"type": "calculation",
|
| 201 |
+
"stem": (
|
| 202 |
+
f"A provider orders {volume} mL IV over {hours} hour(s). "
|
| 203 |
+
f"The IV tubing has a drop factor of {drop_factor} gtt/mL. "
|
| 204 |
+
f"At how many drops per minute should the nurse regulate the flow?"
|
| 205 |
+
),
|
| 206 |
+
"options": options,
|
| 207 |
+
"correct": correct_idx,
|
| 208 |
+
"rationale": (
|
| 209 |
+
f"Formula: (Volume Γ Drop factor) Γ· (Time in minutes)\n"
|
| 210 |
+
f"= ({volume} mL Γ {drop_factor} gtt/mL) Γ· ({hours} hr Γ 60 min/hr)\n"
|
| 211 |
+
f"= {volume * drop_factor} Γ· {hours * 60}\n"
|
| 212 |
+
f"= **{rate_gtt_min} gtt/min**"
|
| 213 |
+
),
|
| 214 |
+
"tags": ["gtt/min", "drip-rate", "manual-IV", "calculation"],
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def generate_batch(n: int = 5) -> list[dict]:
|
| 219 |
+
"""Generate a mixed batch of n calculation questions."""
|
| 220 |
+
generators = [
|
| 221 |
+
gen_oral_tablets,
|
| 222 |
+
gen_weight_based_iv,
|
| 223 |
+
gen_iv_drip_rate,
|
| 224 |
+
gen_paediatric_weight,
|
| 225 |
+
gen_gtts_per_min,
|
| 226 |
+
]
|
| 227 |
+
questions = []
|
| 228 |
+
for i in range(n):
|
| 229 |
+
gen = generators[i % len(generators)]
|
| 230 |
+
questions.append(gen())
|
| 231 |
+
return questions
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
# ---------------------------------------------------------------------------
|
| 235 |
+
# Internal helper
|
| 236 |
+
# ---------------------------------------------------------------------------
|
| 237 |
+
|
| 238 |
+
def _build_options(correct, distractors: list, unit: str) -> tuple[list[str], int]:
|
| 239 |
+
"""Shuffle correct + distractors and return (options list, correct_index)."""
|
| 240 |
+
pool = [correct] + distractors[:3]
|
| 241 |
+
# Remove duplicates
|
| 242 |
+
seen, unique = set(), []
|
| 243 |
+
for v in pool:
|
| 244 |
+
if v not in seen:
|
| 245 |
+
seen.add(v)
|
| 246 |
+
unique.append(v)
|
| 247 |
+
# Pad if needed
|
| 248 |
+
while len(unique) < 4:
|
| 249 |
+
unique.append(_round2(correct * random.uniform(1.3, 2.0)))
|
| 250 |
+
|
| 251 |
+
random.shuffle(unique)
|
| 252 |
+
formatted = [f"{v} {unit}" for v in unique]
|
| 253 |
+
correct_idx = unique.index(correct)
|
| 254 |
+
return formatted, correct_idx
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.32.0
|
| 2 |
+
requests>=2.31.0
|
scorer.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Quiz scoring, progress tracking, and performance feedback.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from collections import defaultdict
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def calculate_score(answers: dict, questions: list[dict]) -> dict:
|
| 9 |
+
"""
|
| 10 |
+
answers: {question_index: user_answer}
|
| 11 |
+
For MCQ: int (index of chosen option)
|
| 12 |
+
For SATA: list[int] (indices of chosen options)
|
| 13 |
+
Returns comprehensive score report.
|
| 14 |
+
"""
|
| 15 |
+
total = len(questions)
|
| 16 |
+
correct = 0
|
| 17 |
+
by_category = defaultdict(lambda: {"correct": 0, "total": 0})
|
| 18 |
+
by_difficulty = defaultdict(lambda: {"correct": 0, "total": 0})
|
| 19 |
+
wrong_questions = []
|
| 20 |
+
|
| 21 |
+
for i, q in enumerate(questions):
|
| 22 |
+
cat = q.get("category", "Unknown")
|
| 23 |
+
diff = q.get("difficulty", "unknown")
|
| 24 |
+
by_category[cat]["total"] += 1
|
| 25 |
+
by_difficulty[diff]["total"] += 1
|
| 26 |
+
|
| 27 |
+
user_ans = answers.get(i)
|
| 28 |
+
if user_ans is None:
|
| 29 |
+
wrong_questions.append({"question": q, "user_answer": None, "correct": False})
|
| 30 |
+
continue
|
| 31 |
+
|
| 32 |
+
is_correct = _check_answer(q, user_ans)
|
| 33 |
+
if is_correct:
|
| 34 |
+
correct += 1
|
| 35 |
+
by_category[cat]["correct"] += 1
|
| 36 |
+
by_difficulty[diff]["correct"] += 1
|
| 37 |
+
else:
|
| 38 |
+
wrong_questions.append({
|
| 39 |
+
"question": q,
|
| 40 |
+
"user_answer": user_ans,
|
| 41 |
+
"correct": False,
|
| 42 |
+
})
|
| 43 |
+
|
| 44 |
+
pct = round((correct / total) * 100, 1) if total > 0 else 0
|
| 45 |
+
|
| 46 |
+
return {
|
| 47 |
+
"total": total,
|
| 48 |
+
"correct": correct,
|
| 49 |
+
"incorrect": total - correct,
|
| 50 |
+
"percentage": pct,
|
| 51 |
+
"pass": pct >= 60,
|
| 52 |
+
"by_category": dict(by_category),
|
| 53 |
+
"by_difficulty": dict(by_difficulty),
|
| 54 |
+
"wrong_questions": wrong_questions,
|
| 55 |
+
"performance_band": _band(pct),
|
| 56 |
+
"feedback": _feedback(pct),
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _check_answer(question: dict, user_answer) -> bool:
|
| 61 |
+
correct = question.get("correct")
|
| 62 |
+
if question["type"] == "sata":
|
| 63 |
+
if not isinstance(user_answer, (list, set)):
|
| 64 |
+
return False
|
| 65 |
+
return set(user_answer) == set(correct)
|
| 66 |
+
else:
|
| 67 |
+
return user_answer == correct
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _band(pct: float) -> str:
|
| 71 |
+
if pct >= 85: return "Excellent"
|
| 72 |
+
if pct >= 75: return "Proficient"
|
| 73 |
+
if pct >= 60: return "Borderline β keep practising"
|
| 74 |
+
if pct >= 45: return "Developing β review weak areas"
|
| 75 |
+
return "Needs significant review"
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _feedback(pct: float) -> str:
|
| 79 |
+
if pct >= 85:
|
| 80 |
+
return "Outstanding performance! You demonstrate strong clinical knowledge across NCLEX domains."
|
| 81 |
+
if pct >= 75:
|
| 82 |
+
return "Good work! You're on track for NCLEX success. Focus on your weaker categories."
|
| 83 |
+
if pct >= 60:
|
| 84 |
+
return "Borderline pass. Review your missed questions carefully β focus on rationales, not just answers."
|
| 85 |
+
if pct >= 45:
|
| 86 |
+
return "Keep studying! Focus on understanding WHY each answer is correct using the rationale."
|
| 87 |
+
return "Significant review needed. Consider revisiting core nursing content for each category you missed."
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def category_percentage(score_report: dict) -> dict[str, float]:
|
| 91 |
+
"""Return {category: percentage} for charting."""
|
| 92 |
+
out = {}
|
| 93 |
+
for cat, data in score_report["by_category"].items():
|
| 94 |
+
t = data["total"]
|
| 95 |
+
out[cat] = round((data["correct"] / t) * 100, 1) if t > 0 else 0
|
| 96 |
+
return out
|
streamlit_app.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
NCLEX Practice Question Generator
|
| 3 |
+
Streamlit app β Hugging Face Spaces (free CPU tier)
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import random
|
| 7 |
+
import streamlit as st
|
| 8 |
+
|
| 9 |
+
from questions.bank import filter_questions, get_categories, get_all
|
| 10 |
+
from questions.calculations import generate_batch
|
| 11 |
+
from scorer import calculate_score, category_percentage
|
| 12 |
+
|
| 13 |
+
# ---------------------------------------------------------------------------
|
| 14 |
+
# Page config
|
| 15 |
+
# ---------------------------------------------------------------------------
|
| 16 |
+
st.set_page_config(
|
| 17 |
+
page_title="NCLEX Practice β Student Nurses",
|
| 18 |
+
page_icon="π",
|
| 19 |
+
layout="wide",
|
| 20 |
+
initial_sidebar_state="expanded",
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# ---------------------------------------------------------------------------
|
| 24 |
+
# CSS
|
| 25 |
+
# ---------------------------------------------------------------------------
|
| 26 |
+
st.markdown("""
|
| 27 |
+
<style>
|
| 28 |
+
.question-card {
|
| 29 |
+
background:#f8fafc; border:1px solid #d0dae8;
|
| 30 |
+
border-radius:10px; padding:1.2rem 1.4rem; margin-bottom:1rem;
|
| 31 |
+
}
|
| 32 |
+
.correct-ans { background:#e8f5e9; border-left:4px solid #2e7d32;
|
| 33 |
+
padding:0.6rem 1rem; border-radius:4px; margin-top:0.5rem; }
|
| 34 |
+
.wrong-ans { background:#fce4ec; border-left:4px solid #c62828;
|
| 35 |
+
padding:0.6rem 1rem; border-radius:4px; margin-top:0.5rem; }
|
| 36 |
+
.rationale { background:#e3f2fd; border-left:4px solid #1565c0;
|
| 37 |
+
padding:0.6rem 1rem; border-radius:4px; margin-top:0.5rem; }
|
| 38 |
+
.sata-badge { background:#fff8e1; color:#e65100; padding:2px 8px;
|
| 39 |
+
border-radius:4px; font-size:0.75em; font-weight:700; }
|
| 40 |
+
.calc-badge { background:#f3e5f5; color:#6a1b9a; padding:2px 8px;
|
| 41 |
+
border-radius:4px; font-size:0.75em; font-weight:700; }
|
| 42 |
+
.score-big { font-size:3rem; font-weight:800; text-align:center; }
|
| 43 |
+
.diff-easy { color:#2e7d32; font-weight:600; font-size:0.78em; }
|
| 44 |
+
.diff-inter { color:#e65100; font-weight:600; font-size:0.78em; }
|
| 45 |
+
.diff-hard { color:#c62828; font-weight:600; font-size:0.78em; }
|
| 46 |
+
</style>
|
| 47 |
+
""", unsafe_allow_html=True)
|
| 48 |
+
|
| 49 |
+
# ---------------------------------------------------------------------------
|
| 50 |
+
# Session state
|
| 51 |
+
# ---------------------------------------------------------------------------
|
| 52 |
+
_DEFAULTS = {
|
| 53 |
+
"quiz_questions": [],
|
| 54 |
+
"quiz_answers": {},
|
| 55 |
+
"quiz_submitted": False,
|
| 56 |
+
"quiz_score": None,
|
| 57 |
+
"calc_questions": [],
|
| 58 |
+
"calc_answers": {},
|
| 59 |
+
"calc_submitted": False,
|
| 60 |
+
"current_q_idx": 0,
|
| 61 |
+
"quiz_mode": "all", # "all" or "one-by-one"
|
| 62 |
+
}
|
| 63 |
+
for k, v in _DEFAULTS.items():
|
| 64 |
+
if k not in st.session_state:
|
| 65 |
+
st.session_state[k] = v
|
| 66 |
+
|
| 67 |
+
CATEGORIES = get_categories()
|
| 68 |
+
DIFFICULTY_LABELS = {"beginner": "π’ Beginner", "intermediate": "π‘ Intermediate", "advanced": "π΄ Advanced"}
|
| 69 |
+
DIFF_CSS = {"beginner": "diff-easy", "intermediate": "diff-inter", "advanced": "diff-hard"}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# ---------------------------------------------------------------------------
|
| 73 |
+
# Helpers
|
| 74 |
+
# ---------------------------------------------------------------------------
|
| 75 |
+
def _diff_badge(diff: str) -> str:
|
| 76 |
+
css = DIFF_CSS.get(diff, "diff-inter")
|
| 77 |
+
label = DIFFICULTY_LABELS.get(diff, diff.title())
|
| 78 |
+
return f'<span class="{css}">{label}</span>'
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def render_question(q: dict, idx: int, key_prefix: str, show_answer: bool = False) -> int | list | None:
|
| 82 |
+
"""Render a question card. Returns the user's answer or None."""
|
| 83 |
+
qtype = q["type"]
|
| 84 |
+
|
| 85 |
+
with st.container():
|
| 86 |
+
# Header row
|
| 87 |
+
hcol1, hcol2, hcol3 = st.columns([4, 1, 1])
|
| 88 |
+
with hcol1:
|
| 89 |
+
st.markdown(f"**Question {idx + 1}** β {q['category']} βΊ {q['subcategory']}")
|
| 90 |
+
with hcol2:
|
| 91 |
+
st.markdown(_diff_badge(q.get("difficulty", "")), unsafe_allow_html=True)
|
| 92 |
+
with hcol3:
|
| 93 |
+
if qtype == "sata":
|
| 94 |
+
st.markdown('<span class="sata-badge">SELECT ALL</span>', unsafe_allow_html=True)
|
| 95 |
+
elif qtype == "calculation":
|
| 96 |
+
st.markdown('<span class="calc-badge">CALCULATION</span>', unsafe_allow_html=True)
|
| 97 |
+
|
| 98 |
+
# Stem
|
| 99 |
+
st.markdown(f"**{q['stem']}**")
|
| 100 |
+
|
| 101 |
+
# Answer input
|
| 102 |
+
user_answer = None
|
| 103 |
+
options = q["options"]
|
| 104 |
+
|
| 105 |
+
if qtype == "sata":
|
| 106 |
+
st.caption("*Select all that apply*")
|
| 107 |
+
selected = []
|
| 108 |
+
for oi, opt in enumerate(options):
|
| 109 |
+
if show_answer:
|
| 110 |
+
is_correct_opt = oi in q["correct"]
|
| 111 |
+
icon = "β
" if is_correct_opt else "β"
|
| 112 |
+
st.markdown(f"{icon} {opt}")
|
| 113 |
+
else:
|
| 114 |
+
chk = st.checkbox(opt, key=f"{key_prefix}_q{idx}_o{oi}")
|
| 115 |
+
if chk:
|
| 116 |
+
selected.append(oi)
|
| 117 |
+
user_answer = selected
|
| 118 |
+
|
| 119 |
+
else: # mcq or calculation
|
| 120 |
+
if show_answer:
|
| 121 |
+
correct_txt = options[q["correct"]]
|
| 122 |
+
for oi, opt in enumerate(options):
|
| 123 |
+
if oi == q["correct"]:
|
| 124 |
+
st.markdown(f"β
**{opt}**")
|
| 125 |
+
else:
|
| 126 |
+
st.markdown(f"β {opt}")
|
| 127 |
+
else:
|
| 128 |
+
choice = st.radio(
|
| 129 |
+
"Select your answer:",
|
| 130 |
+
options,
|
| 131 |
+
key=f"{key_prefix}_q{idx}",
|
| 132 |
+
index=None,
|
| 133 |
+
label_visibility="collapsed",
|
| 134 |
+
)
|
| 135 |
+
user_answer = options.index(choice) if choice else None
|
| 136 |
+
|
| 137 |
+
# Show rationale if reviewing
|
| 138 |
+
if show_answer:
|
| 139 |
+
st.markdown(
|
| 140 |
+
f'<div class="rationale">π <b>Rationale:</b> {q["rationale"]}</div>',
|
| 141 |
+
unsafe_allow_html=True,
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
st.divider()
|
| 145 |
+
return user_answer
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# ---------------------------------------------------------------------------
|
| 149 |
+
# Sidebar
|
| 150 |
+
# ---------------------------------------------------------------------------
|
| 151 |
+
with st.sidebar:
|
| 152 |
+
st.markdown("## π NCLEX Practice")
|
| 153 |
+
st.divider()
|
| 154 |
+
|
| 155 |
+
st.markdown("**Quiz Settings**")
|
| 156 |
+
sel_cats = st.multiselect(
|
| 157 |
+
"Categories",
|
| 158 |
+
CATEGORIES,
|
| 159 |
+
default=CATEGORIES,
|
| 160 |
+
help="Select which NCLEX categories to include",
|
| 161 |
+
)
|
| 162 |
+
sel_diff = st.selectbox(
|
| 163 |
+
"Difficulty",
|
| 164 |
+
["Any", "beginner", "intermediate", "advanced"],
|
| 165 |
+
format_func=lambda x: "Any difficulty" if x == "Any" else DIFFICULTY_LABELS[x],
|
| 166 |
+
)
|
| 167 |
+
sel_type = st.selectbox(
|
| 168 |
+
"Question type",
|
| 169 |
+
["Any", "mcq", "sata", "calculation"],
|
| 170 |
+
format_func=lambda x: {
|
| 171 |
+
"Any": "All types", "mcq": "Multiple Choice (MCQ)",
|
| 172 |
+
"sata": "Select All That Apply (SATA)", "calculation": "Dosage Calculation"
|
| 173 |
+
}[x],
|
| 174 |
+
)
|
| 175 |
+
n_questions = st.slider("Number of questions", 5, 30, 10)
|
| 176 |
+
|
| 177 |
+
st.divider()
|
| 178 |
+
available = filter_questions(sel_cats, sel_diff, sel_type if sel_type != "calculation" else None)
|
| 179 |
+
st.caption(f"π {len(get_all())} questions in bank Β· {len(available)} match filters")
|
| 180 |
+
|
| 181 |
+
st.divider()
|
| 182 |
+
st.markdown(
|
| 183 |
+
"""
|
| 184 |
+
**NCLEX-RN Test Plan**
|
| 185 |
+
- Safe & Effective Care Environment
|
| 186 |
+
- Health Promotion & Maintenance
|
| 187 |
+
- Psychosocial Integrity
|
| 188 |
+
- Physiological Integrity
|
| 189 |
+
"""
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# ---------------------------------------------------------------------------
|
| 194 |
+
# Header
|
| 195 |
+
# ---------------------------------------------------------------------------
|
| 196 |
+
st.title("π NCLEX Practice Question Generator")
|
| 197 |
+
st.caption(
|
| 198 |
+
"Evidence-based NCLEX-style practice questions Β· MCQ Β· Select All That Apply Β· "
|
| 199 |
+
"Dosage Calculations Β· Full rationales for every question"
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
# ---------------------------------------------------------------------------
|
| 203 |
+
# Tabs
|
| 204 |
+
# ---------------------------------------------------------------------------
|
| 205 |
+
tab_quiz, tab_calc, tab_results, tab_review = st.tabs(
|
| 206 |
+
["π― Practice Quiz", "π Dosage Calculations", "π My Results", "π Review Wrong Answers"]
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# ========================= PRACTICE QUIZ TAB ==============================
|
| 210 |
+
with tab_quiz:
|
| 211 |
+
col_start, col_reset = st.columns([3, 1])
|
| 212 |
+
|
| 213 |
+
with col_start:
|
| 214 |
+
if st.button("π― Generate New Quiz", type="primary", use_container_width=True):
|
| 215 |
+
bank = filter_questions(sel_cats, sel_diff, sel_type if sel_type != "calculation" else None)
|
| 216 |
+
if not bank:
|
| 217 |
+
st.error("No questions match your filters. Adjust the sidebar settings.")
|
| 218 |
+
else:
|
| 219 |
+
sampled = random.sample(bank, min(n_questions, len(bank)))
|
| 220 |
+
# Mix in calc questions if type is Any or calculation
|
| 221 |
+
if sel_type in ("Any", "calculation"):
|
| 222 |
+
n_calc = max(1, n_questions // 5)
|
| 223 |
+
calc_q = generate_batch(n_calc)
|
| 224 |
+
if sel_type == "calculation":
|
| 225 |
+
sampled = calc_q * ((n_questions // n_calc) + 1)
|
| 226 |
+
sampled = sampled[:n_questions]
|
| 227 |
+
else:
|
| 228 |
+
sampled = sampled[:-n_calc] + calc_q
|
| 229 |
+
random.shuffle(sampled)
|
| 230 |
+
|
| 231 |
+
st.session_state.quiz_questions = sampled[:n_questions]
|
| 232 |
+
st.session_state.quiz_answers = {}
|
| 233 |
+
st.session_state.quiz_submitted = False
|
| 234 |
+
st.session_state.quiz_score = None
|
| 235 |
+
st.rerun()
|
| 236 |
+
|
| 237 |
+
with col_reset:
|
| 238 |
+
if st.button("π Reset", use_container_width=True):
|
| 239 |
+
for k in ["quiz_questions","quiz_answers","quiz_submitted","quiz_score"]:
|
| 240 |
+
st.session_state[k] = [] if k == "quiz_questions" else ({} if "answers" in k else (False if "submitted" in k else None))
|
| 241 |
+
st.rerun()
|
| 242 |
+
|
| 243 |
+
questions = st.session_state.quiz_questions
|
| 244 |
+
|
| 245 |
+
if not questions:
|
| 246 |
+
st.info("Click **Generate New Quiz** to start practising.")
|
| 247 |
+
else:
|
| 248 |
+
submitted = st.session_state.quiz_submitted
|
| 249 |
+
|
| 250 |
+
if not submitted:
|
| 251 |
+
st.markdown(f"**{len(questions)} questions** β read each carefully, then click Submit.")
|
| 252 |
+
st.divider()
|
| 253 |
+
|
| 254 |
+
# Render all questions
|
| 255 |
+
for i, q in enumerate(questions):
|
| 256 |
+
ans = render_question(q, i, "quiz", show_answer=False)
|
| 257 |
+
if ans is not None and ans != []:
|
| 258 |
+
st.session_state.quiz_answers[i] = ans
|
| 259 |
+
|
| 260 |
+
if st.button("β
Submit Quiz", type="primary", use_container_width=False):
|
| 261 |
+
score = calculate_score(st.session_state.quiz_answers, questions)
|
| 262 |
+
st.session_state.quiz_score = score
|
| 263 |
+
st.session_state.quiz_submitted = True
|
| 264 |
+
st.rerun()
|
| 265 |
+
|
| 266 |
+
else:
|
| 267 |
+
# Show results summary
|
| 268 |
+
score = st.session_state.quiz_score
|
| 269 |
+
pct = score["percentage"]
|
| 270 |
+
band = score["performance_band"]
|
| 271 |
+
emoji = "π" if pct >= 75 else "π"
|
| 272 |
+
|
| 273 |
+
c1, c2, c3 = st.columns(3)
|
| 274 |
+
c1.metric("Score", f"{score['correct']} / {score['total']}")
|
| 275 |
+
c2.metric("Percentage", f"{pct}%")
|
| 276 |
+
c3.metric("Result", band)
|
| 277 |
+
|
| 278 |
+
st.progress(pct / 100)
|
| 279 |
+
st.info(f"{emoji} {score['feedback']}")
|
| 280 |
+
st.divider()
|
| 281 |
+
|
| 282 |
+
# Show all questions with correct answers
|
| 283 |
+
st.subheader("Question Review")
|
| 284 |
+
for i, q in enumerate(questions):
|
| 285 |
+
user_ans = st.session_state.quiz_answers.get(i)
|
| 286 |
+
correct = q["correct"]
|
| 287 |
+
is_right = (set(user_ans) == set(correct)) if q["type"] == "sata" else user_ans == correct
|
| 288 |
+
|
| 289 |
+
icon = "β
" if is_right else "β"
|
| 290 |
+
with st.expander(f"{icon} Q{i+1}: {q['stem'][:80]}β¦", expanded=not is_right):
|
| 291 |
+
render_question(q, i, "review", show_answer=True)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# ========================= DOSAGE CALCULATIONS TAB ========================
|
| 295 |
+
with tab_calc:
|
| 296 |
+
c_gen, c_rst = st.columns([3, 1])
|
| 297 |
+
with c_gen:
|
| 298 |
+
if st.button("π Generate Calculation Set", type="primary", use_container_width=True):
|
| 299 |
+
st.session_state.calc_questions = generate_batch(n_questions)
|
| 300 |
+
st.session_state.calc_answers = {}
|
| 301 |
+
st.session_state.calc_submitted = False
|
| 302 |
+
st.rerun()
|
| 303 |
+
with c_rst:
|
| 304 |
+
if st.button("π Reset ", use_container_width=True, key="calc_rst"):
|
| 305 |
+
st.session_state.calc_questions = []
|
| 306 |
+
st.session_state.calc_answers = {}
|
| 307 |
+
st.session_state.calc_submitted = False
|
| 308 |
+
st.rerun()
|
| 309 |
+
|
| 310 |
+
calc_qs = st.session_state.calc_questions
|
| 311 |
+
|
| 312 |
+
if not calc_qs:
|
| 313 |
+
st.info(
|
| 314 |
+
"Click **Generate Calculation Set** for a fresh set of dosage math problems. "
|
| 315 |
+
"Each set is uniquely generated β unlimited practice!"
|
| 316 |
+
)
|
| 317 |
+
with st.expander("π Dosage Calculation Formula Reference"):
|
| 318 |
+
st.markdown("""
|
| 319 |
+
**Oral Tablets:**
|
| 320 |
+
> (Ordered dose Γ· Dose on hand) Γ Quantity = Tablets to give
|
| 321 |
+
|
| 322 |
+
**IV Rate (mL/hr):**
|
| 323 |
+
> Volume (mL) Γ· Time (hours) = mL/hr
|
| 324 |
+
|
| 325 |
+
**Manual Drip Rate (gtt/min):**
|
| 326 |
+
> (Volume Γ Drop factor) Γ· Time (minutes) = gtt/min
|
| 327 |
+
|
| 328 |
+
**Weight-based IV Infusion:**
|
| 329 |
+
> (mcg/kg/min Γ kg Γ 60) Γ· Concentration (mcg/mL) = mL/hr
|
| 330 |
+
|
| 331 |
+
**Paediatric Oral Dose:**
|
| 332 |
+
> mg/kg Γ weight (kg) Γ· concentration (mg/mL) = mL to give
|
| 333 |
+
""")
|
| 334 |
+
else:
|
| 335 |
+
calc_submitted = st.session_state.calc_submitted
|
| 336 |
+
|
| 337 |
+
if not calc_submitted:
|
| 338 |
+
for i, q in enumerate(calc_qs):
|
| 339 |
+
ans = render_question(q, i, "calc", show_answer=False)
|
| 340 |
+
if ans is not None and ans != []:
|
| 341 |
+
st.session_state.calc_answers[i] = ans
|
| 342 |
+
|
| 343 |
+
if st.button("β
Submit Calculations", type="primary"):
|
| 344 |
+
score = calculate_score(st.session_state.calc_answers, calc_qs)
|
| 345 |
+
st.session_state.calc_submitted = True
|
| 346 |
+
st.session_state.quiz_score = score
|
| 347 |
+
st.rerun()
|
| 348 |
+
else:
|
| 349 |
+
score = calculate_score(st.session_state.calc_answers, calc_qs)
|
| 350 |
+
pct = score["percentage"]
|
| 351 |
+
c1, c2, c3 = st.columns(3)
|
| 352 |
+
c1.metric("Correct", f"{score['correct']} / {score['total']}")
|
| 353 |
+
c2.metric("Percentage", f"{pct}%")
|
| 354 |
+
c3.metric("Band", score["performance_band"])
|
| 355 |
+
st.progress(pct / 100)
|
| 356 |
+
st.divider()
|
| 357 |
+
|
| 358 |
+
for i, q in enumerate(calc_qs):
|
| 359 |
+
user_ans = st.session_state.calc_answers.get(i)
|
| 360 |
+
is_right = user_ans == q["correct"]
|
| 361 |
+
icon = "β
" if is_right else "β"
|
| 362 |
+
with st.expander(f"{icon} Q{i+1}: {q['stem'][:80]}β¦", expanded=not is_right):
|
| 363 |
+
render_question(q, i, "calc_rev", show_answer=True)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
# ========================= RESULTS TAB ====================================
|
| 367 |
+
with tab_results:
|
| 368 |
+
score = st.session_state.quiz_score
|
| 369 |
+
if not score:
|
| 370 |
+
st.info("Complete a quiz to see your results here.")
|
| 371 |
+
else:
|
| 372 |
+
pct = score["percentage"]
|
| 373 |
+
band = score["performance_band"]
|
| 374 |
+
|
| 375 |
+
st.markdown(f'<div class="score-big">{pct}%</div>', unsafe_allow_html=True)
|
| 376 |
+
st.markdown(f"<h3 style='text-align:center'>{band}</h3>", unsafe_allow_html=True)
|
| 377 |
+
st.markdown(f"<p style='text-align:center'>{score['feedback']}</p>", unsafe_allow_html=True)
|
| 378 |
+
st.divider()
|
| 379 |
+
|
| 380 |
+
# Category breakdown
|
| 381 |
+
st.subheader("Performance by Category")
|
| 382 |
+
cat_pct = category_percentage(score)
|
| 383 |
+
for cat, p in sorted(cat_pct.items(), key=lambda x: x[1]):
|
| 384 |
+
col_l, col_r = st.columns([3, 1])
|
| 385 |
+
col_l.write(cat)
|
| 386 |
+
col_r.write(f"**{p}%**")
|
| 387 |
+
st.progress(p / 100)
|
| 388 |
+
|
| 389 |
+
st.divider()
|
| 390 |
+
|
| 391 |
+
# Difficulty breakdown
|
| 392 |
+
st.subheader("Performance by Difficulty")
|
| 393 |
+
diff_data = score["by_difficulty"]
|
| 394 |
+
dcols = st.columns(3)
|
| 395 |
+
for i, (diff, data) in enumerate(diff_data.items()):
|
| 396 |
+
t = data["total"]
|
| 397 |
+
p = round((data["correct"] / t) * 100, 1) if t > 0 else 0
|
| 398 |
+
dcols[i % 3].metric(
|
| 399 |
+
DIFFICULTY_LABELS.get(diff, diff.title()),
|
| 400 |
+
f"{p}%",
|
| 401 |
+
f"{data['correct']}/{t} correct",
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
# ========================= REVIEW TAB =====================================
|
| 406 |
+
with tab_review:
|
| 407 |
+
score = st.session_state.quiz_score
|
| 408 |
+
wrong = score["wrong_questions"] if score else []
|
| 409 |
+
|
| 410 |
+
if not wrong:
|
| 411 |
+
msg = "No wrong answers to review β great work! π" if score else "Complete a quiz first."
|
| 412 |
+
st.info(msg)
|
| 413 |
+
else:
|
| 414 |
+
st.subheader(f"Review: {len(wrong)} question(s) to revisit")
|
| 415 |
+
st.caption("Study the rationale for each β understanding WHY is the key to NCLEX success.")
|
| 416 |
+
st.divider()
|
| 417 |
+
|
| 418 |
+
for i, item in enumerate(wrong):
|
| 419 |
+
q = item["question"]
|
| 420 |
+
with st.expander(f"β {q['category']} | {q['stem'][:70]}β¦"):
|
| 421 |
+
st.markdown(f"**Category:** {q['category']} βΊ {q['subcategory']}")
|
| 422 |
+
st.markdown(f"**NCLEX Framework:** {q.get('nclex_framework','')}")
|
| 423 |
+
st.markdown(_diff_badge(q.get("difficulty", "")), unsafe_allow_html=True)
|
| 424 |
+
st.divider()
|
| 425 |
+
render_question(q, i, "wrong_rev", show_answer=True)
|
| 426 |
+
|
| 427 |
+
# ---------------------------------------------------------------------------
|
| 428 |
+
# Footer
|
| 429 |
+
# ---------------------------------------------------------------------------
|
| 430 |
+
st.divider()
|
| 431 |
+
st.caption(
|
| 432 |
+
"Questions aligned to the 2023 NCLEX-RN Test Plan (NCSBN). "
|
| 433 |
+
"Dosage calculations are dynamically generated β every set is unique. "
|
| 434 |
+
"For educational purposes only β always apply clinical judgment in practice."
|
| 435 |
+
)
|