#!/usr/bin/env python3 """Base utilities for question generation.""" import random import sys import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from db.init import get_db, get_question_type_id, insert_questions_batch def shuffle_options(correct, wrongs, explanation=""): """Create options with correct answer randomly placed.""" options = [correct] + wrongs[:3] # Map original correct to its letter indices = list(range(4)) random.shuffle(indices) shuffled = [options[i] for i in indices] correct_letter = chr(65 + indices.index(0)) # A, B, C, D return { 'option_a': str(shuffled[0]), 'option_b': str(shuffled[1]), 'option_c': str(shuffled[2]), 'option_d': str(shuffled[3]), 'correct_option': correct_letter, 'explanation': explanation } def make_question(qtype_id, text, correct, wrongs, explanation="", difficulty=1): """Build a question dict ready for DB insertion.""" q = shuffle_options(correct, wrongs, explanation) q['question_type_id'] = qtype_id q['question_text'] = text q['difficulty'] = difficulty return q def get_qtid(conn, subject, subtopic, topic, qtype): """Shorthand for get_question_type_id.""" return get_question_type_id(conn, subject, subtopic, topic, qtype) def nearby_wrong(val, spread=None): """Generate plausible wrong answers near the correct value.""" if spread is None: spread = max(1, abs(val) // 5) if val != 0 else 5 wrongs = set() attempts = 0 while len(wrongs) < 3 and attempts < 50: offset = random.randint(1, max(1, spread)) sign = random.choice([-1, 1]) w = val + sign * offset if w != val and w not in wrongs: wrongs.add(w) attempts += 1 # Fallback while len(wrongs) < 3: wrongs.add(val + len(wrongs) + 1) return [str(w) for w in wrongs] def nearby_wrong_float(val, spread=None, decimals=2): """Generate plausible wrong answers for float values.""" if spread is None: spread = max(0.5, abs(val) * 0.2) wrongs = set() attempts = 0 while len(wrongs) < 3 and attempts < 50: offset = round(random.uniform(0.1, spread), decimals) sign = random.choice([-1, 1]) w = round(val + sign * offset, decimals) if w != round(val, decimals) and w not in wrongs and w > 0: wrongs.add(w) attempts += 1 while len(wrongs) < 3: wrongs.add(round(val + (len(wrongs) + 1) * 0.5, decimals)) return [str(w) for w in wrongs] def frac_str(num, den): """Format a fraction as string.""" from math import gcd g = gcd(abs(num), abs(den)) n, d = num // g, den // g if d == 1: return str(n) return f"{n}/{d}"