Spaces:
No application file
No application file
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,1581 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
# app.py - Enhanced Test Generator for Hugging Face
|
| 4 |
-
# Aligned with Pakistani National Curriculum (2006)
|
| 5 |
-
# Developer: Najaf Ali Sharqi
|
| 6 |
-
import os
|
| 7 |
-
import gradio as gr
|
| 8 |
-
from groq import Groq
|
| 9 |
-
from datetime import datetime
|
| 10 |
-
from docx import Document
|
| 11 |
-
from docx.shared import Pt, RGBColor, Inches
|
| 12 |
-
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
| 13 |
-
from docx.oxml.ns import qn
|
| 14 |
-
from docx.oxml import OxmlElement
|
| 15 |
-
import re
|
| 16 |
-
import json
|
| 17 |
-
|
| 18 |
-
# ---------------------------
|
| 19 |
-
# Initialize GROQ client with error handling
|
| 20 |
-
# ---------------------------
|
| 21 |
-
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 22 |
-
if not GROQ_API_KEY:
|
| 23 |
-
raise ValueError("GROQ_API_KEY environment variable not set")
|
| 24 |
-
client = Groq(api_key=GROQ_API_KEY)
|
| 25 |
-
|
| 26 |
-
# ---------------------------
|
| 27 |
-
# Configuration Constants
|
| 28 |
-
# ---------------------------
|
| 29 |
-
APP_VERSION = "2.6"
|
| 30 |
-
MODEL_NAME = "llama-3.3-70b-versatile"
|
| 31 |
-
|
| 32 |
-
# ---------------------------
|
| 33 |
-
# Grades and subjects aligned with Pakistan National Curriculum
|
| 34 |
-
# ---------------------------
|
| 35 |
-
grades = [f"Grade {i}" for i in range(1, 13)]
|
| 36 |
-
|
| 37 |
-
subjects_by_grade = {
|
| 38 |
-
"Grade 1": ["Mathematics", "English", "Urdu", "Islamiyat", "General Knowledge"],
|
| 39 |
-
"Grade 2": ["Mathematics", "English", "Urdu", "Islamiyat", "General Knowledge"],
|
| 40 |
-
"Grade 3": ["Mathematics", "English", "Urdu", "Science", "Islamiyat", "General Knowledge"],
|
| 41 |
-
"Grade 4": ["Mathematics", "English", "Urdu", "Science", "Islamiyat", "General Knowledge"],
|
| 42 |
-
"Grade 5": ["Mathematics", "English", "Urdu", "Science", "Islamiyat", "Social Studies"],
|
| 43 |
-
"Grade 6": ["Mathematics", "English", "Urdu", "Science", "Islamiyat", "Social Studies"],
|
| 44 |
-
"Grade 7": ["Mathematics", "English", "Urdu", "Science", "Islamiyat", "Social Studies", "Computer"],
|
| 45 |
-
"Grade 8": ["Mathematics", "English", "Urdu", "Science", "Islamiyat", "Social Studies", "Computer"],
|
| 46 |
-
"Grade 9": ["Mathematics", "English", "Urdu", "Biology", "Chemistry", "Physics", "Islamiyat", "Pakistan Studies"],
|
| 47 |
-
"Grade 10": ["Mathematics", "English", "Urdu", "Biology", "Chemistry", "Physics", "Islamiyat", "Pakistan Studies"],
|
| 48 |
-
"Grade 11": ["Mathematics", "English", "Urdu", "Biology", "Chemistry", "Physics", "Computer", "Islamiyat", "Pakistan Studies"],
|
| 49 |
-
"Grade 12": ["Mathematics", "English", "Urdu", "Biology", "Chemistry", "Physics", "Computer", "Islamiyat", "Pakistan Studies"]
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
# ---------------------------
|
| 53 |
-
# Chapter mapping (National Curriculum 2006)
|
| 54 |
-
# ---------------------------
|
| 55 |
-
chapters_by_subject_and_grade = {
|
| 56 |
-
"Grade 1": {
|
| 57 |
-
"Mathematics": ["Counting & Numbers", "Basic Addition", "Basic Subtraction", "Shapes", "Measurements"],
|
| 58 |
-
"English": ["Alphabet & Sounds", "Basic Words", "Simple Sentences", "Listening & Speaking", "Reading Short Texts"],
|
| 59 |
-
"Urdu": ["حروف", "الفاظ", "سادہ جملے", "پڑھنا", "لکھنا"],
|
| 60 |
-
"Islamiyat": ["Basic Beliefs", "Prophets Stories", "Good Manners", "Prayers & Worship"],
|
| 61 |
-
"General Knowledge": ["My Body", "My Home", "My School", "Seasons"]
|
| 62 |
-
},
|
| 63 |
-
"Grade 2": {
|
| 64 |
-
"Mathematics": ["Place Value", "Addition & Subtraction", "Money", "Time", "Shapes"],
|
| 65 |
-
"English": ["Parts of Speech Basics", "Simple Grammar", "Short Comprehension", "Vocabulary Building"],
|
| 66 |
-
"Urdu": ["الفاظ و جملے", "مختصر کہانیاں", "حروفِ صحیح"],
|
| 67 |
-
"Islamiyat": ["Prophets", "Good Deeds", "Mosque Etiquette"],
|
| 68 |
-
"General Knowledge": ["Plants & Animals", "Local Community", "Transport"]
|
| 69 |
-
},
|
| 70 |
-
"Grade 3": {
|
| 71 |
-
"Mathematics": ["Multiplication", "Division", "Fractions", "Geometry Basics", "Measurement"],
|
| 72 |
-
"English": ["Tenses (simple)", "Comprehension Passages", "Vocabulary", "Sentence Formation"],
|
| 73 |
-
"Urdu": ["کالم و مضامین", "قواعدِ اردو", "کہانیاں"],
|
| 74 |
-
"Science": ["Plants", "Animals", "Human Body", "Materials"],
|
| 75 |
-
"Islamiyat": ["Basics of Iman", "Seerat", "Manners"],
|
| 76 |
-
"General Knowledge": ["Environment", "Community Helpers", "Safety"]
|
| 77 |
-
},
|
| 78 |
-
"Grade 4": {
|
| 79 |
-
"Mathematics": ["Fractions & Decimals", "Geometry", "Data Handling", "Word Problems"],
|
| 80 |
-
"English": ["Grammar (intermediate)", "Comprehension", "Writing Short Paragraphs"],
|
| 81 |
-
"Urdu": ["نظم و نثر", "قواعد", "مفردات"],
|
| 82 |
-
"Science": ["Ecosystems", "Forces", "Heat & Energy"],
|
| 83 |
-
"Islamiyat": ["Pillars of Islam", "Seerah Stories"],
|
| 84 |
-
"General Knowledge": ["Maps & Directions", "Cultural Heritage", "Technology"]
|
| 85 |
-
},
|
| 86 |
-
"Grade 5": {
|
| 87 |
-
"Mathematics": ["Numbers & Operations", "Geometry", "Statistics", "Ratio & Proportion"],
|
| 88 |
-
"English": ["Advanced Grammar Basics", "Paragraph Writing", "Comprehension"],
|
| 89 |
-
"Urdu": ["ادبی مطالعہ", "تعبیر و تشریح"],
|
| 90 |
-
"Science": ["Cells & Life", "Matter", "Energy"],
|
| 91 |
-
"Islamiyat": ["Quranic Stories", "Akhlaq"],
|
| 92 |
-
"Social Studies": ["Pakistan Geography", "History Basics", "Civics Introduction"]
|
| 93 |
-
},
|
| 94 |
-
"Grade 6": {
|
| 95 |
-
"Mathematics": ["Number System", "Algebra Introduction", "Geometry", "Data Handling"],
|
| 96 |
-
"English": ["Grammar (complex)", "Essay Writing", "Comprehension Skills"],
|
| 97 |
-
"Urdu": ["ادب و نحو", "تحریر"],
|
| 98 |
-
"Science": ["Atoms & Molecules", "Human Body", "Plants"],
|
| 99 |
-
"Islamiyat": ["Seerah & Ahkam"],
|
| 100 |
-
"Social Studies": ["World Geography", "Historical Events", "Government"]
|
| 101 |
-
},
|
| 102 |
-
"Grade 7": {
|
| 103 |
-
"Mathematics": ["Algebra", "Geometry", "Mensuration", "Statistics"],
|
| 104 |
-
"English": ["Poetry & Prose", "Grammar", "Comprehension"],
|
| 105 |
-
"Urdu": ["نثر و شاعری", "قواعد"],
|
| 106 |
-
"Science": ["Cell Biology", "Human Systems", "Electricity Basics"],
|
| 107 |
-
"Islamiyat": ["Islamic History", "Moral Teachings"],
|
| 108 |
-
"Social Studies": ["Geography Basics", "History", "Civics"],
|
| 109 |
-
"Computer": ["Basic ICT", "Computer Hardware", "Introduction to Coding"]
|
| 110 |
-
},
|
| 111 |
-
"Grade 8": {
|
| 112 |
-
"Mathematics": ["Linear Equations", "Geometry", "Pythagoras", "Graphs"],
|
| 113 |
-
"English": ["Advanced Comprehension", "Composition", "Grammar"],
|
| 114 |
-
"Urdu": ["تجزیہ ادب", "تحقیقِ مختصر"],
|
| 115 |
-
"Science": ["Forces & Motion", "Heat", "Waves Basics"],
|
| 116 |
-
"Islamiyat": ["Islamic Law Basics", "Seerah"],
|
| 117 |
-
"Social Studies": ["Regional Geography", "Historical Events"],
|
| 118 |
-
"Computer": ["Algorithms", "Basic Programming"]
|
| 119 |
-
},
|
| 120 |
-
"Grade 9": {
|
| 121 |
-
"Mathematics": ["Number Systems & Algebra", "Coordinate Geometry", "Trigonometry", "Graphs"],
|
| 122 |
-
"English": ["Advanced Grammar", "Comprehension", "Writing Skills"],
|
| 123 |
-
"Urdu": ["ادب اور نثر", "نظم", "مضامین"],
|
| 124 |
-
"Biology": [
|
| 125 |
-
"Introduction to Biology", "Solving a Biological Problem", "Biodiversity",
|
| 126 |
-
"Cells and Tissues", "Cell Cycle", "Enzymes", "Bioenergetics", "Nutrition", "Transport"
|
| 127 |
-
],
|
| 128 |
-
"Chemistry": [
|
| 129 |
-
"Fundamentals of Chemistry", "Structure of Atoms", "Periodic Table and Periodicity of Properties",
|
| 130 |
-
"Structure of Molecules", "Physical States of Matter", "Solutions", "Electrochemistry", "Chemical Reactivity"
|
| 131 |
-
],
|
| 132 |
-
"Physics": [
|
| 133 |
-
"Physical Quantities and Measurement", "Kinematics", "Dynamics", "Turning Effect of Forces",
|
| 134 |
-
"Gravitation", "Work and Energy", "Properties of Matter", "Thermal Properties of Matter", "Transfer of Heat"
|
| 135 |
-
],
|
| 136 |
-
"Islamiyat": ["Seerat of Prophet", "Faith & Beliefs", "Worship Practices"],
|
| 137 |
-
"Pakistan Studies": ["Ideological Basis of Pakistan", "Making of Pakistan", "Land and Environment", "History of Pakistan"]
|
| 138 |
-
},
|
| 139 |
-
"Grade 10": {
|
| 140 |
-
"Mathematics": ["Advanced Algebra", "Trigonometry", "Geometry", "Probability"],
|
| 141 |
-
"English": ["Literature", "Advanced Composition", "Comprehension"],
|
| 142 |
-
"Urdu": ["ادبِ جدید", "تحقیقی مضمون"],
|
| 143 |
-
"Biology": [
|
| 144 |
-
"Gaseous Exchange", "Homeostasis", "Coordination", "Support and Movement",
|
| 145 |
-
"Reproduction", "Inheritance", "Man and His Environment", "Biotechnology", "Pharmacology"
|
| 146 |
-
],
|
| 147 |
-
"Chemistry": [
|
| 148 |
-
"Chemical Equilibrium", "Acids, Bases and Salts", "Organic Chemistry", "Hydrocarbons",
|
| 149 |
-
"Biochemistry", "Environmental Chemistry I: The Atmosphere", "Environmental Chemistry II: Water", "Chemical Industries"
|
| 150 |
-
],
|
| 151 |
-
"Physics": [
|
| 152 |
-
"Simple Harmonic Motion and Waves", "Sound", "Geometrical Optics", "Electrostatics",
|
| 153 |
-
"Current Electricity", "Electromagnetism", "Introductory Electronics",
|
| 154 |
-
"Information and Communication Technology", "Radioactivity"
|
| 155 |
-
],
|
| 156 |
-
"Islamiyat": ["Islamic Studies II", "Ethics & Society", "Fiqh Basics"],
|
| 157 |
-
"Pakistan Studies": [
|
| 158 |
-
"History of Pakistan-II", "Pakistan in World Affairs", "Economic Developments",
|
| 159 |
-
"Population, Society and Culture of Pakistan"
|
| 160 |
-
]
|
| 161 |
-
},
|
| 162 |
-
"Grade 11": {
|
| 163 |
-
"Mathematics": ["Advanced Algebra & Calculus Intro", "Coordinate Geometry", "Trigonometry", "Vectors"],
|
| 164 |
-
"English": ["Academic Writing", "Literature Studies", "Critical Reading"],
|
| 165 |
-
"Urdu": ["ادبی مطالعہ", "نظم و نثر کی تشریح"],
|
| 166 |
-
"Biology": [
|
| 167 |
-
"Cell Structure and Functions", "Biological Molecules", "Enzymes", "Bioenergetics",
|
| 168 |
-
"Acellular Life", "Prokaryotes", "Protists and Fungi", "Diversity among Plants",
|
| 169 |
-
"Diversity among Animals", "Form and Functions in Plants", "Digestion", "Circulation", "Immunity"
|
| 170 |
-
],
|
| 171 |
-
"Chemistry": [
|
| 172 |
-
"Stoichiometry", "Atomic Structure", "Theories of Covalent Bonding and Shapes of Molecules",
|
| 173 |
-
"States of Matter I: Gases", "States of Matter II: Liquids", "States of Matter III: Solids",
|
| 174 |
-
"Chemical Equilibrium", "Acids, Bases and Salts", "Chemical Kinetics",
|
| 175 |
-
"Solutions and Colloids", "Thermochemistry", "Oxidation, Reduction and Electrochemistry"
|
| 176 |
-
],
|
| 177 |
-
"Physics": [
|
| 178 |
-
"Measurement", "Vectors and Equilibrium", "Forces and Motion", "Work and Energy",
|
| 179 |
-
"Rotational and Circular Motion", "Fluid Dynamics", "Oscillations", "Waves",
|
| 180 |
-
"Physical Optics", "Thermodynamics", "Electrostatics", "Current Electricity"
|
| 181 |
-
],
|
| 182 |
-
"Computer": ["Programming Fundamentals", "Data Structures Intro", "Databases"],
|
| 183 |
-
"Islamiyat": ["Advanced Seerah", "Comparative Religion"],
|
| 184 |
-
"Pakistan Studies": [
|
| 185 |
-
"Pakistan in Geographical Perspective", "Pakistan Resources", "Historical Perspective (Ancient to Modern)",
|
| 186 |
-
"Islam and Pakistan", "Administrative System", "Political and Constitutional Developments", "Human Rights"
|
| 187 |
-
]
|
| 188 |
-
},
|
| 189 |
-
"Grade 12": {
|
| 190 |
-
"Mathematics": ["Calculus", "Advanced Algebra", "Statistics", "Analytical Geometry"],
|
| 191 |
-
"English": ["Research Writing", "Advanced Literature", "Critical Analysis"],
|
| 192 |
-
"Urdu": ["تحقیق و تنقید", "ادبی شخصیات"],
|
| 193 |
-
"Biology": [
|
| 194 |
-
"Respiration", "Homeostasis", "Support and Movement", "Nervous Coordination",
|
| 195 |
-
"Chemical Coordination", "Behavior", "Reproduction", "Development and Aging",
|
| 196 |
-
"Inheritance", "Chromosome and DNA", "Evolution", "Man and His Environment",
|
| 197 |
-
"Biotechnology", "Biology and Human Welfare"
|
| 198 |
-
],
|
| 199 |
-
"Chemistry": [
|
| 200 |
-
"s and p Block Elements", "d-Block Elements", "Organic Compounds", "Hydrocarbons",
|
| 201 |
-
"Alkyl Halides and Amines", "Alcohols and Phenols", "Aldehydes and Ketones",
|
| 202 |
-
"Carboxylic Acids and Functional Derivatives", "Biochemistry", "Industrial Chemistry",
|
| 203 |
-
"Environmental Chemistry", "Analytical Chemistry"
|
| 204 |
-
],
|
| 205 |
-
"Physics": [
|
| 206 |
-
"Electromagnetism", "Electromagnetic Induction", "Alternating Current", "Physics of Solids",
|
| 207 |
-
"Electronics", "Dawn of Modern Physics", "Atomic Spectra", "Nuclear Physics"
|
| 208 |
-
],
|
| 209 |
-
"Computer": ["Web Technologies", "Advanced Programming Concepts"],
|
| 210 |
-
"Islamiyat": ["Advanced Islamic Thought", "Islamic Jurisprudence"],
|
| 211 |
-
"Pakistan Studies": [
|
| 212 |
-
"Society and Culture", "Foreign Relations of Pakistan", "Economic Development",
|
| 213 |
-
"Sports, Tourism and National Identity", "Contemporary Challenges and Policies"
|
| 214 |
-
]
|
| 215 |
-
}
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
-
# ---------------------------
|
| 219 |
-
# Question types for comprehensive assessment
|
| 220 |
-
# ---------------------------
|
| 221 |
-
QUESTION_TYPES = {
|
| 222 |
-
"Multiple Choice Questions (MCQs)": {
|
| 223 |
-
"code": "mcq",
|
| 224 |
-
"description": "Four-option questions testing recall and application",
|
| 225 |
-
"format": "Question with options A, B, C, D"
|
| 226 |
-
},
|
| 227 |
-
"Fill in the Blanks": {
|
| 228 |
-
"code": "fill_blank",
|
| 229 |
-
"description": "Complete sentences with missing key terms",
|
| 230 |
-
"format": "Sentences with underlined blanks"
|
| 231 |
-
},
|
| 232 |
-
"Match the Column": {
|
| 233 |
-
"code": "match_column",
|
| 234 |
-
"description": "Connect related items from two columns",
|
| 235 |
-
"format": "Two columns with items to match"
|
| 236 |
-
},
|
| 237 |
-
"Short Response Questions": {
|
| 238 |
-
"code": "short_response",
|
| 239 |
-
"description": "Brief written answers (2-3 sentences)",
|
| 240 |
-
"format": "Questions requiring concise explanations"
|
| 241 |
-
},
|
| 242 |
-
"Essay Type Questions": {
|
| 243 |
-
"code": "essay",
|
| 244 |
-
"description": "Extended written responses with detailed analysis",
|
| 245 |
-
"format": "Open-ended questions requiring paragraphs"
|
| 246 |
-
}
|
| 247 |
-
}
|
| 248 |
-
|
| 249 |
-
# ---------------------------
|
| 250 |
-
# Cognitive levels for item writing (Bloom's Taxonomy)
|
| 251 |
-
# ---------------------------
|
| 252 |
-
DIFFICULTY_LEVELS = {
|
| 253 |
-
"Remembering": {
|
| 254 |
-
"description": "Recall facts, terms, basic concepts",
|
| 255 |
-
"keywords": ["define", "list", "identify", "name", "state", "recall"]
|
| 256 |
-
},
|
| 257 |
-
"Understanding": {
|
| 258 |
-
"description": "Explain ideas or concepts",
|
| 259 |
-
"keywords": ["explain", "describe", "summarize", "interpret", "paraphrase"]
|
| 260 |
-
},
|
| 261 |
-
"Applying": {
|
| 262 |
-
"description": "Use information in new situations",
|
| 263 |
-
"keywords": ["apply", "demonstrate", "solve", "use", "calculate", "implement"]
|
| 264 |
-
},
|
| 265 |
-
"Analyzing": {
|
| 266 |
-
"description": "Draw connections, examine relationships",
|
| 267 |
-
"keywords": ["analyze", "compare", "contrast", "differentiate", "examine"]
|
| 268 |
-
},
|
| 269 |
-
"Evaluating": {
|
| 270 |
-
"description": "Justify decisions, make judgments",
|
| 271 |
-
"keywords": ["evaluate", "justify", "critique", "assess", "judge"]
|
| 272 |
-
},
|
| 273 |
-
"Creating": {
|
| 274 |
-
"description": "Produce new or original work",
|
| 275 |
-
"keywords": ["create", "design", "construct", "develop", "formulate"]
|
| 276 |
-
}
|
| 277 |
-
}
|
| 278 |
-
|
| 279 |
-
# ---------------------------
|
| 280 |
-
# Enhanced question generation with Bloom's Taxonomy distribution
|
| 281 |
-
# ---------------------------
|
| 282 |
-
def generate_questions_multi_level(grade, subject, chapter, test_type, school_name, question_type,
|
| 283 |
-
bloom_distribution, total_questions):
|
| 284 |
-
"""Generate questions with mixed cognitive levels based on user distribution"""
|
| 285 |
-
|
| 286 |
-
all_questions = []
|
| 287 |
-
all_answers = []
|
| 288 |
-
|
| 289 |
-
try:
|
| 290 |
-
# Calculate number of questions per level
|
| 291 |
-
for level, percentage in bloom_distribution.items():
|
| 292 |
-
if percentage > 0:
|
| 293 |
-
num_q = int((percentage / 100) * total_questions)
|
| 294 |
-
if num_q > 0:
|
| 295 |
-
questions, answers, error, q_type_code = generate_questions(
|
| 296 |
-
grade, subject, chapter, level,
|
| 297 |
-
num_q, test_type, school_name, question_type
|
| 298 |
-
)
|
| 299 |
-
|
| 300 |
-
if error:
|
| 301 |
-
return [], [], error, "mcq"
|
| 302 |
-
|
| 303 |
-
all_questions.extend(questions)
|
| 304 |
-
all_answers.extend(answers)
|
| 305 |
-
|
| 306 |
-
# Ensure we have the right number of questions
|
| 307 |
-
if len(all_questions) < total_questions:
|
| 308 |
-
# Generate additional questions at first non-zero level
|
| 309 |
-
shortage = total_questions - len(all_questions)
|
| 310 |
-
first_level = next((k for k, v in bloom_distribution.items() if v > 0), "Understanding")
|
| 311 |
-
|
| 312 |
-
extra_q, extra_a, error, q_type_code = generate_questions(
|
| 313 |
-
grade, subject, chapter, first_level,
|
| 314 |
-
shortage, test_type, school_name, question_type
|
| 315 |
-
)
|
| 316 |
-
|
| 317 |
-
if not error:
|
| 318 |
-
all_questions.extend(extra_q)
|
| 319 |
-
all_answers.extend(extra_a)
|
| 320 |
-
|
| 321 |
-
return all_questions[:total_questions], all_answers[:total_questions], None, q_type_code
|
| 322 |
-
|
| 323 |
-
except Exception as e:
|
| 324 |
-
return [], [], f"Error in multi-level generation: {str(e)}", "mcq"
|
| 325 |
-
|
| 326 |
-
# ---------------------------
|
| 327 |
-
# Enhanced question generation with proper item writing principles
|
| 328 |
-
# ---------------------------
|
| 329 |
-
def generate_questions(grade, subject, chapter, difficulty, num_questions, test_type, school_name, question_type):
|
| 330 |
-
"""Generate questions using AI with enhanced item writing guidelines"""
|
| 331 |
-
|
| 332 |
-
try:
|
| 333 |
-
q_type_code = QUESTION_TYPES.get(question_type, {}).get("code", "mcq")
|
| 334 |
-
is_mathematics = subject.lower() == "mathematics"
|
| 335 |
-
|
| 336 |
-
# Extract grade number for level-specific prompts
|
| 337 |
-
grade_num = int(grade.split()[-1]) if grade.startswith("Grade") else 6
|
| 338 |
-
|
| 339 |
-
# Build type-specific prompts
|
| 340 |
-
if q_type_code == "mcq":
|
| 341 |
-
system_msg = """You are an expert educational item writer specializing in Multiple Choice Questions aligned with the Pakistani National Curriculum (2006).
|
| 342 |
-
CRITICAL FORMATTING RULES:
|
| 343 |
-
- Start DIRECTLY with question 1
|
| 344 |
-
- DO NOT write introductory text like "Here are the questions" or similar
|
| 345 |
-
- DO NOT number your output with "Q1." - the system will add numbering
|
| 346 |
-
- Each question must be on its own, clean format
|
| 347 |
-
Professional MCQ Principles:
|
| 348 |
-
1. STEM CLARITY: Write clear, concise question stems
|
| 349 |
-
2. PLAUSIBLE DISTRACTORS: Wrong options based on common misconceptions
|
| 350 |
-
3. GRAMMATICAL CONSISTENCY: All options match the stem
|
| 351 |
-
4. NO CUEING: Avoid "all/none of the above"
|
| 352 |
-
5. EQUAL LENGTH: Similar option lengths
|
| 353 |
-
6. AGE-APPROPRIATE: Vocabulary for grade level
|
| 354 |
-
Format EXACTLY as:
|
| 355 |
-
[Question stem]
|
| 356 |
-
A) [Option 1]
|
| 357 |
-
B) [Option 2]
|
| 358 |
-
C) [Option 3]
|
| 359 |
-
D) [Option 4]
|
| 360 |
-
[Next question stem]
|
| 361 |
-
A) [Option 1]
|
| 362 |
-
B) [Option 2]
|
| 363 |
-
C) [Option 3]
|
| 364 |
-
D) [Option 4]
|
| 365 |
-
After all questions, provide:
|
| 366 |
-
ANSWER KEY:
|
| 367 |
-
1. B
|
| 368 |
-
2. A"""
|
| 369 |
-
|
| 370 |
-
user_msg = f"""Create {num_questions} MCQs for {grade} {subject}, Chapter: {chapter}
|
| 371 |
-
Cognitive Level: {difficulty}
|
| 372 |
-
START DIRECTLY WITH THE FIRST QUESTION - NO INTRODUCTION TEXT.
|
| 373 |
-
Align with Pakistani National Curriculum (2006).
|
| 374 |
-
Use {difficulty} skills: {DIFFICULTY_LEVELS.get(difficulty, {}).get('description', '')}"""
|
| 375 |
-
|
| 376 |
-
elif q_type_code == "fill_blank":
|
| 377 |
-
system_msg = """You are an expert at creating Fill in the Blanks questions for Pakistani curriculum.
|
| 378 |
-
CRITICAL: Start DIRECTLY with question 1. NO introductory text.
|
| 379 |
-
Format EXACTLY as:
|
| 380 |
-
[Sentence with __________ for the blank]
|
| 381 |
-
[Next sentence with __________ for the blank]
|
| 382 |
-
After all questions:
|
| 383 |
-
ANSWERS:
|
| 384 |
-
1. [correct word/phrase]
|
| 385 |
-
2. [correct word/phrase]"""
|
| 386 |
-
|
| 387 |
-
user_msg = f"""Create {num_questions} Fill in the Blanks for {grade} {subject}, Chapter: {chapter}
|
| 388 |
-
Cognitive Level: {difficulty}
|
| 389 |
-
Rules:
|
| 390 |
-
- One blank per sentence marked with __________
|
| 391 |
-
- Blank should test key concepts
|
| 392 |
-
- Provide clear context clues
|
| 393 |
-
- NO introduction - start with question 1 directly"""
|
| 394 |
-
|
| 395 |
-
elif q_type_code == "match_column":
|
| 396 |
-
system_msg = """You are an expert at creating Match the Column questions for Pakistani curriculum.
|
| 397 |
-
CRITICAL: Start DIRECTLY with the matching sets. NO introductory text.
|
| 398 |
-
Format EXACTLY as:
|
| 399 |
-
Column A | Column B
|
| 400 |
-
1. [Item A1] | a. [Item B1]
|
| 401 |
-
2. [Item A2] | b. [Item B2]
|
| 402 |
-
3. [Item A3] | c. [Item B3]
|
| 403 |
-
ANSWERS:
|
| 404 |
-
1-[letter], 2-[letter], 3-[letter]"""
|
| 405 |
-
|
| 406 |
-
user_msg = f"""Create {num_questions//2} matching pairs for {grade} {subject}, Chapter: {chapter}
|
| 407 |
-
Cognitive Level: {difficulty}
|
| 408 |
-
Create two columns with related items (terms-definitions, concepts-examples, etc.)
|
| 409 |
-
NO introduction - start directly with the table"""
|
| 410 |
-
|
| 411 |
-
elif q_type_code == "short_response":
|
| 412 |
-
if is_mathematics:
|
| 413 |
-
# MATHEMATICS-SPECIFIC SHORT QUESTIONS
|
| 414 |
-
system_msg = f"""You are an expert Mathematics teacher creating SHORT NUMERICAL/COMPUTATIONAL questions for {grade} aligned with Pakistani National Curriculum.
|
| 415 |
-
|
| 416 |
-
CRITICAL RULES FOR MATHEMATICS SHORT QUESTIONS:
|
| 417 |
-
1. Create ACTUAL COMPUTATIONAL PROBLEMS - NOT descriptive/theoretical questions
|
| 418 |
-
2. Each question must require CALCULATIONS, SOLVING, or MATHEMATICAL WORKING
|
| 419 |
-
3. Questions must be SOLVABLE with specific numerical answers
|
| 420 |
-
4. NO questions like "Describe...", "Explain the significance...", "What is the importance..."
|
| 421 |
-
5. START DIRECTLY - NO introductory text
|
| 422 |
-
|
| 423 |
-
QUESTION TYPES TO USE (based on grade level):
|
| 424 |
-
"""
|
| 425 |
-
# Add grade-specific guidance
|
| 426 |
-
if grade_num <= 5:
|
| 427 |
-
system_msg += """
|
| 428 |
-
- Simple arithmetic calculations (addition, subtraction, multiplication, division)
|
| 429 |
-
- Word problems with numbers
|
| 430 |
-
- Basic fraction operations
|
| 431 |
-
- Simple geometry (find perimeter, area)
|
| 432 |
-
- Money calculations
|
| 433 |
-
"""
|
| 434 |
-
elif grade_num <= 8:
|
| 435 |
-
system_msg += """
|
| 436 |
-
- Algebraic expressions and simplification
|
| 437 |
-
- Solving linear equations (1-2 steps)
|
| 438 |
-
- Geometry problems (angles, areas, perimeters)
|
| 439 |
-
- Ratio and proportion calculations
|
| 440 |
-
- Percentage problems
|
| 441 |
-
- Statistics (mean, median, mode)
|
| 442 |
-
"""
|
| 443 |
-
else: # Grade 9-12
|
| 444 |
-
system_msg += """
|
| 445 |
-
- Solving algebraic equations and inequalities
|
| 446 |
-
- Trigonometric calculations (find angles, sides)
|
| 447 |
-
- Coordinate geometry problems
|
| 448 |
-
- Calculus problems (differentiation, integration for Grade 11-12)
|
| 449 |
-
- Matrix operations
|
| 450 |
-
- Sequence and series
|
| 451 |
-
- Probability calculations
|
| 452 |
-
- Vector operations (for higher grades)
|
| 453 |
-
"""
|
| 454 |
-
|
| 455 |
-
system_msg += """
|
| 456 |
-
FORMAT EXACTLY AS:
|
| 457 |
-
Solve: 3x + 5 = 20
|
| 458 |
-
Find the area of a triangle with base 8 cm and height 6 cm.
|
| 459 |
-
Calculate: (2/3) + (5/6)
|
| 460 |
-
|
| 461 |
-
After all questions provide:
|
| 462 |
-
SOLUTIONS WITH WORKING:
|
| 463 |
-
1. 3x + 5 = 20
|
| 464 |
-
3x = 20 - 5
|
| 465 |
-
3x = 15
|
| 466 |
-
x = 5
|
| 467 |
-
|
| 468 |
-
2. Area = (1/2) × base × height
|
| 469 |
-
Area = (1/2) × 8 × 6
|
| 470 |
-
Area = 24 cm²
|
| 471 |
-
"""
|
| 472 |
-
|
| 473 |
-
user_msg = f"""Create {num_questions} SHORT MATHEMATICAL PROBLEMS for {grade}, Chapter: {chapter}
|
| 474 |
-
Cognitive Level: {difficulty}
|
| 475 |
-
|
| 476 |
-
CRITICAL REQUIREMENTS:
|
| 477 |
-
- Each question must be a SOLVABLE mathematical problem
|
| 478 |
-
- Include specific numbers and require calculations
|
| 479 |
-
- Mix of: equations, word problems, geometry, calculations
|
| 480 |
-
- Difficulty appropriate for {grade}
|
| 481 |
-
- Related to chapter: {chapter}
|
| 482 |
-
- NO descriptive/theoretical questions
|
| 483 |
-
|
| 484 |
-
START DIRECTLY WITH FIRST PROBLEM - NO INTRODUCTION"""
|
| 485 |
-
|
| 486 |
-
else:
|
| 487 |
-
# Non-mathematics subjects (original behavior)
|
| 488 |
-
system_msg = """You are an expert at creating Short Response questions for Pakistani curriculum.
|
| 489 |
-
CRITICAL: Start DIRECTLY with question 1. NO introductory text.
|
| 490 |
-
Format EXACTLY as:
|
| 491 |
-
[Question requiring 2-3 sentence answer]
|
| 492 |
-
[Next question]
|
| 493 |
-
After all questions:
|
| 494 |
-
MARKING SCHEME:
|
| 495 |
-
1. Key points: [point 1], [point 2], [point 3]
|
| 496 |
-
2. Key points: [point 1], [point 2]"""
|
| 497 |
-
|
| 498 |
-
user_msg = f"""Create {num_questions} Short Response questions for {grade} {subject}, Chapter: {chapter}
|
| 499 |
-
Cognitive Level: {difficulty}
|
| 500 |
-
Each answer should be 2-3 sentences (3-5 marks each)
|
| 501 |
-
NO introduction - start with question 1 directly"""
|
| 502 |
-
|
| 503 |
-
elif q_type_code == "essay":
|
| 504 |
-
if is_mathematics:
|
| 505 |
-
# MATHEMATICS-SPECIFIC LONG QUESTIONS
|
| 506 |
-
system_msg = f"""You are an expert Mathematics teacher creating LONG NUMERICAL/PROOF questions for {grade} aligned with Pakistani National Curriculum.
|
| 507 |
-
|
| 508 |
-
CRITICAL RULES FOR MATHEMATICS LONG QUESTIONS:
|
| 509 |
-
1. Create MULTI-STEP MATHEMATICAL PROBLEMS requiring detailed working
|
| 510 |
-
2. Each question must involve CALCULATIONS, PROOFS, DERIVATIONS, or COMPLEX PROBLEM-SOLVING
|
| 511 |
-
3. Questions must require showing complete mathematical working
|
| 512 |
-
4. NO descriptive essays like "Write an essay on...", "Discuss the history of..."
|
| 513 |
-
5. START DIRECTLY - NO introductory text
|
| 514 |
-
|
| 515 |
-
QUESTION TYPES TO USE (based on grade level):
|
| 516 |
-
"""
|
| 517 |
-
# Add grade-specific guidance
|
| 518 |
-
if grade_num <= 5:
|
| 519 |
-
system_msg += """
|
| 520 |
-
- Multi-step word problems
|
| 521 |
-
- Combined operations problems
|
| 522 |
-
- Detailed geometry problems (multiple shapes)
|
| 523 |
-
- Problems requiring multiple calculations
|
| 524 |
-
"""
|
| 525 |
-
elif grade_num <= 8:
|
| 526 |
-
system_msg += """
|
| 527 |
-
- Solve and verify equations
|
| 528 |
-
- Word problems with multiple steps
|
| 529 |
-
- Geometric proofs (basic)
|
| 530 |
-
- Complete problems with parts (a), (b), (c)
|
| 531 |
-
- Data handling with multiple calculations
|
| 532 |
-
- Graph drawing and interpretation
|
| 533 |
-
"""
|
| 534 |
-
else: # Grade 9-12
|
| 535 |
-
system_msg += """
|
| 536 |
-
- Multi-part algebraic problems with proof
|
| 537 |
-
- Trigonometric identities to prove
|
| 538 |
-
- Calculus problems (derivatives, integrals for Grade 11-12)
|
| 539 |
-
- Coordinate geometry with complete derivation
|
| 540 |
-
- Vector problems with multiple parts
|
| 541 |
-
- Matrix operations and determinants
|
| 542 |
-
- Complex word problems requiring modeling
|
| 543 |
-
- Geometric constructions and proofs
|
| 544 |
-
- Sequence and series with proofs
|
| 545 |
-
- Probability with multiple scenarios
|
| 546 |
-
"""
|
| 547 |
-
|
| 548 |
-
system_msg += """
|
| 549 |
-
FORMAT FOR MULTI-PART QUESTIONS:
|
| 550 |
-
Question 1:
|
| 551 |
-
(a) Solve the equation: 2x² - 5x - 3 = 0
|
| 552 |
-
(b) Verify your solutions by substituting back
|
| 553 |
-
(c) Graph the quadratic function
|
| 554 |
-
|
| 555 |
-
Question 2:
|
| 556 |
-
Prove that: sin²θ + cos²θ = 1
|
| 557 |
-
[Show complete algebraic proof]
|
| 558 |
-
|
| 559 |
-
After all questions provide:
|
| 560 |
-
DETAILED SOLUTIONS:
|
| 561 |
-
1. (a) Using quadratic formula:
|
| 562 |
-
x = [-b ± √(b² - 4ac)] / 2a
|
| 563 |
-
[complete working shown step by step]
|
| 564 |
-
|
| 565 |
-
(b) Verification:
|
| 566 |
-
[substitution and checking]
|
| 567 |
-
|
| 568 |
-
(c) [Graph description with key points]
|
| 569 |
-
|
| 570 |
-
MARKING SCHEME:
|
| 571 |
-
- Method: X marks
|
| 572 |
-
- Calculations: Y marks
|
| 573 |
-
- Final answer: Z marks
|
| 574 |
-
"""
|
| 575 |
-
|
| 576 |
-
user_msg = f"""Create {num_questions} LONG MATHEMATICAL PROBLEMS for {grade}, Chapter: {chapter}
|
| 577 |
-
Cognitive Level: {difficulty}
|
| 578 |
-
|
| 579 |
-
CRITICAL REQUIREMENTS:
|
| 580 |
-
- Each question must be a COMPLEX SOLVABLE mathematical problem
|
| 581 |
-
- Include multi-step calculations, proofs, or derivations
|
| 582 |
-
- Can have multiple parts (a), (b), (c)
|
| 583 |
-
- Appropriate difficulty for {grade}
|
| 584 |
-
- Related to chapter: {chapter}
|
| 585 |
-
- Show complete working in solutions
|
| 586 |
-
- NO essay-type descriptive questions
|
| 587 |
-
|
| 588 |
-
Types to include:
|
| 589 |
-
- Numerical problems with multiple steps
|
| 590 |
-
- Proofs of identities/theorems (for higher grades)
|
| 591 |
-
- Word problems requiring mathematical modeling
|
| 592 |
-
- Problems combining multiple concepts
|
| 593 |
-
|
| 594 |
-
START DIRECTLY WITH FIRST PROBLEM - NO INTRODUCTION"""
|
| 595 |
-
|
| 596 |
-
else:
|
| 597 |
-
# Non-mathematics subjects (original behavior)
|
| 598 |
-
system_msg = """You are an expert at creating Essay questions for Pakistani curriculum.
|
| 599 |
-
CRITICAL: Start DIRECTLY with question 1. NO introductory text.
|
| 600 |
-
Format EXACTLY as:
|
| 601 |
-
[Essay question requiring detailed analysis]
|
| 602 |
-
[Next essay question]
|
| 603 |
-
After all questions:
|
| 604 |
-
MARKING CRITERIA:
|
| 605 |
-
1. Expected coverage: [main points to cover]
|
| 606 |
-
2. Expected coverage: [main points to cover]"""
|
| 607 |
-
|
| 608 |
-
user_msg = f"""Create {num_questions} Essay questions for {grade} {subject}, Chapter: {chapter}
|
| 609 |
-
Cognitive Level: {difficulty}
|
| 610 |
-
Each answer should be 1-2 paragraphs (8-10 marks each)
|
| 611 |
-
Require analysis, evaluation, or synthesis
|
| 612 |
-
NO introduction - start with question 1 directly"""
|
| 613 |
-
|
| 614 |
-
# Call Groq API
|
| 615 |
-
response = client.chat.completions.create(
|
| 616 |
-
messages=[
|
| 617 |
-
{"role": "system", "content": system_msg},
|
| 618 |
-
{"role": "user", "content": user_msg}
|
| 619 |
-
],
|
| 620 |
-
model=MODEL_NAME,
|
| 621 |
-
temperature=0.7,
|
| 622 |
-
max_tokens=4000
|
| 623 |
-
)
|
| 624 |
-
|
| 625 |
-
text = response.choices[0].message.content
|
| 626 |
-
|
| 627 |
-
# Clean up any introductory text
|
| 628 |
-
text = clean_ai_response(text)
|
| 629 |
-
|
| 630 |
-
# Parse based on question type
|
| 631 |
-
if q_type_code == "mcq":
|
| 632 |
-
questions, answers = parse_mcqs_and_answers(text, num_questions)
|
| 633 |
-
elif q_type_code == "fill_blank":
|
| 634 |
-
questions, answers = parse_fill_blanks(text, num_questions)
|
| 635 |
-
elif q_type_code == "match_column":
|
| 636 |
-
questions, answers = parse_match_column(text)
|
| 637 |
-
elif q_type_code == "short_response":
|
| 638 |
-
questions, answers = parse_short_response(text, num_questions)
|
| 639 |
-
elif q_type_code == "essay":
|
| 640 |
-
questions, answers = parse_essay(text, num_questions)
|
| 641 |
-
else:
|
| 642 |
-
questions, answers = [], []
|
| 643 |
-
|
| 644 |
-
return questions, answers, None, q_type_code
|
| 645 |
-
|
| 646 |
-
except Exception as e:
|
| 647 |
-
error_msg = f"Error generating questions: {str(e)}"
|
| 648 |
-
return [], [], error_msg, "mcq"
|
| 649 |
-
|
| 650 |
-
def clean_ai_response(text):
|
| 651 |
-
"""Remove common AI introductory phrases"""
|
| 652 |
-
# Patterns to remove
|
| 653 |
-
patterns = [
|
| 654 |
-
r'^Here\s+(are|is)\s+the\s+\d+\s+.*?:?\s*\n*',
|
| 655 |
-
r'^Below\s+(are|is)\s+.*?:?\s*\n*',
|
| 656 |
-
r'^I\'ve\s+created\s+.*?:?\s*\n*',
|
| 657 |
-
r'^The\s+following\s+(are|is)\s+.*?:?\s*\n*',
|
| 658 |
-
r'^Q1\.\s+Here.*?:\s*\n*',
|
| 659 |
-
]
|
| 660 |
-
|
| 661 |
-
for pattern in patterns:
|
| 662 |
-
text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.MULTILINE)
|
| 663 |
-
|
| 664 |
-
return text.strip()
|
| 665 |
-
|
| 666 |
-
def parse_mcqs_and_answers(text, expected_count):
|
| 667 |
-
"""Parse AI response to extract MCQs and answer key"""
|
| 668 |
-
|
| 669 |
-
# Split by answer key section
|
| 670 |
-
parts = re.split(r'ANSWER\s+KEY:?', text, flags=re.IGNORECASE)
|
| 671 |
-
|
| 672 |
-
mcqs = []
|
| 673 |
-
answer_key = []
|
| 674 |
-
|
| 675 |
-
if len(parts) >= 1:
|
| 676 |
-
# Extract MCQs
|
| 677 |
-
mcq_text = parts[0]
|
| 678 |
-
|
| 679 |
-
# Remove any "Q#." prefixes and split by question patterns
|
| 680 |
-
mcq_text = re.sub(r'^\s*Q\d+\.\s*', '', mcq_text, flags=re.MULTILINE)
|
| 681 |
-
|
| 682 |
-
# Split by numbered questions (1., 2., etc.) but keep the content
|
| 683 |
-
questions = re.split(r'\n\s*\d+\.\s+', mcq_text)
|
| 684 |
-
questions = [q.strip() for q in questions if q.strip() and len(q.strip()) > 20]
|
| 685 |
-
mcqs = questions[:expected_count]
|
| 686 |
-
|
| 687 |
-
if len(parts) >= 2:
|
| 688 |
-
# Extract answer key
|
| 689 |
-
answer_text = parts[1]
|
| 690 |
-
# Find patterns like "1. B" or "1) B" or "1: B"
|
| 691 |
-
answers = re.findall(r'\d+[\.\):\s]+([A-D])', answer_text, re.IGNORECASE)
|
| 692 |
-
answer_key = [ans.upper() for ans in answers[:expected_count]]
|
| 693 |
-
|
| 694 |
-
# If parsing failed, create placeholder
|
| 695 |
-
if len(answer_key) < len(mcqs):
|
| 696 |
-
answer_key.extend(['A'] * (len(mcqs) - len(answer_key)))
|
| 697 |
-
|
| 698 |
-
return mcqs, answer_key
|
| 699 |
-
|
| 700 |
-
def parse_fill_blanks(text, expected_count):
|
| 701 |
-
"""Parse Fill in the Blanks questions"""
|
| 702 |
-
parts = re.split(r'ANSWERS?:?', text, flags=re.IGNORECASE)
|
| 703 |
-
|
| 704 |
-
questions = []
|
| 705 |
-
answers = []
|
| 706 |
-
|
| 707 |
-
if len(parts) >= 1:
|
| 708 |
-
q_text = parts[0]
|
| 709 |
-
# Split by line breaks, filter empty lines
|
| 710 |
-
lines = [line.strip() for line in q_text.split('\n') if line.strip() and '_' in line]
|
| 711 |
-
questions = lines[:expected_count]
|
| 712 |
-
|
| 713 |
-
if len(parts) >= 2:
|
| 714 |
-
a_text = parts[1]
|
| 715 |
-
# Extract answers
|
| 716 |
-
ans_lines = re.findall(r'\d+[\.\):\s]+(.+)', a_text)
|
| 717 |
-
answers = [ans.strip() for ans in ans_lines[:expected_count]]
|
| 718 |
-
|
| 719 |
-
if len(answers) < len(questions):
|
| 720 |
-
answers.extend(['_____'] * (len(questions) - len(answers)))
|
| 721 |
-
|
| 722 |
-
return questions, answers
|
| 723 |
-
|
| 724 |
-
def parse_match_column(text):
|
| 725 |
-
"""Parse Match the Column questions"""
|
| 726 |
-
parts = re.split(r'ANSWERS?:?', text, flags=re.IGNORECASE)
|
| 727 |
-
|
| 728 |
-
questions = []
|
| 729 |
-
answers = []
|
| 730 |
-
|
| 731 |
-
if len(parts) >= 1:
|
| 732 |
-
# Extract the table
|
| 733 |
-
questions.append(parts[0].strip())
|
| 734 |
-
|
| 735 |
-
if len(parts) >= 2:
|
| 736 |
-
# Extract matching answers
|
| 737 |
-
answers.append(parts[1].strip())
|
| 738 |
-
|
| 739 |
-
return questions, answers
|
| 740 |
-
|
| 741 |
-
def parse_short_response(text, expected_count):
|
| 742 |
-
"""Parse Short Response questions - separating questions from expected answers"""
|
| 743 |
-
# Split by expected answers section
|
| 744 |
-
parts = re.split(r'EXPECTED\s+ANSWERS?:?|MARKING\s+SCHEME:?|SOLUTIONS?\s+WITH\s+WORKING:?', text, flags=re.IGNORECASE)
|
| 745 |
-
|
| 746 |
-
questions = []
|
| 747 |
-
answers = []
|
| 748 |
-
|
| 749 |
-
if len(parts) >= 1:
|
| 750 |
-
q_text = parts[0]
|
| 751 |
-
# Split by numbered patterns
|
| 752 |
-
qs = re.split(r'\n\s*\d+[\.\)]\s+', q_text)
|
| 753 |
-
questions = [q.strip() for q in qs if q.strip() and len(q.strip()) > 10][:expected_count]
|
| 754 |
-
|
| 755 |
-
if len(parts) >= 2:
|
| 756 |
-
a_text = parts[1]
|
| 757 |
-
# Extract expected answers/key points
|
| 758 |
-
schemes = re.findall(r'\d+[\.\)]\s*(.+?)(?=\d+[\.\)]|$)', a_text, re.DOTALL)
|
| 759 |
-
answers = [s.strip() for s in schemes[:expected_count]]
|
| 760 |
-
|
| 761 |
-
if len(answers) < len(questions):
|
| 762 |
-
answers.extend(['Key points: Provide comprehensive explanation with examples'] * (len(questions) - len(answers)))
|
| 763 |
-
|
| 764 |
-
return questions, answers
|
| 765 |
-
|
| 766 |
-
def parse_essay(text, expected_count):
|
| 767 |
-
"""Parse Essay questions - separating questions from expected answers"""
|
| 768 |
-
# Split by expected answers section
|
| 769 |
-
parts = re.split(r'EXPECTED\s+ANSWERS?:?|MARKING\s+CRITERIA:?|DETAILED\s+SOLUTIONS?:?', text, flags=re.IGNORECASE)
|
| 770 |
-
|
| 771 |
-
questions = []
|
| 772 |
-
answers = []
|
| 773 |
-
|
| 774 |
-
if len(parts) >= 1:
|
| 775 |
-
q_text = parts[0]
|
| 776 |
-
# Split by numbered patterns or "Question X:"
|
| 777 |
-
qs = re.split(r'\n\s*(?:\d+[\.\)]|Question\s+\d+:)\s+', q_text, flags=re.IGNORECASE)
|
| 778 |
-
questions = [q.strip() for q in qs if q.strip() and len(q.strip()) > 15][:expected_count]
|
| 779 |
-
|
| 780 |
-
if len(parts) >= 2:
|
| 781 |
-
a_text = parts[1]
|
| 782 |
-
# Extract criteria/expected coverage
|
| 783 |
-
criteria = re.findall(r'(?:\d+[\.\)]|Question\s+\d+:)\s*(.+?)(?=(?:\d+[\.\)]|Question\s+\d+:)|$)', a_text, re.DOTALL | re.IGNORECASE)
|
| 784 |
-
answers = [c.strip() for c in criteria[:expected_count]]
|
| 785 |
-
|
| 786 |
-
if len(answers) < len(questions):
|
| 787 |
-
answers.extend(['Main points: Detailed analysis with evidence, examples, and logical argumentation'] * (len(questions) - len(answers)))
|
| 788 |
-
|
| 789 |
-
return questions, answers
|
| 790 |
-
|
| 791 |
-
def create_answer_table(doc, answers, num_cols=5):
|
| 792 |
-
"""Helper function to create formatted answer table"""
|
| 793 |
-
num_rows = len(answers) + 1
|
| 794 |
-
ans_table = doc.add_table(rows=num_rows, cols=num_cols)
|
| 795 |
-
ans_table.style = 'Medium Grid 1 Accent 1'
|
| 796 |
-
|
| 797 |
-
# Set column widths
|
| 798 |
-
for row in ans_table.rows:
|
| 799 |
-
for idx, cell in enumerate(row.cells):
|
| 800 |
-
if idx == 0:
|
| 801 |
-
cell.width = Inches(0.6)
|
| 802 |
-
else:
|
| 803 |
-
cell.width = Inches(1.2)
|
| 804 |
-
|
| 805 |
-
return ans_table
|
| 806 |
-
|
| 807 |
-
# ---------------------------
|
| 808 |
-
# Enhanced DOCX export supporting all question types
|
| 809 |
-
# ---------------------------
|
| 810 |
-
def export_to_word(questions, answers, grade, subject, chapter, difficulty, test_type, school_name, total_marks, question_type_code):
|
| 811 |
-
"""Export questions to professionally formatted Word document"""
|
| 812 |
-
|
| 813 |
-
try:
|
| 814 |
-
doc = Document()
|
| 815 |
-
|
| 816 |
-
# Set margins
|
| 817 |
-
sections = doc.sections
|
| 818 |
-
for section in sections:
|
| 819 |
-
section.top_margin = Inches(0.8)
|
| 820 |
-
section.bottom_margin = Inches(0.8)
|
| 821 |
-
section.left_margin = Inches(0.9)
|
| 822 |
-
section.right_margin = Inches(0.9)
|
| 823 |
-
|
| 824 |
-
# Header with school name
|
| 825 |
-
if school_name and school_name.strip():
|
| 826 |
-
school_heading = doc.add_heading(school_name.upper(), level=0)
|
| 827 |
-
school_heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 828 |
-
for run in school_heading.runs:
|
| 829 |
-
run.font.size = Pt(16)
|
| 830 |
-
run.font.color.rgb = RGBColor(0, 51, 102)
|
| 831 |
-
|
| 832 |
-
# Test title
|
| 833 |
-
test_title = doc.add_heading(f"{test_type.upper()}", level=1)
|
| 834 |
-
test_title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 835 |
-
for run in test_title.runs:
|
| 836 |
-
run.bold = True
|
| 837 |
-
run.font.color.rgb = RGBColor(0, 102, 51)
|
| 838 |
-
run.font.size = Pt(16)
|
| 839 |
-
|
| 840 |
-
# Information table
|
| 841 |
-
table = doc.add_table(rows=7, cols=2)
|
| 842 |
-
table.style = 'Light Grid Accent 1'
|
| 843 |
-
|
| 844 |
-
# Get question type name
|
| 845 |
-
q_type_name = next((k for k, v in QUESTION_TYPES.items() if v['code'] == question_type_code), "MCQs")
|
| 846 |
-
|
| 847 |
-
info_data = [
|
| 848 |
-
("Grade:", grade),
|
| 849 |
-
("Subject:", subject),
|
| 850 |
-
("Chapter:", chapter),
|
| 851 |
-
("Question Type:", q_type_name),
|
| 852 |
-
("Cognitive Level:", difficulty),
|
| 853 |
-
("Total Marks:", str(total_marks)),
|
| 854 |
-
("Date:", datetime.now().strftime("%d-%m-%Y"))
|
| 855 |
-
]
|
| 856 |
-
|
| 857 |
-
for idx, (label, value) in enumerate(info_data):
|
| 858 |
-
row = table.rows[idx]
|
| 859 |
-
row.cells[0].text = label
|
| 860 |
-
row.cells[1].text = value
|
| 861 |
-
row.cells[0].paragraphs[0].runs[0].bold = True
|
| 862 |
-
row.cells[0].paragraphs[0].runs[0].font.size = Pt(10)
|
| 863 |
-
row.cells[1].paragraphs[0].runs[0].font.size = Pt(10)
|
| 864 |
-
|
| 865 |
-
# Type-specific instructions
|
| 866 |
-
doc.add_paragraph()
|
| 867 |
-
instructions = doc.add_paragraph()
|
| 868 |
-
instructions.add_run("INSTRUCTIONS:").bold = True
|
| 869 |
-
|
| 870 |
-
if question_type_code == "mcq":
|
| 871 |
-
doc.add_paragraph("• Read each question carefully before selecting your answer")
|
| 872 |
-
doc.add_paragraph("• Each question carries equal marks")
|
| 873 |
-
doc.add_paragraph("• Select the MOST appropriate answer from the given options")
|
| 874 |
-
elif question_type_code == "fill_blank":
|
| 875 |
-
doc.add_paragraph("• Fill in the blanks with appropriate words/phrases")
|
| 876 |
-
doc.add_paragraph("• Write clearly and legibly")
|
| 877 |
-
doc.add_paragraph("• Each blank carries equal marks")
|
| 878 |
-
elif question_type_code == "match_column":
|
| 879 |
-
doc.add_paragraph("• Match items from Column A with Column B")
|
| 880 |
-
doc.add_paragraph("• Write the letter of the correct match")
|
| 881 |
-
doc.add_paragraph("• Each correct match carries equal marks")
|
| 882 |
-
elif question_type_code == "short_response":
|
| 883 |
-
if subject.lower() == "mathematics":
|
| 884 |
-
doc.add_paragraph("• Show all your working clearly")
|
| 885 |
-
doc.add_paragraph("• Write the final answer clearly")
|
| 886 |
-
doc.add_paragraph("• Marks are awarded for method and accuracy")
|
| 887 |
-
else:
|
| 888 |
-
doc.add_paragraph("• Answer in 2-3 complete sentences")
|
| 889 |
-
doc.add_paragraph("• Be concise and clear")
|
| 890 |
-
doc.add_paragraph("• Each question carries 3-5 marks")
|
| 891 |
-
elif question_type_code == "essay":
|
| 892 |
-
if subject.lower() == "mathematics":
|
| 893 |
-
doc.add_paragraph("• Show complete working for all calculations")
|
| 894 |
-
doc.add_paragraph("• Write proofs/derivations step-by-step")
|
| 895 |
-
doc.add_paragraph("• Label diagrams clearly if required")
|
| 896 |
-
doc.add_paragraph("• Each question may have multiple parts")
|
| 897 |
-
else:
|
| 898 |
-
doc.add_paragraph("• Write detailed, well-organized paragraphs")
|
| 899 |
-
doc.add_paragraph("• Support your answers with relevant examples")
|
| 900 |
-
doc.add_paragraph("• Each question carries 8-10 marks")
|
| 901 |
-
|
| 902 |
-
# Questions section
|
| 903 |
-
doc.add_paragraph()
|
| 904 |
-
section_title = "SECTION A: " + q_type_name.upper()
|
| 905 |
-
questions_heading = doc.add_paragraph()
|
| 906 |
-
questions_heading.add_run(section_title).bold = True
|
| 907 |
-
questions_heading.runs[0].font.size = Pt(12)
|
| 908 |
-
doc.add_paragraph()
|
| 909 |
-
|
| 910 |
-
# Add questions based on type
|
| 911 |
-
if question_type_code == "mcq":
|
| 912 |
-
for i, q in enumerate(questions, start=1):
|
| 913 |
-
q_para = doc.add_paragraph()
|
| 914 |
-
q_para.add_run(f"{i}. ").bold = True
|
| 915 |
-
q_para.add_run(q)
|
| 916 |
-
q_para.space_after = Pt(8)
|
| 917 |
-
doc.add_paragraph()
|
| 918 |
-
|
| 919 |
-
elif question_type_code == "fill_blank":
|
| 920 |
-
for i, q in enumerate(questions, start=1):
|
| 921 |
-
q_para = doc.add_paragraph()
|
| 922 |
-
q_para.add_run(f"{i}. ").bold = True
|
| 923 |
-
q_para.add_run(q)
|
| 924 |
-
q_para.space_after = Pt(8)
|
| 925 |
-
doc.add_paragraph()
|
| 926 |
-
|
| 927 |
-
elif question_type_code == "match_column":
|
| 928 |
-
for q in questions:
|
| 929 |
-
doc.add_paragraph(q)
|
| 930 |
-
|
| 931 |
-
elif question_type_code in ["short_response", "essay"]:
|
| 932 |
-
marks_per_q = 5 if question_type_code == "short_response" else 10
|
| 933 |
-
|
| 934 |
-
# For mathematics, provide space for working
|
| 935 |
-
working_lines = 5 if question_type_code == "short_response" else 12
|
| 936 |
-
|
| 937 |
-
for i, q in enumerate(questions, start=1):
|
| 938 |
-
q_para = doc.add_paragraph()
|
| 939 |
-
q_para.add_run(f"Q{i}. ").bold = True
|
| 940 |
-
q_para.add_run(q)
|
| 941 |
-
q_para.add_run(f" [{marks_per_q} marks]").italic = True
|
| 942 |
-
doc.add_paragraph()
|
| 943 |
-
|
| 944 |
-
# Add space for answer
|
| 945 |
-
if subject.lower() == "mathematics":
|
| 946 |
-
# Add "Working:" label for math questions
|
| 947 |
-
working_para = doc.add_paragraph()
|
| 948 |
-
working_para.add_run("Working:").italic = True
|
| 949 |
-
|
| 950 |
-
for _ in range(working_lines):
|
| 951 |
-
doc.add_paragraph("_" * 85)
|
| 952 |
-
doc.add_paragraph()
|
| 953 |
-
|
| 954 |
-
# Answer key (on separate page)
|
| 955 |
-
doc.add_page_break()
|
| 956 |
-
|
| 957 |
-
ans_heading = doc.add_heading("ANSWER KEY / MARKING SCHEME", level=2)
|
| 958 |
-
ans_heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 959 |
-
for run in ans_heading.runs:
|
| 960 |
-
run.font.color.rgb = RGBColor(153, 0, 0)
|
| 961 |
-
|
| 962 |
-
doc.add_paragraph("(For Teacher's Use Only)", style='Intense Quote')
|
| 963 |
-
doc.add_paragraph()
|
| 964 |
-
|
| 965 |
-
# Add answers based on type
|
| 966 |
-
if question_type_code == "mcq":
|
| 967 |
-
# Answer table for MCQs
|
| 968 |
-
ans_table = create_answer_table(doc, answers, 5)
|
| 969 |
-
|
| 970 |
-
headers = ["Q#", "A", "B", "C", "D"]
|
| 971 |
-
for idx, header in enumerate(headers):
|
| 972 |
-
cell = ans_table.cell(0, idx)
|
| 973 |
-
cell.text = header
|
| 974 |
-
cell.paragraphs[0].runs[0].bold = True
|
| 975 |
-
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 976 |
-
|
| 977 |
-
for r, ans in enumerate(answers, start=1):
|
| 978 |
-
ans_table.cell(r, 0).text = str(r)
|
| 979 |
-
ans_table.cell(r, 0).paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 980 |
-
|
| 981 |
-
for c, option in enumerate(["A", "B", "C", "D"], start=1):
|
| 982 |
-
cell = ans_table.cell(r, c)
|
| 983 |
-
if ans == option:
|
| 984 |
-
cell.text = "✓"
|
| 985 |
-
cell.paragraphs[0].runs[0].bold = True
|
| 986 |
-
cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 128, 0)
|
| 987 |
-
cell.paragraphs[0].runs[0].font.size = Pt(14)
|
| 988 |
-
else:
|
| 989 |
-
cell.text = ""
|
| 990 |
-
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 991 |
-
|
| 992 |
-
elif question_type_code == "fill_blank":
|
| 993 |
-
# Tabular answer key for Fill in the Blanks
|
| 994 |
-
ans_table = create_answer_table(doc, answers, 2)
|
| 995 |
-
|
| 996 |
-
# Headers
|
| 997 |
-
headers = ["Q#", "Correct Answer"]
|
| 998 |
-
for idx, header in enumerate(headers):
|
| 999 |
-
cell = ans_table.cell(0, idx)
|
| 1000 |
-
cell.text = header
|
| 1001 |
-
cell.paragraphs[0].runs[0].bold = True
|
| 1002 |
-
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 1003 |
-
|
| 1004 |
-
# Fill answers
|
| 1005 |
-
for r, ans in enumerate(answers, start=1):
|
| 1006 |
-
ans_table.cell(r, 0).text = str(r)
|
| 1007 |
-
ans_table.cell(r, 0).paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 1008 |
-
ans_table.cell(r, 1).text = str(ans)
|
| 1009 |
-
|
| 1010 |
-
elif question_type_code in ["short_response", "essay"]:
|
| 1011 |
-
# Expected answers for subjective questions
|
| 1012 |
-
for i, ans in enumerate(answers, start=1):
|
| 1013 |
-
ans_para = doc.add_paragraph()
|
| 1014 |
-
if subject.lower() == "mathematics":
|
| 1015 |
-
ans_para.add_run(f"Q{i}. Solution / Marking Scheme:").bold = True
|
| 1016 |
-
else:
|
| 1017 |
-
ans_para.add_run(f"Q{i}. Expected Answer:").bold = True
|
| 1018 |
-
doc.add_paragraph(str(ans))
|
| 1019 |
-
doc.add_paragraph()
|
| 1020 |
-
|
| 1021 |
-
else:
|
| 1022 |
-
# Text-based answers for other types
|
| 1023 |
-
for i, ans in enumerate(answers, start=1):
|
| 1024 |
-
ans_para = doc.add_paragraph()
|
| 1025 |
-
ans_para.add_run(f"{i}. ").bold = True
|
| 1026 |
-
ans_para.add_run(str(ans))
|
| 1027 |
-
doc.add_paragraph()
|
| 1028 |
-
|
| 1029 |
-
# Footer
|
| 1030 |
-
doc.add_paragraph()
|
| 1031 |
-
footer = doc.add_paragraph()
|
| 1032 |
-
footer.add_run(f"Generated by Test Generator Pro v{APP_VERSION} | Developer: Najaf Ali Sharqi | {datetime.now().strftime('%d-%m-%Y %H:%M')}").italic = True
|
| 1033 |
-
footer.runs[0].font.size = Pt(8)
|
| 1034 |
-
footer.runs[0].font.color.rgb = RGBColor(128, 128, 128)
|
| 1035 |
-
footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
| 1036 |
-
|
| 1037 |
-
# Save file
|
| 1038 |
-
safe_chapter = re.sub(r'[\\/*?:"<>|]', "_", chapter)
|
| 1039 |
-
safe_school = re.sub(r'[\\/*?:"<>|]', "_", school_name) if school_name else "School"
|
| 1040 |
-
safe_qtype = re.sub(r'[\\/*?:"<>|]', "_", q_type_name)
|
| 1041 |
-
filename = f"{safe_school}_{grade}_{subject}_{safe_chapter}_{safe_qtype}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
| 1042 |
-
|
| 1043 |
-
doc.save(filename)
|
| 1044 |
-
return filename, None
|
| 1045 |
-
|
| 1046 |
-
except Exception as e:
|
| 1047 |
-
return None, f"Error creating document: {str(e)}"
|
| 1048 |
-
|
| 1049 |
-
# ---------------------------
|
| 1050 |
-
# Main generation handler with Bloom's Taxonomy distribution
|
| 1051 |
-
# ---------------------------
|
| 1052 |
-
def on_generate(grade, subject, chapter, test_type, school_name, question_type,
|
| 1053 |
-
use_bloom_mix, bloom_remember, bloom_understand, bloom_apply,
|
| 1054 |
-
bloom_analyze, bloom_evaluate, bloom_create, num_questions):
|
| 1055 |
-
"""Handle question generation request with optional Bloom's distribution"""
|
| 1056 |
-
|
| 1057 |
-
# Validation
|
| 1058 |
-
if not all([grade, subject, chapter, question_type]):
|
| 1059 |
-
return "⚠️ Please fill all required fields", None, None
|
| 1060 |
-
|
| 1061 |
-
if not school_name or not school_name.strip():
|
| 1062 |
-
school_name = "Educational Institution"
|
| 1063 |
-
|
| 1064 |
-
try:
|
| 1065 |
-
# Check if using Bloom's mix
|
| 1066 |
-
if use_bloom_mix:
|
| 1067 |
-
# Validate percentages
|
| 1068 |
-
total_percent = bloom_remember + bloom_understand + bloom_apply + bloom_analyze + bloom_evaluate + bloom_create
|
| 1069 |
-
if total_percent != 100:
|
| 1070 |
-
return f"❌ Bloom's Taxonomy percentages must total 100% (currently {total_percent}%)", None, None
|
| 1071 |
-
|
| 1072 |
-
bloom_distribution = {
|
| 1073 |
-
"Remembering": bloom_remember,
|
| 1074 |
-
"Understanding": bloom_understand,
|
| 1075 |
-
"Applying": bloom_apply,
|
| 1076 |
-
"Analyzing": bloom_analyze,
|
| 1077 |
-
"Evaluating": bloom_evaluate,
|
| 1078 |
-
"Creating": bloom_create
|
| 1079 |
-
}
|
| 1080 |
-
|
| 1081 |
-
# Show progress
|
| 1082 |
-
status_msg = f"🔄 Generating {num_questions} {question_type} with mixed cognitive levels...\n"
|
| 1083 |
-
status_msg += f"📊 Bloom's Distribution:\n"
|
| 1084 |
-
for level, pct in bloom_distribution.items():
|
| 1085 |
-
if pct > 0:
|
| 1086 |
-
status_msg += f" • {level}: {pct}% ({int(pct * num_questions / 100)} questions)\n"
|
| 1087 |
-
|
| 1088 |
-
# Generate with mixed levels
|
| 1089 |
-
questions, answers, error, q_type_code = generate_questions_multi_level(
|
| 1090 |
-
grade, subject, chapter, test_type, school_name, question_type,
|
| 1091 |
-
bloom_distribution, num_questions
|
| 1092 |
-
)
|
| 1093 |
-
else:
|
| 1094 |
-
# Single level generation (default to Understanding)
|
| 1095 |
-
status_msg = f"🔄 Generating {num_questions} {question_type}...\n"
|
| 1096 |
-
status_msg += f"📖 Grade: {grade} | Subject: {subject}\n"
|
| 1097 |
-
status_msg += f"📚 Chapter: {chapter}\n"
|
| 1098 |
-
status_msg += f"🎯 Cognitive Level: Understanding\n"
|
| 1099 |
-
status_msg += "⏳ Please wait..."
|
| 1100 |
-
|
| 1101 |
-
questions, answers, error, q_type_code = generate_questions(
|
| 1102 |
-
grade, subject, chapter, "Understanding",
|
| 1103 |
-
num_questions, test_type, school_name, question_type
|
| 1104 |
-
)
|
| 1105 |
-
|
| 1106 |
-
if error:
|
| 1107 |
-
return f"❌ Error: {error}", None, None
|
| 1108 |
-
|
| 1109 |
-
if not questions or len(questions) == 0:
|
| 1110 |
-
return "❌ No questions generated. Please try again.", None, None
|
| 1111 |
-
|
| 1112 |
-
# Calculate marks based on question type
|
| 1113 |
-
if q_type_code == "mcq":
|
| 1114 |
-
marks_per_q = 1
|
| 1115 |
-
elif q_type_code == "fill_blank":
|
| 1116 |
-
marks_per_q = 2
|
| 1117 |
-
elif q_type_code == "match_column":
|
| 1118 |
-
marks_per_q = len(questions) * 0.5
|
| 1119 |
-
elif q_type_code == "short_response":
|
| 1120 |
-
marks_per_q = 5
|
| 1121 |
-
elif q_type_code == "essay":
|
| 1122 |
-
marks_per_q = 10
|
| 1123 |
-
else:
|
| 1124 |
-
marks_per_q = 1
|
| 1125 |
-
|
| 1126 |
-
total_marks = int(len(questions) * marks_per_q)
|
| 1127 |
-
|
| 1128 |
-
# Export to Word
|
| 1129 |
-
file_path, doc_error = export_to_word(
|
| 1130 |
-
questions, answers, grade, subject, chapter,
|
| 1131 |
-
"Mixed Cognitive Levels" if use_bloom_mix else "Understanding",
|
| 1132 |
-
test_type, school_name, total_marks, q_type_code
|
| 1133 |
-
)
|
| 1134 |
-
|
| 1135 |
-
if doc_error:
|
| 1136 |
-
return f"❌ {doc_error}", None, None
|
| 1137 |
-
|
| 1138 |
-
# Success message
|
| 1139 |
-
success_msg = f"✅ Test generated successfully!\n\n"
|
| 1140 |
-
success_msg += f"📊 Test Statistics:\n"
|
| 1141 |
-
success_msg += f"• Question Type: {question_type}\n"
|
| 1142 |
-
success_msg += f"• Total Questions: {len(questions)}\n"
|
| 1143 |
-
success_msg += f"• Total Marks: {total_marks}\n"
|
| 1144 |
-
success_msg += f"• Grade: {grade} | Subject: {subject}\n"
|
| 1145 |
-
success_msg += f"• Chapter: {chapter}\n"
|
| 1146 |
-
|
| 1147 |
-
if use_bloom_mix:
|
| 1148 |
-
success_msg += f"\n🎯 Bloom's Taxonomy Distribution:\n"
|
| 1149 |
-
for level, pct in bloom_distribution.items():
|
| 1150 |
-
if pct > 0:
|
| 1151 |
-
success_msg += f" • {level}: {pct}%\n"
|
| 1152 |
-
|
| 1153 |
-
success_msg += f"\n📥 Download your test document below"
|
| 1154 |
-
|
| 1155 |
-
# Preview
|
| 1156 |
-
preview_count = min(3, len(questions))
|
| 1157 |
-
preview = f"\n\n📝 PREVIEW (First {preview_count} Questions):\n" + "="*50 + "\n\n"
|
| 1158 |
-
|
| 1159 |
-
for i, q in enumerate(questions[:preview_count], 1):
|
| 1160 |
-
if q_type_code == "mcq":
|
| 1161 |
-
preview += f"{i}. {q[:200]}...\n\n" if len(q) > 200 else f"{i}. {q}\n\n"
|
| 1162 |
-
else:
|
| 1163 |
-
preview += f"{i}. {q[:200]}...\n\n" if len(q) > 200 else f"{i}. {q}\n\n"
|
| 1164 |
-
|
| 1165 |
-
return success_msg + preview, file_path, f"🎉 File ready: {file_path}"
|
| 1166 |
-
|
| 1167 |
-
except Exception as e:
|
| 1168 |
-
return f"❌ Unexpected error: {str(e)}", None, None
|
| 1169 |
-
|
| 1170 |
-
# ---------------------------
|
| 1171 |
-
# Enhanced Gradio UI
|
| 1172 |
-
# ---------------------------
|
| 1173 |
-
def create_ui():
|
| 1174 |
-
"""Create professional Gradio interface"""
|
| 1175 |
-
|
| 1176 |
-
with gr.Blocks(
|
| 1177 |
-
theme=gr.themes.Soft(
|
| 1178 |
-
primary_hue="green",
|
| 1179 |
-
secondary_hue="blue",
|
| 1180 |
-
),
|
| 1181 |
-
css="""
|
| 1182 |
-
.header {text-align: center; margin-bottom: 20px;}
|
| 1183 |
-
.description {text-align: center; color: #666; margin-bottom: 30px;}
|
| 1184 |
-
.footer {text-align: center; margin-top: 30px; padding: 20px; color: #888;}
|
| 1185 |
-
.warning {background-color: #fff3cd; padding: 10px; border-radius: 5px; margin: 10px 0;}
|
| 1186 |
-
"""
|
| 1187 |
-
) as demo:
|
| 1188 |
-
|
| 1189 |
-
# Header
|
| 1190 |
-
gr.Markdown(
|
| 1191 |
-
"""
|
| 1192 |
-
# 📚 Test Generator Pro
|
| 1193 |
-
### **Developer: Najaf Ali Sharqi**
|
| 1194 |
-
|
| 1195 |
-
Professional Assessment Tool Aligned with Pakistani National Curriculum (2006)
|
| 1196 |
-
""",
|
| 1197 |
-
elem_classes="header"
|
| 1198 |
-
)
|
| 1199 |
-
|
| 1200 |
-
gr.Markdown(
|
| 1201 |
-
"""
|
| 1202 |
-
Generate high-quality assessment questions with multiple formats and Bloom's Taxonomy integration.
|
| 1203 |
-
Perfect for educators creating comprehensive tests for Grades 1-12.
|
| 1204 |
-
|
| 1205 |
-
**✨ New: Enhanced Mathematics Question Generation** - Creates actual computational problems, equations, and proofs!
|
| 1206 |
-
""",
|
| 1207 |
-
elem_classes="description"
|
| 1208 |
-
)
|
| 1209 |
-
|
| 1210 |
-
with gr.Row():
|
| 1211 |
-
with gr.Column(scale=1):
|
| 1212 |
-
gr.Markdown("### 📋 Test Configuration")
|
| 1213 |
-
|
| 1214 |
-
school_name = gr.Textbox(
|
| 1215 |
-
label="School/Institution Name",
|
| 1216 |
-
placeholder="Enter your school name (optional)",
|
| 1217 |
-
value="",
|
| 1218 |
-
info="Will appear on the test document header"
|
| 1219 |
-
)
|
| 1220 |
-
|
| 1221 |
-
test_type = gr.Dropdown(
|
| 1222 |
-
choices=[
|
| 1223 |
-
"Weekly Test",
|
| 1224 |
-
"Monthly Test",
|
| 1225 |
-
"Mid-Term Examination",
|
| 1226 |
-
"Final Examination",
|
| 1227 |
-
"Practice Test",
|
| 1228 |
-
"Mock Examination"
|
| 1229 |
-
],
|
| 1230 |
-
value="Monthly Test",
|
| 1231 |
-
label="Test Type",
|
| 1232 |
-
info="Select the type of assessment"
|
| 1233 |
-
)
|
| 1234 |
-
|
| 1235 |
-
question_type = gr.Dropdown(
|
| 1236 |
-
choices=list(QUESTION_TYPES.keys()),
|
| 1237 |
-
value="Multiple Choice Questions (MCQs)",
|
| 1238 |
-
label="Question Type",
|
| 1239 |
-
info="Select the format of questions to generate"
|
| 1240 |
-
)
|
| 1241 |
-
|
| 1242 |
-
# Show description of selected question type
|
| 1243 |
-
question_type_info = gr.Markdown(
|
| 1244 |
-
"""
|
| 1245 |
-
**Description:** Four-option questions testing recall and application
|
| 1246 |
-
|
| 1247 |
-
**Format:** Question with options A, B, C, D
|
| 1248 |
-
""",
|
| 1249 |
-
visible=True
|
| 1250 |
-
)
|
| 1251 |
-
|
| 1252 |
-
with gr.Row():
|
| 1253 |
-
grade_dropdown = gr.Dropdown(
|
| 1254 |
-
choices=grades,
|
| 1255 |
-
label="Grade Level",
|
| 1256 |
-
info="Select student grade level"
|
| 1257 |
-
)
|
| 1258 |
-
|
| 1259 |
-
num_questions = gr.Slider(
|
| 1260 |
-
minimum=5,
|
| 1261 |
-
maximum=50,
|
| 1262 |
-
value=20,
|
| 1263 |
-
step=5,
|
| 1264 |
-
label="Number of Questions",
|
| 1265 |
-
info="How many questions to generate"
|
| 1266 |
-
)
|
| 1267 |
-
|
| 1268 |
-
subject_dropdown = gr.Dropdown(
|
| 1269 |
-
choices=[],
|
| 1270 |
-
label="Subject",
|
| 1271 |
-
info="Select subject based on grade"
|
| 1272 |
-
)
|
| 1273 |
-
|
| 1274 |
-
chapter_dropdown = gr.Dropdown(
|
| 1275 |
-
choices=[],
|
| 1276 |
-
label="Chapter/Unit",
|
| 1277 |
-
info="Select specific chapter or unit"
|
| 1278 |
-
)
|
| 1279 |
-
|
| 1280 |
-
# Bloom's Taxonomy Distribution Section
|
| 1281 |
-
use_bloom_mix = gr.Checkbox(
|
| 1282 |
-
label="📊 Use Mixed Bloom's Taxonomy Levels",
|
| 1283 |
-
value=False,
|
| 1284 |
-
info="Create tests with questions from multiple cognitive levels"
|
| 1285 |
-
)
|
| 1286 |
-
|
| 1287 |
-
bloom_percentages = gr.Column(visible=False)
|
| 1288 |
-
|
| 1289 |
-
with bloom_percentages:
|
| 1290 |
-
gr.Markdown("### Set Percentage for Each Cognitive Level")
|
| 1291 |
-
gr.Markdown("*(Total must equal 100%)*")
|
| 1292 |
-
|
| 1293 |
-
with gr.Row():
|
| 1294 |
-
bloom_remember = gr.Slider(
|
| 1295 |
-
minimum=0, maximum=100, value=30, step=5,
|
| 1296 |
-
label="Remembering %",
|
| 1297 |
-
info="Recall facts and basic concepts"
|
| 1298 |
-
)
|
| 1299 |
-
bloom_understand = gr.Slider(
|
| 1300 |
-
minimum=0, maximum=100, value=30, step=5,
|
| 1301 |
-
label="Understanding %",
|
| 1302 |
-
info="Explain ideas and concepts"
|
| 1303 |
-
)
|
| 1304 |
-
|
| 1305 |
-
with gr.Row():
|
| 1306 |
-
bloom_apply = gr.Slider(
|
| 1307 |
-
minimum=0, maximum=100, value=20, step=5,
|
| 1308 |
-
label="Applying %",
|
| 1309 |
-
info="Use information in new situations"
|
| 1310 |
-
)
|
| 1311 |
-
bloom_analyze = gr.Slider(
|
| 1312 |
-
minimum=0, maximum=100, value=10, step=5,
|
| 1313 |
-
label="Analyzing %",
|
| 1314 |
-
info="Draw connections and relationships"
|
| 1315 |
-
)
|
| 1316 |
-
|
| 1317 |
-
with gr.Row():
|
| 1318 |
-
bloom_evaluate = gr.Slider(
|
| 1319 |
-
minimum=0, maximum=100, value=10, step=5,
|
| 1320 |
-
label="Evaluating %",
|
| 1321 |
-
info="Justify decisions and make judgments"
|
| 1322 |
-
)
|
| 1323 |
-
bloom_create = gr.Slider(
|
| 1324 |
-
minimum=0, maximum=100, value=0, step=5,
|
| 1325 |
-
label="Creating %",
|
| 1326 |
-
info="Produce new or original work"
|
| 1327 |
-
)
|
| 1328 |
-
|
| 1329 |
-
bloom_total = gr.Textbox(
|
| 1330 |
-
label="Total Percentage",
|
| 1331 |
-
value="100%",
|
| 1332 |
-
interactive=False
|
| 1333 |
-
)
|
| 1334 |
-
|
| 1335 |
-
difficulty_dropdown = gr.Dropdown(
|
| 1336 |
-
choices=list(DIFFICULTY_LEVELS.keys()),
|
| 1337 |
-
value="Understanding",
|
| 1338 |
-
label="Single Cognitive Level (if not using mix)",
|
| 1339 |
-
info="Select one level for entire test",
|
| 1340 |
-
visible=True
|
| 1341 |
-
)
|
| 1342 |
-
|
| 1343 |
-
gr.Markdown(
|
| 1344 |
-
"""
|
| 1345 |
-
<div class="warning">
|
| 1346 |
-
⚠️ <strong>Note:</strong> Ensure GROQ_API_KEY is set in environment variables
|
| 1347 |
-
</div>
|
| 1348 |
-
"""
|
| 1349 |
-
)
|
| 1350 |
-
|
| 1351 |
-
generate_btn = gr.Button(
|
| 1352 |
-
"🚀 Generate Test",
|
| 1353 |
-
variant="primary",
|
| 1354 |
-
size="lg"
|
| 1355 |
-
)
|
| 1356 |
-
|
| 1357 |
-
gr.Markdown("---")
|
| 1358 |
-
|
| 1359 |
-
gr.Markdown(
|
| 1360 |
-
"""
|
| 1361 |
-
### 💡 Tips for Best Results:
|
| 1362 |
-
- **MCQs**: Best for quick assessment and objective grading
|
| 1363 |
-
- **Fill in the Blanks**: Tests specific terminology and facts
|
| 1364 |
-
- **Match the Column**: Assesses relationships and connections
|
| 1365 |
-
- **Short Response**: Evaluates understanding and explanation
|
| 1366 |
-
- *Mathematics*: Generates actual calculation problems and equations
|
| 1367 |
-
- **Essay Questions**: Measures critical thinking and synthesis
|
| 1368 |
-
- *Mathematics*: Creates multi-step problems, proofs, and derivations
|
| 1369 |
-
- **Bloom's Mix**: Use for comprehensive assessment across cognitive levels
|
| 1370 |
-
- Review generated questions before final use
|
| 1371 |
-
|
| 1372 |
-
### 🔢 Mathematics Question Enhancement:
|
| 1373 |
-
- Short questions now include actual numerical problems, equations, and calculations
|
| 1374 |
-
- Long questions feature multi-step problems, proofs, and detailed derivations
|
| 1375 |
-
- Aligned with FBISE and provincial board exam patterns
|
| 1376 |
-
- Grade-appropriate complexity (Middle, Secondary, Higher Secondary)
|
| 1377 |
-
|
| 1378 |
-
### 📧 Custom App Development
|
| 1379 |
-
**Need a customized assessment tool?**
|
| 1380 |
-
Contact developer **Najaf Ali Sharqi** for:
|
| 1381 |
-
- Custom question banks
|
| 1382 |
-
- Institution-specific features
|
| 1383 |
-
- Advanced analytics
|
| 1384 |
-
- Integration with LMS platforms
|
| 1385 |
-
"""
|
| 1386 |
-
)
|
| 1387 |
-
|
| 1388 |
-
with gr.Column(scale=1):
|
| 1389 |
-
gr.Markdown("### 📊 Output & Preview")
|
| 1390 |
-
|
| 1391 |
-
output_text = gr.Textbox(
|
| 1392 |
-
label="Generation Status & Preview",
|
| 1393 |
-
lines=20,
|
| 1394 |
-
placeholder="Click 'Generate Test' to create questions...",
|
| 1395 |
-
interactive=False
|
| 1396 |
-
)
|
| 1397 |
-
|
| 1398 |
-
file_status = gr.Textbox(
|
| 1399 |
-
label="File Status",
|
| 1400 |
-
lines=1,
|
| 1401 |
-
interactive=False,
|
| 1402 |
-
visible=False
|
| 1403 |
-
)
|
| 1404 |
-
|
| 1405 |
-
output_file = gr.File(
|
| 1406 |
-
label="📥 Download Test Document (.docx)",
|
| 1407 |
-
file_types=[".docx"]
|
| 1408 |
-
)
|
| 1409 |
-
|
| 1410 |
-
gr.Markdown(
|
| 1411 |
-
"""
|
| 1412 |
-
### 📄 Document Includes:
|
| 1413 |
-
- ✅ Professional header with school name
|
| 1414 |
-
- ✅ Complete test information table
|
| 1415 |
-
- ✅ Type-specific instructions
|
| 1416 |
-
- ✅ Well-formatted questions
|
| 1417 |
-
- ✅ Separate answer key / marking scheme
|
| 1418 |
-
- ✅ Answer spaces (for written questions)
|
| 1419 |
-
- ✅ **NEW**: Proper mathematical notation and working space
|
| 1420 |
-
"""
|
| 1421 |
-
)
|
| 1422 |
-
|
| 1423 |
-
# Footer
|
| 1424 |
-
gr.Markdown(
|
| 1425 |
-
"""
|
| 1426 |
-
---
|
| 1427 |
-
<div class="footer">
|
| 1428 |
-
<strong>Test Generator Pro v2.6</strong><br>
|
| 1429 |
-
Comprehensive Assessment Tool with Enhanced Mathematics Support<br>
|
| 1430 |
-
Now featuring actual computational problems, equations, and proofs for Mathematics!<br>
|
| 1431 |
-
Aligned with Pakistani National Curriculum (2006) & FBISE Standards<br>
|
| 1432 |
-
Developer: <strong>Najaf Ali Sharqi</strong><br>
|
| 1433 |
-
Based on Professional Item Writing Standards<br><br>
|
| 1434 |
-
📧 For custom app development or support: Contact Najaf Ali Sharqi
|
| 1435 |
-
</div>
|
| 1436 |
-
""",
|
| 1437 |
-
elem_classes="footer"
|
| 1438 |
-
)
|
| 1439 |
-
|
| 1440 |
-
# Event handlers
|
| 1441 |
-
def update_subjects(selected_grade):
|
| 1442 |
-
"""Update subject dropdown when grade changes"""
|
| 1443 |
-
if selected_grade:
|
| 1444 |
-
subjects = subjects_by_grade.get(selected_grade, [])
|
| 1445 |
-
return gr.update(choices=subjects, value=None)
|
| 1446 |
-
return gr.update(choices=[], value=None)
|
| 1447 |
-
|
| 1448 |
-
def update_chapters(selected_subject, selected_grade):
|
| 1449 |
-
"""Update chapter dropdown when subject or grade changes"""
|
| 1450 |
-
if selected_grade and selected_subject:
|
| 1451 |
-
chapters = chapters_by_subject_and_grade.get(selected_grade, {}).get(selected_subject, [])
|
| 1452 |
-
return gr.update(choices=chapters, value=None)
|
| 1453 |
-
return gr.update(choices=[], value=None)
|
| 1454 |
-
|
| 1455 |
-
def update_question_type_info(q_type):
|
| 1456 |
-
"""Update question type description"""
|
| 1457 |
-
if q_type in QUESTION_TYPES:
|
| 1458 |
-
info = QUESTION_TYPES[q_type]
|
| 1459 |
-
desc = info.get('description', '')
|
| 1460 |
-
format_info = info.get('format', '')
|
| 1461 |
-
return f"""
|
| 1462 |
-
**Description:** {desc}
|
| 1463 |
-
|
| 1464 |
-
**Format:** {format_info}
|
| 1465 |
-
"""
|
| 1466 |
-
return ""
|
| 1467 |
-
|
| 1468 |
-
def toggle_bloom_sliders(use_mix):
|
| 1469 |
-
"""Show/hide Bloom's taxonomy sliders"""
|
| 1470 |
-
return gr.update(visible=use_mix), gr.update(visible=not use_mix)
|
| 1471 |
-
|
| 1472 |
-
def calculate_bloom_total(r, u, ap, an, e, c):
|
| 1473 |
-
"""Calculate total percentage"""
|
| 1474 |
-
total = r + u + ap + an + e + c
|
| 1475 |
-
color = "green" if total == 100 else "red"
|
| 1476 |
-
return f'<span style="color:{color}; font-weight:bold;">{total}%</span>'
|
| 1477 |
-
|
| 1478 |
-
# Connect event handlers
|
| 1479 |
-
grade_dropdown.change(
|
| 1480 |
-
update_subjects,
|
| 1481 |
-
inputs=[grade_dropdown],
|
| 1482 |
-
outputs=[subject_dropdown]
|
| 1483 |
-
)
|
| 1484 |
-
|
| 1485 |
-
subject_dropdown.change(
|
| 1486 |
-
update_chapters,
|
| 1487 |
-
inputs=[subject_dropdown, grade_dropdown],
|
| 1488 |
-
outputs=[chapter_dropdown]
|
| 1489 |
-
)
|
| 1490 |
-
|
| 1491 |
-
grade_dropdown.change(
|
| 1492 |
-
update_chapters,
|
| 1493 |
-
inputs=[subject_dropdown, grade_dropdown],
|
| 1494 |
-
outputs=[chapter_dropdown]
|
| 1495 |
-
)
|
| 1496 |
-
|
| 1497 |
-
question_type.change(
|
| 1498 |
-
update_question_type_info,
|
| 1499 |
-
inputs=[question_type],
|
| 1500 |
-
outputs=[question_type_info]
|
| 1501 |
-
)
|
| 1502 |
-
|
| 1503 |
-
use_bloom_mix.change(
|
| 1504 |
-
toggle_bloom_sliders,
|
| 1505 |
-
inputs=[use_bloom_mix],
|
| 1506 |
-
outputs=[bloom_percentages, difficulty_dropdown]
|
| 1507 |
-
)
|
| 1508 |
-
|
| 1509 |
-
# Update total when sliders change
|
| 1510 |
-
for slider in [bloom_remember, bloom_understand, bloom_apply, bloom_analyze, bloom_evaluate, bloom_create]:
|
| 1511 |
-
slider.change(
|
| 1512 |
-
calculate_bloom_total,
|
| 1513 |
-
inputs=[bloom_remember, bloom_understand, bloom_apply, bloom_analyze, bloom_evaluate, bloom_create],
|
| 1514 |
-
outputs=[bloom_total]
|
| 1515 |
-
)
|
| 1516 |
-
|
| 1517 |
-
generate_btn.click(
|
| 1518 |
-
on_generate,
|
| 1519 |
-
inputs=[
|
| 1520 |
-
grade_dropdown,
|
| 1521 |
-
subject_dropdown,
|
| 1522 |
-
chapter_dropdown,
|
| 1523 |
-
test_type,
|
| 1524 |
-
school_name,
|
| 1525 |
-
question_type,
|
| 1526 |
-
use_bloom_mix,
|
| 1527 |
-
bloom_remember,
|
| 1528 |
-
bloom_understand,
|
| 1529 |
-
bloom_apply,
|
| 1530 |
-
bloom_analyze,
|
| 1531 |
-
bloom_evaluate,
|
| 1532 |
-
bloom_create,
|
| 1533 |
-
num_questions
|
| 1534 |
-
],
|
| 1535 |
-
outputs=[output_text, output_file, file_status]
|
| 1536 |
-
)
|
| 1537 |
-
|
| 1538 |
-
return demo
|
| 1539 |
-
|
| 1540 |
-
# ---------------------------
|
| 1541 |
-
# Launch application
|
| 1542 |
-
# ---------------------------
|
| 1543 |
-
try:
|
| 1544 |
-
print("🚀 Starting Test Generator Pro v2.6")
|
| 1545 |
-
print(f"📦 Gradio version: {gr.__version__}")
|
| 1546 |
-
|
| 1547 |
-
# Check API key
|
| 1548 |
-
if not GROQ_API_KEY:
|
| 1549 |
-
print("⚠️ WARNING: GROQ_API_KEY not found in environment variables")
|
| 1550 |
-
print("Please set it in Hugging Face Space Settings > Repository secrets")
|
| 1551 |
-
else:
|
| 1552 |
-
print(f"✅ API Key configured (length: {len(GROQ_API_KEY)})")
|
| 1553 |
-
|
| 1554 |
-
# Create and launch
|
| 1555 |
-
demo = create_ui()
|
| 1556 |
-
print("✅ UI created successfully")
|
| 1557 |
-
|
| 1558 |
-
demo.queue() # Enable queue for better handling
|
| 1559 |
-
demo.launch()
|
| 1560 |
-
|
| 1561 |
-
except Exception as e:
|
| 1562 |
-
print(f"❌ Critical Error: {str(e)}")
|
| 1563 |
-
import traceback
|
| 1564 |
-
traceback.print_exc()
|
| 1565 |
-
|
| 1566 |
-
# Create minimal error interface
|
| 1567 |
-
with gr.Blocks() as error_demo:
|
| 1568 |
-
gr.Markdown(f"""
|
| 1569 |
-
# ❌ Application Error
|
| 1570 |
-
|
| 1571 |
-
**Error Message:** {str(e)}
|
| 1572 |
-
|
| 1573 |
-
**Common Solutions:**
|
| 1574 |
-
1. Check if GROQ_API_KEY is set in Space settings
|
| 1575 |
-
2. Verify all dependencies are installed
|
| 1576 |
-
3. Check the build logs for errors
|
| 1577 |
-
|
| 1578 |
-
**Contact:** Developer - Najaf Ali Sharqi
|
| 1579 |
-
""")
|
| 1580 |
-
|
| 1581 |
-
error_demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|