From d3d0b1b3bfbf431066be0cb842e0f62290ba3253 Mon Sep 17 00:00:00 2001 From: Black Box Date: Sun, 29 Mar 2026 10:58:13 +0530 Subject: [PATCH] Initial commit for SSCTopper platform --- .gitignore | 29 + db/__pycache__/init.cpython-314.pyc | Bin 0 -> 7430 bytes db/init.py | 145 +++ db/schema.sql | 83 ++ db/syllabus.json | 826 +++++++++++++ generators/__init__.py | 1 + .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 157 bytes generators/__pycache__/base.cpython-314.pyc | Bin 0 -> 4712 bytes .../english_generator.cpython-314.pyc | Bin 0 -> 37493 bytes .../__pycache__/gk_generator.cpython-314.pyc | Bin 0 -> 28747 bytes .../quant_generator.cpython-314.pyc | Bin 0 -> 51568 bytes .../reasoning_generator.cpython-314.pyc | Bin 0 -> 34576 bytes generators/base.py | 89 ++ generators/english_generator.py | 545 +++++++++ generators/gk_generator.py | 354 ++++++ generators/quant_generator.py | 721 ++++++++++++ generators/reasoning_generator.py | 510 ++++++++ generators/run_all.py | 63 + server.py | 492 ++++++++ static/index.html | 1036 +++++++++++++++++ static/robots.txt | 4 + static/sitemap.xml | 9 + 22 files changed, 4907 insertions(+) create mode 100644 .gitignore create mode 100644 db/__pycache__/init.cpython-314.pyc create mode 100644 db/init.py create mode 100644 db/schema.sql create mode 100644 db/syllabus.json create mode 100644 generators/__init__.py create mode 100644 generators/__pycache__/__init__.cpython-314.pyc create mode 100644 generators/__pycache__/base.cpython-314.pyc create mode 100644 generators/__pycache__/english_generator.cpython-314.pyc create mode 100644 generators/__pycache__/gk_generator.cpython-314.pyc create mode 100644 generators/__pycache__/quant_generator.cpython-314.pyc create mode 100644 generators/__pycache__/reasoning_generator.cpython-314.pyc create mode 100644 generators/base.py create mode 100644 generators/english_generator.py create mode 100644 generators/gk_generator.py create mode 100644 generators/quant_generator.py create mode 100644 generators/reasoning_generator.py create mode 100644 generators/run_all.py create mode 100644 server.py create mode 100644 static/index.html create mode 100644 static/robots.txt create mode 100644 static/sitemap.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c77834 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# database +*.db diff --git a/db/__pycache__/init.cpython-314.pyc b/db/__pycache__/init.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3aa1f264b231a1121530e21e6edda9c5ab56798b GIT binary patch literal 7430 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 Xp<8o-KauvOH#SJ`Hzq_jD5Llr(V%X( literal 0 HcmV?d00001 diff --git a/db/init.py b/db/init.py new file mode 100644 index 0000000..cc6ad41 --- /dev/null +++ b/db/init.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +Database initialization script for SSCTopper. +Creates the database at /tmp/ssctopper.db, applies schema, and seeds the syllabus structure. +""" +import sqlite3 +import json +import os +import sys + +DB_DIR = os.path.dirname(os.path.abspath(__file__)) +DB_PATH = os.environ.get('SSCTOPPER_DB', '/tmp/ssctopper.db') +SCHEMA_PATH = os.path.join(DB_DIR, 'schema.sql') +SYLLABUS_PATH = os.path.join(DB_DIR, 'syllabus.json') + + +def get_db(): + """Get database connection.""" + conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA foreign_keys=ON") + conn.row_factory = sqlite3.Row + return conn + + +def init_db(force=False): + """Initialize database with schema and syllabus data.""" + if os.path.exists(DB_PATH): + if force: + os.remove(DB_PATH) + print(f"Removed existing database at {DB_PATH}") + else: + print(f"Database already exists at {DB_PATH}. Use --force to recreate.") + return + + conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA foreign_keys=ON") + cursor = conn.cursor() + + # Apply schema + with open(SCHEMA_PATH, 'r') as f: + cursor.executescript(f.read()) + print("Schema applied successfully.") + + # Load syllabus + with open(SYLLABUS_PATH, 'r') as f: + syllabus = json.load(f) + + # Seed syllabus data + stats = {'subjects': 0, 'subtopics': 0, 'topics': 0, 'qtypes': 0} + + for subject in syllabus['subjects']: + cursor.execute( + "INSERT INTO subjects (name, tier, description, target_questions) VALUES (?, ?, ?, ?)", + (subject['name'], subject['tier'], subject['description'], subject.get('target_questions', 0)) + ) + subject_id = cursor.lastrowid + stats['subjects'] += 1 + + for subtopic in subject.get('subtopics', []): + cursor.execute( + "INSERT INTO subtopics (subject_id, name, description) VALUES (?, ?, ?)", + (subject_id, subtopic['name'], subtopic.get('description', '')) + ) + subtopic_id = cursor.lastrowid + stats['subtopics'] += 1 + + for topic in subtopic.get('topics', []): + cursor.execute( + "INSERT INTO topics (subtopic_id, name, description) VALUES (?, ?, ?)", + (subtopic_id, topic['name'], topic.get('description', '')) + ) + topic_id = cursor.lastrowid + stats['topics'] += 1 + + for qtype in topic.get('question_types', []): + cursor.execute( + "INSERT INTO question_types (topic_id, name) VALUES (?, ?)", + (topic_id, qtype) + ) + stats['qtypes'] += 1 + + conn.commit() + conn.close() + + print(f"\n{'='*50}") + print(f"Database initialized at: {DB_PATH}") + print(f"{'='*50}") + print(f" Subjects: {stats['subjects']}") + print(f" Sub-topics: {stats['subtopics']}") + print(f" Topics: {stats['topics']}") + print(f" Question Types: {stats['qtypes']}") + print(f"\nReady for question generation!") + return stats + + +def get_question_type_id(conn, subject_name, subtopic_name, topic_name, qtype_name): + """Look up a question_type_id by hierarchical names.""" + row = conn.execute(""" + SELECT qt.id FROM question_types qt + JOIN topics t ON qt.topic_id = t.id + JOIN subtopics st ON t.subtopic_id = st.id + JOIN subjects s ON st.subject_id = s.id + WHERE s.name = ? AND st.name = ? AND t.name = ? AND qt.name = ? + """, (subject_name, subtopic_name, topic_name, qtype_name)).fetchone() + return row[0] if row else None + + +def insert_questions_batch(conn, questions): + """Insert a batch of questions. Each question is a dict with keys: + question_type_id, question_text, option_a, option_b, option_c, option_d, + correct_option, explanation, difficulty + """ + conn.executemany(""" + INSERT INTO questions (question_type_id, question_text, option_a, option_b, option_c, option_d, + correct_option, explanation, difficulty) + VALUES (:question_type_id, :question_text, :option_a, :option_b, :option_c, :option_d, + :correct_option, :explanation, :difficulty) + """, questions) + conn.commit() + + +def get_stats(conn): + """Get current question statistics.""" + stats = {} + rows = conn.execute(""" + SELECT s.name, COUNT(q.id) as count + FROM subjects s + LEFT JOIN subtopics st ON st.subject_id = s.id + LEFT JOIN topics t ON t.subtopic_id = st.id + LEFT JOIN question_types qt ON qt.topic_id = t.id + LEFT JOIN questions q ON q.question_type_id = qt.id + GROUP BY s.id + """).fetchall() + for row in rows: + stats[row[0]] = row[1] + total = conn.execute("SELECT COUNT(*) FROM questions").fetchone()[0] + stats['TOTAL'] = total + return stats + + +if __name__ == '__main__': + force = '--force' in sys.argv + init_db(force=force) diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..0b917e7 --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,83 @@ +-- SSCTopper Database Schema +-- Complete SSC CGL Question Bank + +CREATE TABLE IF NOT EXISTS subjects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + tier TEXT NOT NULL, + description TEXT, + target_questions INTEGER DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS subtopics ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + subject_id INTEGER NOT NULL, + name TEXT NOT NULL, + description TEXT, + FOREIGN KEY (subject_id) REFERENCES subjects(id), + UNIQUE(subject_id, name) +); + +CREATE TABLE IF NOT EXISTS topics ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + subtopic_id INTEGER NOT NULL, + name TEXT NOT NULL, + description TEXT, + FOREIGN KEY (subtopic_id) REFERENCES subtopics(id), + UNIQUE(subtopic_id, name) +); + +CREATE TABLE IF NOT EXISTS question_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + topic_id INTEGER NOT NULL, + name TEXT NOT NULL, + FOREIGN KEY (topic_id) REFERENCES topics(id), + UNIQUE(topic_id, name) +); + +CREATE TABLE IF NOT EXISTS questions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + question_type_id INTEGER NOT NULL, + question_text TEXT NOT NULL, + option_a TEXT NOT NULL, + option_b TEXT NOT NULL, + option_c TEXT NOT NULL, + option_d TEXT NOT NULL, + correct_option TEXT NOT NULL CHECK(correct_option IN ('A','B','C','D')), + explanation TEXT, + difficulty INTEGER DEFAULT 1 CHECK(difficulty BETWEEN 1 AND 3), + year_appeared TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (question_type_id) REFERENCES question_types(id) +); + +-- Performance indexes +CREATE INDEX IF NOT EXISTS idx_questions_type ON questions(question_type_id); +CREATE INDEX IF NOT EXISTS idx_questions_difficulty ON questions(difficulty); +CREATE INDEX IF NOT EXISTS idx_topics_subtopic ON topics(subtopic_id); +CREATE INDEX IF NOT EXISTS idx_subtopics_subject ON subtopics(subject_id); +CREATE INDEX IF NOT EXISTS idx_qtypes_topic ON question_types(topic_id); + +-- User Management +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Progress Tracking (Question level) +CREATE TABLE IF NOT EXISTS user_answers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + question_id INTEGER NOT NULL, + is_correct BOOLEAN NOT NULL, + time_taken REAL DEFAULT 0.0, + answered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (question_id) REFERENCES questions(id) +); + +CREATE INDEX IF NOT EXISTS idx_user_answers_user ON user_answers(user_id); +CREATE INDEX IF NOT EXISTS idx_user_answers_question ON user_answers(question_id); diff --git a/db/syllabus.json b/db/syllabus.json new file mode 100644 index 0000000..2938d7f --- /dev/null +++ b/db/syllabus.json @@ -0,0 +1,826 @@ +{ + "subjects": [ + { + "name": "Quantitative Aptitude", + "tier": "1,2", + "description": "Mathematical skills including arithmetic, algebra, geometry, mensuration, trigonometry, and data interpretation", + "target_questions": 30000, + "subtopics": [ + { + "name": "Arithmetic", + "description": "Basic arithmetic operations and number concepts", + "topics": [ + { + "name": "Number System", + "description": "Natural numbers, whole numbers, integers, rational & irrational numbers, divisibility, remainders", + "question_types": ["Find the value", "Divisibility test", "Remainder problems", "Unit digit", "LCM/HCF word problems"] + }, + { + "name": "HCF and LCM", + "description": "Highest Common Factor and Lowest Common Multiple", + "question_types": ["Find HCF", "Find LCM", "Word problems on HCF/LCM", "Product of two numbers"] + }, + { + "name": "Simplification", + "description": "BODMAS, fractions, decimals, surds", + "question_types": ["Simplify expression", "Compare values", "Find missing number"] + }, + { + "name": "Decimals and Fractions", + "description": "Conversion, operations on decimals and fractions", + "question_types": ["Convert fraction to decimal", "Operations on fractions", "Recurring decimals", "Ordering fractions"] + } + ] + }, + { + "name": "Percentage", + "description": "Percentage calculations and applications", + "topics": [ + { + "name": "Basic Percentage", + "description": "Finding percentage of a number, expressing as percentage", + "question_types": ["Find X% of Y", "What percent is X of Y", "Percentage change", "Find original value"] + }, + { + "name": "Successive Percentage", + "description": "Multiple percentage changes applied successively", + "question_types": ["Net percentage change", "Two successive increases/decreases", "Mixed increase and decrease"] + }, + { + "name": "Population and Depreciation", + "description": "Population growth and depreciation of value over time", + "question_types": ["Population after N years", "Depreciation value", "Find rate of growth"] + } + ] + }, + { + "name": "Profit and Loss", + "description": "Cost price, selling price, profit, loss, discount and marked price", + "topics": [ + { + "name": "Basic Profit and Loss", + "description": "Finding profit, loss, cost price, selling price", + "question_types": ["Find profit/loss", "Find profit/loss percentage", "Find CP given SP and profit%", "Find SP given CP and loss%"] + }, + { + "name": "Discount and Marked Price", + "description": "Marked price, discount percentage, successive discounts", + "question_types": ["Find selling price after discount", "Find marked price", "Successive discounts", "Find single equivalent discount"] + }, + { + "name": "Dishonest Dealings", + "description": "False weights, mixing to gain profit", + "question_types": ["Profit with false weight", "Overall gain/loss in mixing"] + } + ] + }, + { + "name": "Ratio and Proportion", + "description": "Ratios, proportions, and their applications", + "topics": [ + { + "name": "Basic Ratio", + "description": "Simplifying ratios, comparing ratios, dividing in ratio", + "question_types": ["Simplify ratio", "Divide in given ratio", "Find quantity from ratio", "Compare ratios"] + }, + { + "name": "Proportion", + "description": "Direct proportion, inverse proportion, mean proportional", + "question_types": ["Find fourth proportional", "Find mean proportional", "Third proportional"] + }, + { + "name": "Partnership", + "description": "Profit sharing among partners based on investment and time", + "question_types": ["Divide profit among partners", "Find investment amount", "Find time of investment"] + }, + { + "name": "Mixture and Alligation", + "description": "Mixing two or more quantities", + "question_types": ["Find ratio of mixing", "Mean price of mixture", "Replacement problems"] + } + ] + }, + { + "name": "Average", + "description": "Mean, weighted average and related problems", + "topics": [ + { + "name": "Simple Average", + "description": "Average of numbers, runs, marks", + "question_types": ["Find average", "Find sum from average", "Find missing value", "Average after adding/removing"] + }, + { + "name": "Weighted Average", + "description": "Average with different weights", + "question_types": ["Weighted mean", "Combined average of groups", "Average speed"] + }, + { + "name": "Age Problems", + "description": "Average age related problems", + "question_types": ["Average age of family", "Age ratio problems", "Present/future/past average age"] + } + ] + }, + { + "name": "Time and Work", + "description": "Work efficiency, combined work, pipes and cisterns", + "topics": [ + { + "name": "Basic Time and Work", + "description": "Individual and combined work efficiency", + "question_types": ["Find time to complete work", "Find combined time", "Work done in given days", "Alternate working"] + }, + { + "name": "Pipes and Cisterns", + "description": "Filling and emptying tanks through pipes", + "question_types": ["Time to fill tank", "Net filling rate", "Leak problems", "Multiple pipes"] + }, + { + "name": "Work and Wages", + "description": "Wages proportional to work done", + "question_types": ["Divide wages", "Find individual wage", "Efficiency-based wages"] + } + ] + }, + { + "name": "Time, Speed and Distance", + "description": "Speed, distance, time relationships and applications", + "topics": [ + { + "name": "Basic Speed and Distance", + "description": "Finding speed, distance, time", + "question_types": ["Find speed", "Find distance", "Find time", "Average speed", "Relative speed"] + }, + { + "name": "Trains", + "description": "Problems involving trains passing objects or each other", + "question_types": ["Train passing pole", "Train passing platform", "Two trains passing each other", "Find length of train"] + }, + { + "name": "Boats and Streams", + "description": "Upstream and downstream problems", + "question_types": ["Find speed in still water", "Find stream speed", "Time for upstream/downstream journey", "Round trip time"] + }, + { + "name": "Races", + "description": "Head start, dead heat, circular track", + "question_types": ["Head start distance", "Dead heat problems", "Meeting point on circular track"] + } + ] + }, + { + "name": "Interest", + "description": "Simple and compound interest calculations", + "topics": [ + { + "name": "Simple Interest", + "description": "SI = PRT/100", + "question_types": ["Find SI", "Find principal", "Find rate", "Find time", "Amount after time"] + }, + { + "name": "Compound Interest", + "description": "Interest compounded annually, half-yearly, quarterly", + "question_types": ["Find CI", "Find amount", "Difference between CI and SI", "Half-yearly/quarterly compounding"] + } + ] + }, + { + "name": "Algebra", + "description": "Algebraic expressions, equations and identities", + "topics": [ + { + "name": "Linear Equations", + "description": "One variable and two variable linear equations", + "question_types": ["Solve for x", "Word problems", "System of equations", "Find value of expression"] + }, + { + "name": "Quadratic Equations", + "description": "Solving quadratic equations, nature of roots", + "question_types": ["Find roots", "Sum and product of roots", "Nature of roots", "Form equation from roots"] + }, + { + "name": "Algebraic Identities", + "description": "Standard identities and their applications", + "question_types": ["Simplify using identity", "Find value of expression", "Factorize"] + }, + { + "name": "Surds and Indices", + "description": "Laws of exponents, simplification of surds", + "question_types": ["Simplify expression", "Rationalize denominator", "Compare surds", "Find value"] + } + ] + }, + { + "name": "Geometry", + "description": "Lines, angles, triangles, circles, quadrilaterals", + "topics": [ + { + "name": "Lines and Angles", + "description": "Parallel lines, transversals, angle relationships", + "question_types": ["Find angle value", "Identify angle type", "Parallel line properties"] + }, + { + "name": "Triangles", + "description": "Properties, congruence, similarity, centers of triangle", + "question_types": ["Find angle in triangle", "Congruence condition", "Similar triangle ratio", "Centroid/Incenter/Circumcenter problems"] + }, + { + "name": "Circles", + "description": "Chord, tangent, arc, sector properties", + "question_types": ["Find angle in circle", "Chord length", "Tangent properties", "Arc/sector angle"] + }, + { + "name": "Quadrilaterals and Polygons", + "description": "Properties of parallelogram, rhombus, rectangle, regular polygons", + "question_types": ["Find angle", "Find diagonal", "Properties identification", "Interior/exterior angle sum"] + }, + { + "name": "Coordinate Geometry", + "description": "Distance, section formula, area of triangle, equation of line", + "question_types": ["Find distance between points", "Find midpoint", "Area of triangle", "Slope of line"] + } + ] + }, + { + "name": "Mensuration", + "description": "Area, perimeter, volume, surface area of 2D and 3D shapes", + "topics": [ + { + "name": "2D Figures", + "description": "Area and perimeter of plane figures", + "question_types": ["Find area", "Find perimeter", "Find diagonal", "Combined figure area"] + }, + { + "name": "3D Figures", + "description": "Volume and surface area of solids", + "question_types": ["Find volume", "Find surface area", "Find curved surface area", "Conversion between shapes"] + } + ] + }, + { + "name": "Trigonometry", + "description": "Trigonometric ratios, identities, heights and distances", + "topics": [ + { + "name": "Trigonometric Ratios", + "description": "sin, cos, tan and their values for standard angles", + "question_types": ["Find value of expression", "Simplify trig expression", "Find angle"] + }, + { + "name": "Trigonometric Identities", + "description": "Standard identities and their proofs", + "question_types": ["Prove identity", "Simplify using identity", "Find value given condition"] + }, + { + "name": "Heights and Distances", + "description": "Application of trigonometry to find heights and distances", + "question_types": ["Find height of tower", "Find distance", "Angle of elevation/depression", "Two-observer problems"] + } + ] + }, + { + "name": "Data Interpretation", + "description": "Reading and analyzing data from charts and tables", + "topics": [ + { + "name": "Bar Graph", + "description": "Interpreting bar charts", + "question_types": ["Find value", "Calculate ratio", "Find percentage change", "Compare data"] + }, + { + "name": "Pie Chart", + "description": "Interpreting pie charts", + "question_types": ["Find degree/value", "Calculate percentage", "Compare sectors"] + }, + { + "name": "Line Graph", + "description": "Interpreting line graphs", + "question_types": ["Find trend", "Calculate change", "Average over period"] + }, + { + "name": "Table", + "description": "Interpreting tabular data", + "question_types": ["Calculate from table", "Find ratio", "Percentage calculation"] + } + ] + }, + { + "name": "Statistics", + "description": "Measures of central tendency and dispersion", + "topics": [ + { + "name": "Mean, Median and Mode", + "description": "Central tendency measures for grouped and ungrouped data", + "question_types": ["Find mean", "Find median", "Find mode", "Combined mean"] + }, + { + "name": "Range and Standard Deviation", + "description": "Measures of spread", + "question_types": ["Find range", "Find standard deviation", "Find variance"] + } + ] + } + ] + }, + { + "name": "General Intelligence and Reasoning", + "tier": "1,2", + "description": "Logical and analytical reasoning covering verbal, non-verbal, and critical thinking", + "target_questions": 25000, + "subtopics": [ + { + "name": "Verbal Reasoning", + "description": "Reasoning with words, letters, and numbers", + "topics": [ + { + "name": "Analogy", + "description": "Finding relationships between pairs of words/numbers/letters", + "question_types": ["Word analogy", "Number analogy", "Letter analogy", "Mixed analogy"] + }, + { + "name": "Classification", + "description": "Finding the odd one out from a group", + "question_types": ["Word classification", "Number classification", "Letter classification", "Mixed classification"] + }, + { + "name": "Number Series", + "description": "Finding patterns in number sequences", + "question_types": ["Find next number", "Find missing number", "Find wrong number"] + }, + { + "name": "Letter Series", + "description": "Finding patterns in letter sequences", + "question_types": ["Find next letters", "Find missing letters", "Pattern identification"] + }, + { + "name": "Alpha-Numeric Series", + "description": "Combined letter-number patterns", + "question_types": ["Find next term", "Find missing term", "Pattern rule"] + }, + { + "name": "Coding-Decoding", + "description": "Encoding and decoding messages using rules", + "question_types": ["Letter coding", "Number coding", "Mixed coding", "Conditional coding"] + } + ] + }, + { + "name": "Logical Reasoning", + "description": "Logical deduction and analytical problems", + "topics": [ + { + "name": "Blood Relations", + "description": "Family relationship problems", + "question_types": ["Direct relation", "Coded relation", "Family tree", "Generation problems"] + }, + { + "name": "Direction and Distance", + "description": "Navigation and displacement problems", + "question_types": ["Find final direction", "Find distance from start", "Shortest distance", "Shadow-based direction"] + }, + { + "name": "Order and Ranking", + "description": "Position-based problems", + "question_types": ["Find rank from top/bottom", "Total number of persons", "Interchange positions", "Between positions"] + }, + { + "name": "Syllogism", + "description": "Logical conclusions from given statements", + "question_types": ["All/Some/No conclusions", "Either-or conclusions", "Possibility-based"] + }, + { + "name": "Seating Arrangement", + "description": "Linear and circular arrangement puzzles", + "question_types": ["Linear row arrangement", "Circular arrangement", "Two-row arrangement", "Complex multi-variable"] + }, + { + "name": "Puzzle", + "description": "Floor, scheduling, and distribution puzzles", + "question_types": ["Floor-based puzzle", "Day/month scheduling", "Distribution puzzle", "Comparison-based ordering"] + } + ] + }, + { + "name": "Non-Verbal Reasoning", + "description": "Reasoning with figures, patterns and spatial concepts", + "topics": [ + { + "name": "Figure Series", + "description": "Finding next figure in a sequence", + "question_types": ["Find next figure", "Find missing figure", "Pattern completion"] + }, + { + "name": "Mirror and Water Image", + "description": "Reflection of figures and text", + "question_types": ["Mirror image of figure", "Water image of figure", "Mirror image of text/numbers"] + }, + { + "name": "Paper Folding and Cutting", + "description": "Predicting result of folding and cutting paper", + "question_types": ["Find pattern after unfolding", "Number of pieces after cutting"] + }, + { + "name": "Embedded Figures", + "description": "Finding figures hidden within complex figures", + "question_types": ["Find embedded figure", "Count embedded shapes"] + }, + { + "name": "Dice and Cube", + "description": "Problems on dice faces and cube painting", + "question_types": ["Opposite face of dice", "Adjacent faces", "Painted cube counting"] + } + ] + }, + { + "name": "Mathematical Reasoning", + "description": "Reasoning using mathematical operations", + "topics": [ + { + "name": "Mathematical Operations", + "description": "Substituted operations and symbols", + "question_types": ["Symbol substitution", "Find correct equation", "Balancing equations"] + }, + { + "name": "Number Puzzles", + "description": "Missing numbers in grids and figures", + "question_types": ["Find missing number in grid", "Find missing in triangle/circle", "Pattern in figures"] + }, + { + "name": "Venn Diagram", + "description": "Representing relationships using Venn diagrams", + "question_types": ["Identify correct Venn diagram", "Count elements in region", "Minimum/maximum in region"] + } + ] + }, + { + "name": "Critical Thinking", + "description": "Evaluating statements, assumptions and conclusions", + "topics": [ + { + "name": "Statement and Conclusion", + "description": "Drawing valid conclusions from statements", + "question_types": ["Which conclusion follows", "Both/neither/either follows"] + }, + { + "name": "Statement and Assumption", + "description": "Identifying implicit assumptions", + "question_types": ["Which assumption is implicit", "Both/neither implicit"] + }, + { + "name": "Cause and Effect", + "description": "Identifying cause-effect relationships", + "question_types": ["Identify cause", "Identify effect", "Independent/dependent events"] + }, + { + "name": "Course of Action", + "description": "Deciding appropriate actions from given situations", + "question_types": ["Which action follows", "Appropriate/inappropriate action"] + } + ] + } + ] + }, + { + "name": "English Language and Comprehension", + "tier": "1,2", + "description": "English vocabulary, grammar, sentence structure, and reading comprehension", + "target_questions": 25000, + "subtopics": [ + { + "name": "Vocabulary", + "description": "Word meanings, usage, and recognition", + "topics": [ + { + "name": "Synonyms", + "description": "Words with similar meanings", + "question_types": ["Choose synonym", "Most similar meaning in context", "Replace with synonym"] + }, + { + "name": "Antonyms", + "description": "Words with opposite meanings", + "question_types": ["Choose antonym", "Most opposite meaning", "Replace with antonym"] + }, + { + "name": "One Word Substitution", + "description": "Single word for a group of words or phrase", + "question_types": ["Find one word for phrase", "Identify correct substitution"] + }, + { + "name": "Idioms and Phrases", + "description": "Common English idioms and their meanings", + "question_types": ["Meaning of idiom", "Use idiom in context", "Choose correct idiom"] + }, + { + "name": "Spelling Correction", + "description": "Identifying correctly/incorrectly spelled words", + "question_types": ["Find misspelled word", "Choose correct spelling", "Correct the spelling"] + }, + { + "name": "Foreign Words", + "description": "Commonly used foreign words and phrases in English", + "question_types": ["Meaning of foreign phrase", "Use in context"] + } + ] + }, + { + "name": "Grammar", + "description": "Rules of English grammar", + "topics": [ + { + "name": "Tenses", + "description": "Past, present, future tenses and their forms", + "question_types": ["Fill in correct tense", "Identify tense", "Correct the tense error"] + }, + { + "name": "Articles", + "description": "Use of a, an, the", + "question_types": ["Fill in correct article", "Identify article error", "No article needed"] + }, + { + "name": "Prepositions", + "description": "Correct use of prepositions", + "question_types": ["Fill in preposition", "Correct preposition error", "Choose appropriate preposition"] + }, + { + "name": "Subject-Verb Agreement", + "description": "Matching subjects with correct verb forms", + "question_types": ["Choose correct verb", "Find agreement error", "Correct the sentence"] + }, + { + "name": "Modals", + "description": "Can, could, may, might, should, would, etc.", + "question_types": ["Choose correct modal", "Modal usage in context"] + }, + { + "name": "Conjunctions", + "description": "Coordinating, subordinating, correlative conjunctions", + "question_types": ["Fill in conjunction", "Choose correct connector"] + } + ] + }, + { + "name": "Sentence Structure", + "description": "Sentence formation, transformation, and improvement", + "topics": [ + { + "name": "Active and Passive Voice", + "description": "Converting between active and passive voice", + "question_types": ["Convert to passive", "Convert to active", "Identify voice"] + }, + { + "name": "Direct and Indirect Speech", + "description": "Converting between direct and indirect narration", + "question_types": ["Convert to indirect speech", "Convert to direct speech", "Identify correct conversion"] + }, + { + "name": "Sentence Improvement", + "description": "Improving parts of sentences for correctness/style", + "question_types": ["Replace underlined part", "Choose best improvement", "No improvement needed"] + }, + { + "name": "Sentence Rearrangement", + "description": "Arranging jumbled sentences or parts in correct order", + "question_types": ["Arrange sentence parts", "Arrange sentences in paragraph", "Find first/last sentence"] + }, + { + "name": "Sentence Completion", + "description": "Completing sentences with appropriate words/phrases", + "question_types": ["Fill in the blank (single)", "Fill in the blank (double)", "Choose correct phrase"] + } + ] + }, + { + "name": "Error Detection", + "description": "Identifying grammatical and usage errors", + "topics": [ + { + "name": "Spot the Error", + "description": "Finding errors in given sentences", + "question_types": ["Identify erroneous part", "No error option", "Multiple error identification"] + }, + { + "name": "Sentence Correction", + "description": "Correcting given erroneous sentences", + "question_types": ["Choose correct version", "Identify and correct error"] + } + ] + }, + { + "name": "Comprehension", + "description": "Understanding and analyzing written passages", + "topics": [ + { + "name": "Reading Comprehension", + "description": "Answering questions based on passages", + "question_types": ["Main idea", "Inference", "Vocabulary in context", "Detail-based", "Tone/attitude"] + }, + { + "name": "Cloze Test", + "description": "Filling blanks in a passage", + "question_types": ["Fill appropriate word", "Grammar-based blank", "Vocabulary-based blank"] + }, + { + "name": "Para Jumbles", + "description": "Arranging sentences to form coherent paragraph", + "question_types": ["Arrange in order", "Find opening sentence", "Find closing sentence"] + } + ] + } + ] + }, + { + "name": "General Awareness", + "tier": "1,2", + "description": "General knowledge covering history, geography, polity, economics, science, and current affairs", + "target_questions": 20000, + "subtopics": [ + { + "name": "History", + "description": "Indian and world history from ancient to modern times", + "topics": [ + { + "name": "Ancient India", + "description": "Indus Valley, Vedic period, Mauryas, Guptas, post-Gupta", + "question_types": ["Who/What/When", "Match the following", "Chronological order", "Identify dynasty/ruler"] + }, + { + "name": "Medieval India", + "description": "Delhi Sultanate, Mughal Empire, Vijayanagara, Bhakti/Sufi", + "question_types": ["Who/What/When", "Match ruler with achievement", "Battle identification"] + }, + { + "name": "Modern India", + "description": "British rule, freedom movement, post-independence", + "question_types": ["Freedom fighter identification", "Movement/event in order", "Governor General/Viceroy"] + }, + { + "name": "World History", + "description": "World wars, revolutions, important treaties", + "question_types": ["Event identification", "Treaty/agreement", "Revolution causes"] + }, + { + "name": "Art and Culture", + "description": "Indian art forms, dance, music, architecture, literature", + "question_types": ["Identify art form", "Match dance with state", "Architecture identification"] + } + ] + }, + { + "name": "Geography", + "description": "Physical, Indian, and world geography", + "topics": [ + { + "name": "Physical Geography", + "description": "Earth, atmosphere, lithosphere, hydrosphere", + "question_types": ["Concept identification", "Layer/zone properties", "Natural phenomena"] + }, + { + "name": "Indian Geography", + "description": "Rivers, mountains, climate, soil, vegetation, states", + "question_types": ["River system", "Mountain pass/peak", "State capital/boundary", "Climate zone"] + }, + { + "name": "World Geography", + "description": "Continents, oceans, countries, capitals", + "question_types": ["Country-capital", "Largest/smallest/longest", "Geographic features"] + }, + { + "name": "Climate and Weather", + "description": "Monsoons, cyclones, climate change, seasons", + "question_types": ["Monsoon mechanism", "Climate type", "Weather phenomena"] + }, + { + "name": "Resources and Agriculture", + "description": "Natural resources, crops, minerals, industries", + "question_types": ["Crop-region", "Mineral-state", "Industry location", "Resource type"] + } + ] + }, + { + "name": "Indian Polity", + "description": "Constitution, governance, judiciary, and political system", + "topics": [ + { + "name": "Indian Constitution", + "description": "Preamble, fundamental rights, DPSP, duties, amendments", + "question_types": ["Article identification", "Amendment detail", "Right classification", "Feature borrowed from"] + }, + { + "name": "Parliament and State Legislature", + "description": "Lok Sabha, Rajya Sabha, state assemblies, bills", + "question_types": ["Functions", "Composition", "Bill passage process", "Speaker/Chairman powers"] + }, + { + "name": "Judiciary", + "description": "Supreme Court, High Courts, lower courts, judicial review", + "question_types": ["Jurisdiction", "Appointment process", "Writ identification", "Landmark judgement"] + }, + { + "name": "Governance", + "description": "President, PM, Council of Ministers, Governor, CAG", + "question_types": ["Powers and functions", "Appointment", "Removal process", "Emergency provisions"] + }, + { + "name": "Panchayati Raj and Local Governance", + "description": "73rd and 74th amendments, three-tier system", + "question_types": ["Structure", "Functions", "Elections", "Constitutional provisions"] + } + ] + }, + { + "name": "Economics", + "description": "Indian economy, banking, fiscal policy, international trade", + "topics": [ + { + "name": "Indian Economy Basics", + "description": "GDP, GNP, NI, sectors of economy, planning", + "question_types": ["Define term", "Sector identification", "Economic indicator", "Five Year Plan"] + }, + { + "name": "Banking and Finance", + "description": "RBI, commercial banks, monetary policy, financial markets", + "question_types": ["RBI functions", "Bank rate/repo rate", "Financial term", "Banking instrument"] + }, + { + "name": "Budget and Fiscal Policy", + "description": "Union budget, taxes, fiscal deficit, public debt", + "question_types": ["Tax type", "Budget component", "Deficit type", "Revenue/capital"] + }, + { + "name": "International Organizations", + "description": "IMF, World Bank, WTO, ASEAN, BRICS, G20", + "question_types": ["Organization HQ", "Member countries", "Functions", "Formation year"] + }, + { + "name": "Government Schemes", + "description": "Major government welfare and development schemes", + "question_types": ["Scheme objective", "Launch year", "Ministry responsible", "Beneficiary"] + } + ] + }, + { + "name": "Science", + "description": "Physics, chemistry, biology, and technology", + "topics": [ + { + "name": "Physics", + "description": "Mechanics, heat, light, sound, electricity, magnetism", + "question_types": ["Law/principle identification", "Unit/dimension", "Application of concept", "Inventor/discovery"] + }, + { + "name": "Chemistry", + "description": "Elements, compounds, reactions, acids and bases, everyday chemistry", + "question_types": ["Chemical formula", "Reaction type", "Element property", "Everyday chemistry application"] + }, + { + "name": "Biology", + "description": "Cell biology, human body, diseases, nutrition, ecology", + "question_types": ["Organ/system function", "Disease-cause", "Vitamin-deficiency", "Ecology concept"] + }, + { + "name": "Space and Technology", + "description": "ISRO missions, satellites, recent tech developments", + "question_types": ["Mission identification", "Satellite purpose", "Technology application", "First achievements"] + }, + { + "name": "Computer Awareness", + "description": "Computer fundamentals, MS Office, Internet, networking, cybersecurity", + "question_types": ["Term definition", "Shortcut key", "Component function", "Software feature"] + } + ] + }, + { + "name": "Static GK", + "description": "Fixed factual knowledge that doesn't change frequently", + "topics": [ + { + "name": "Books and Authors", + "description": "Famous books and their authors", + "question_types": ["Match book-author", "Identify author", "Award-winning book"] + }, + { + "name": "Important Days and Dates", + "description": "National and international important days", + "question_types": ["Date identification", "Theme of the year", "Organizing body"] + }, + { + "name": "Awards and Honours", + "description": "Bharat Ratna, Padma awards, Nobel Prize, national awards", + "question_types": ["Award-winner", "Award category", "First recipient", "Recent winner"] + }, + { + "name": "Sports", + "description": "Major sports events, trophies, records, personalities", + "question_types": ["Trophy-sport", "Record holder", "Venue/host country", "Player-team"] + }, + { + "name": "National Symbols and Firsts", + "description": "National symbols, firsts in India and world", + "question_types": ["Identify symbol", "First person/event", "National identifier"] + } + ] + } + ] + } + ] +} diff --git a/generators/__init__.py b/generators/__init__.py new file mode 100644 index 0000000..6cec990 --- /dev/null +++ b/generators/__init__.py @@ -0,0 +1 @@ +# SSCTopper Question Generators diff --git a/generators/__pycache__/__init__.cpython-314.pyc b/generators/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..536e82bda77ab6bc88f6821247c1d7eab61978b2 GIT binary patch literal 157 zcmdPq^MY(Pc>LlA>9gC?WjN`@jPApbK+@|Kl;XmM&$ zv3^ocVsds;euchEesXDUYF&ryk0@& gEe@O9{FKt1RJ$Tppiv;pib0G|%#4hTMa)1J09&9XumAu6 literal 0 HcmV?d00001 diff --git a/generators/__pycache__/base.cpython-314.pyc b/generators/__pycache__/base.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3b6630320761763c452c58df68948c23d99574d GIT binary patch literal 4712 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 HiRS+WJ#H&G literal 0 HcmV?d00001 diff --git a/generators/__pycache__/gk_generator.cpython-314.pyc b/generators/__pycache__/gk_generator.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ae4c9f6aaabf82c19a5fb7a98527b3a5ea75146 GIT binary patch literal 28747 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 literal 0 HcmV?d00001 diff --git a/generators/__pycache__/quant_generator.cpython-314.pyc b/generators/__pycache__/quant_generator.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e65df6d42de6c7b7e66de0955780e19f692eb4f2 GIT binary patch literal 51568 zcmd_T33waVohJwo00|O2aPg9OfTu`NBE>_8byJim>Y^o(1SOj$4FVuVn+H@Nb)YjD zclUNjNxtr8Y-@zr(ibF=#;{{e;IushrJZidx#*JJR-&3jncRGmu^wQ3U zzf`MKIGS=F%0u0EokRkObtvl;BKW>&-gYG<`iXil>HVp)iPPv#{a)6uZ%32n-> z1uN68lG2nSWp)S3NM~1i;`&&t+*Br^|G0Ylza%XdU@@DgT@@MmV3T6v>jI+i{ zm0de)vX|I(#i~j5tl3^_&t!WR`#O6T+q2r&+x2X(5PR8dugG3z&tZGT_Hw&{?UmRo z?73{O6zTHV-a4GiXM5{$u7K^8Vb93+%CTo+dlg7$W_y*`v#>oIN@ZnxRrbPS)ok?> zHTI$%r&TKVMwO~e^+Z9Hsza=-YPR-?x+;~uIMxo8swb+eR8Q0@j%!bzkP<8Y#O_Bc zsB75&#!A0G=hU2YdUj&gIXiLQW9#%{ch2pxotpFbW+!H*ZHGP69?m&C!`a60zpt;$ z)^+%JbB=V{h9u&7`#M(P+`cw9mugw%GGVMxC>+v;M-Am2*kMhP4e1F%ABIIGIJSK(or5F(eHz zmCB(Sil?=s$uQ(}VvBk;4$TH}SLOZ>)I~$m72KsB)^3X5r&7f_#;EGPs#1BiLz&Wz zs*|x7Fm?iet5m}o!>-tn_UhJ5oin@`Pn|hrh-EeC7|!XSn&Ico8j8Q54vk&o(Bj zAooST#-8KIVZESH^nxMQ26EYlHL-I>)qr*`qggeV z(cQ{QE%%;b%~GiyIm7BriU~Yc#!ZT@h&`z{<5;`c19KT(t;2wF8dzzw2EVBs-xvkD zVf91x>^Il!+1!*p``xtPG`y2(ylI+YYpeylxoftb@=eQIGquW*o4T#N`GY@FjtED- zvh|hY1Mg}cYxjId?uvIcKh{r*cQt>gIM$KF8b@xdZn3{CuPl~Vfg=w&8XfuQy{w1q zz3`g7XG+<7|C)C1{g7I=TKP6fcUsfDGuBo3+n&*^@tPF1NIe6YVtsxA-xsghYmw&?jFm!%86_@i zQ_dxp*d2;d5D6SFq=#Q4Olf8F3&=d5kQXPZ6ivCYNL+h)dW zQT1?Cz0aR7r>3(XYixULe$&B;^Ao;_(TT~4*-L;Yz%vE-WcynN&rZ0`QaZ|SY-VzD z=EB7ExNVvhz()^qi+4_XY@?TKb6WI|?c4v!<~Q3tQ_hKLH;U=yW=1DHQ$GKCW#PE^ z^$TY`(>8xWEHgI{5wiM~nAKG(VUx|D-;K;zDb715=RCinuf$v5ux++|MCjqIZ_G(2 znrt7N6O)Q{Z+=;GpRfu?AJY4DPh&P$MEmsJzfTXlPg&fjkM4co8`XXKSl!R24FQG5 zT#iykv-$=O96i+4AJuWr>2Xgq6Mx(@Q_(E?J25>Q)p%zvaP)wv#y87xlyfxGb#`XL zg~U#;*E8*oYNnhQ8?8~TYi4>ns&mcE;lAuRB8zGnrc;kZwFD5O8qY;xo*tdK*m7{jH8+JkeJwtp3t-gi;abLHDA(6=j_~kE{6w?&Qq+W_ z5pj5o_}KX5_TX^@k$mH5z-Ls;M)NaAeqpf8AH239xI0|Aut_jH`pm(fWf|_7ist8n z>MQ?^#N(?tOM|uqtR5EDe_r-ZPh6ZGN^nXb$!X=Eloi_bf&8v%zY?Qip5C z@_>8UyeZTl>JC?hyTV%|YTmf>o)J%ZF8kWyMFhbvv=1$ zx{Nlco1YDI2HFFzV9m|?U1RJ391Yb7#m$#{?v+%0DgTB1;JMI-aObTip=8_TBg^K}C39`?(vo>g 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 literal 0 HcmV?d00001 diff --git a/generators/__pycache__/reasoning_generator.cpython-314.pyc b/generators/__pycache__/reasoning_generator.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a111297f50c7bb75d7974439462e8cdc94ef68e GIT binary patch literal 34576 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 F{{ZSTUrGP~ literal 0 HcmV?d00001 diff --git a/generators/base.py b/generators/base.py new file mode 100644 index 0000000..1a8f54f --- /dev/null +++ b/generators/base.py @@ -0,0 +1,89 @@ + + +#!/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}" diff --git a/generators/english_generator.py b/generators/english_generator.py new file mode 100644 index 0000000..d363a2b --- /dev/null +++ b/generators/english_generator.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 +""" +English Language & Comprehension Question Generator for SSC CGL. +Generates ~25,000 questions covering vocabulary, grammar, sentence structure, error detection. +""" +import random +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +from generators.base import make_question, get_qtid, get_db, insert_questions_batch + +SUBJECT = "English Language and Comprehension" + +# ============ WORD BANKS ============ + +SYNONYMS = [ + ("Abundant", "Plentiful", ["Scarce", "Meager", "Rare"]), + ("Accurate", "Precise", ["Wrong", "Vague", "Incorrect"]), + ("Admire", "Respect", ["Despise", "Hate", "Ignore"]), + ("Affluent", "Wealthy", ["Poor", "Needy", "Destitute"]), + ("Agile", "Nimble", ["Clumsy", "Slow", "Stiff"]), + ("Amiable", "Friendly", ["Hostile", "Rude", "Cold"]), + ("Ancient", "Old", ["Modern", "New", "Recent"]), + ("Arduous", "Difficult", ["Easy", "Simple", "Effortless"]), + ("Audacious", "Bold", ["Timid", "Meek", "Cowardly"]), + ("Authentic", "Genuine", ["Fake", "False", "Counterfeit"]), + ("Benevolent", "Kind", ["Cruel", "Malicious", "Harsh"]), + ("Bizarre", "Strange", ["Normal", "Usual", "Ordinary"]), + ("Candid", "Frank", ["Deceptive", "Dishonest", "Sly"]), + ("Cautious", "Careful", ["Reckless", "Careless", "Rash"]), + ("Comprehend", "Understand", ["Misunderstand", "Confuse", "Ignore"]), + ("Conceal", "Hide", ["Reveal", "Expose", "Display"]), + ("Contempt", "Scorn", ["Respect", "Admiration", "Regard"]), + ("Courage", "Bravery", ["Cowardice", "Fear", "Timidity"]), + ("Delight", "Joy", ["Sorrow", "Grief", "Misery"]), + ("Diligent", "Hardworking", ["Lazy", "Idle", "Indolent"]), + ("Diminish", "Reduce", ["Increase", "Enlarge", "Expand"]), + ("Eloquent", "Expressive", ["Inarticulate", "Stammering", "Dull"]), + ("Enormous", "Huge", ["Tiny", "Small", "Minute"]), + ("Eternal", "Everlasting", ["Temporary", "Brief", "Fleeting"]), + ("Exquisite", "Beautiful", ["Ugly", "Plain", "Crude"]), + ("Feeble", "Weak", ["Strong", "Powerful", "Mighty"]), + ("Ferocious", "Fierce", ["Gentle", "Mild", "Tame"]), + ("Frigid", "Cold", ["Hot", "Warm", "Tropical"]), + ("Generous", "Liberal", ["Stingy", "Miserly", "Tight"]), + ("Gratitude", "Thankfulness", ["Ingratitude", "Ungratefulness", "Resentment"]), + ("Halt", "Stop", ["Continue", "Proceed", "Advance"]), + ("Hazardous", "Dangerous", ["Safe", "Secure", "Harmless"]), + ("Hostile", "Unfriendly", ["Friendly", "Warm", "Kind"]), + ("Immense", "Vast", ["Tiny", "Small", "Little"]), + ("Impeccable", "Flawless", ["Faulty", "Defective", "Imperfect"]), + ("Jubilant", "Joyful", ["Sad", "Gloomy", "Depressed"]), + ("Keen", "Eager", ["Reluctant", "Unwilling", "Indifferent"]), + ("Laudable", "Praiseworthy", ["Blameworthy", "Shameful", "Disgraceful"]), + ("Lucid", "Clear", ["Confusing", "Vague", "Obscure"]), + ("Magnificent", "Splendid", ["Ordinary", "Plain", "Dull"]), + ("Meticulous", "Careful", ["Careless", "Sloppy", "Negligent"]), + ("Mundane", "Ordinary", ["Extraordinary", "Unusual", "Special"]), + ("Novice", "Beginner", ["Expert", "Veteran", "Professional"]), + ("Obstinate", "Stubborn", ["Flexible", "Yielding", "Compliant"]), + ("Opulent", "Luxurious", ["Poor", "Shabby", "Modest"]), + ("Pacify", "Calm", ["Agitate", "Provoke", "Irritate"]), + ("Prudent", "Wise", ["Foolish", "Reckless", "Imprudent"]), + ("Replenish", "Refill", ["Drain", "Empty", "Deplete"]), + ("Serene", "Calm", ["Turbulent", "Agitated", "Noisy"]), + ("Tedious", "Boring", ["Interesting", "Exciting", "Engaging"]), + ("Trivial", "Insignificant", ["Important", "Significant", "Vital"]), + ("Ubiquitous", "Everywhere", ["Rare", "Scarce", "Uncommon"]), + ("Valiant", "Brave", ["Cowardly", "Timid", "Fearful"]), + ("Verbose", "Wordy", ["Concise", "Brief", "Terse"]), + ("Wrath", "Anger", ["Calm", "Peace", "Happiness"]), + ("Zealous", "Enthusiastic", ["Apathetic", "Indifferent", "Passive"]), +] + +ANTONYMS = [ + ("Accept", "Reject"), ("Advance", "Retreat"), ("Ancient", "Modern"), + ("Arrival", "Departure"), ("Ascend", "Descend"), ("Bold", "Timid"), + ("Brave", "Cowardly"), ("Bright", "Dim"), ("Calm", "Agitated"), + ("Create", "Destroy"), ("Dawn", "Dusk"), ("Defend", "Attack"), + ("Expand", "Contract"), ("Forget", "Remember"), ("Generous", "Miserly"), + ("Guilty", "Innocent"), ("Humble", "Proud"), ("Import", "Export"), + ("Joy", "Sorrow"), ("Knowledge", "Ignorance"), ("Liberty", "Captivity"), + ("Major", "Minor"), ("Natural", "Artificial"), ("Optimist", "Pessimist"), + ("Peace", "War"), ("Rapid", "Slow"), ("Rigid", "Flexible"), + ("Simple", "Complex"), ("Temporary", "Permanent"), ("Victory", "Defeat"), + ("Wisdom", "Folly"), ("Zenith", "Nadir"), ("Transparent", "Opaque"), + ("Voluntary", "Compulsory"), ("Shallow", "Deep"), ("Fertile", "Barren"), + ("Concord", "Discord"), ("Benign", "Malignant"), ("Prolific", "Barren"), + ("Affluent", "Destitute"), +] + +ONE_WORD_SUBS = [ + ("A person who loves books", "Bibliophile", ["Bibliographer", "Librarian", "Bookworm"]), + ("Government by the people", "Democracy", ["Monarchy", "Autocracy", "Oligarchy"]), + ("One who hates mankind", "Misanthrope", ["Philanthropist", "Misogynist", "Anthropologist"]), + ("A person who speaks two languages", "Bilingual", ["Polyglot", "Monoglot", "Linguist"]), + ("A person who walks in sleep", "Somnambulist", ["Insomniac", "Sleepwalker", "Narcoleptic"]), + ("Fear of water", "Hydrophobia", ["Aquaphobia", "Claustrophobia", "Acrophobia"]), + ("Fear of heights", "Acrophobia", ["Hydrophobia", "Claustrophobia", "Agoraphobia"]), + ("One who eats human flesh", "Cannibal", ["Carnivore", "Omnivore", "Herbivore"]), + ("A word that is opposite in meaning", "Antonym", ["Synonym", "Homonym", "Acronym"]), + ("Killing of a king", "Regicide", ["Homicide", "Genocide", "Fratricide"]), + ("A place for keeping bees", "Apiary", ["Aviary", "Aquarium", "Nursery"]), + ("One who knows everything", "Omniscient", ["Omnipresent", "Omnipotent", "Omnivore"]), + ("Medicine that kills germs", "Antiseptic", ["Antibiotic", "Antidote", "Analgesic"]), + ("A person who is 100 years old", "Centenarian", ["Octogenarian", "Nonagenarian", "Septuagenarian"]), + ("Study of stars", "Astronomy", ["Astrology", "Cosmology", "Astrophysics"]), + ("Government by a single person", "Autocracy", ["Democracy", "Monarchy", "Theocracy"]), + ("One who does not believe in God", "Atheist", ["Theist", "Agnostic", "Pagan"]), + ("A speech delivered without preparation", "Extempore", ["Impromptu", "Rehearsed", "Deliberate"]), + ("One who lives on vegetables", "Vegetarian", ["Vegan", "Carnivore", "Omnivore"]), + ("A place for keeping dead bodies", "Mortuary", ["Cemetery", "Crematorium", "Mausoleum"]), + ("That which cannot be read", "Illegible", ["Eligible", "Legible", "Indelible"]), + ("A person who cannot be corrected", "Incorrigible", ["Incurable", "Invincible", "Inevitable"]), + ("One who is present everywhere", "Omnipresent", ["Omniscient", "Omnipotent", "Omnivore"]), + ("One who looks on the bright side", "Optimist", ["Pessimist", "Realist", "Fatalist"]), + ("Study of ancient things", "Archaeology", ["Anthropology", "Paleontology", "Geology"]), +] + +IDIOMS = [ + ("Break the ice", "To initiate conversation in a social setting", ["To break something", "To melt ice", "To cool down"]), + ("Burn the midnight oil", "To work or study late into the night", ["To waste oil", "To start a fire", "To cook at night"]), + ("Cry over spilt milk", "To regret something that cannot be undone", ["To cry while drinking milk", "To waste milk", "To be sad about dairy"]), + ("Hit the nail on the head", "To be exactly right", ["To do carpentry", "To hurt oneself", "To break something"]), + ("A piece of cake", "Something very easy", ["A type of dessert", "A small portion", "A bakery item"]), + ("Bite the bullet", "To face a difficult situation bravely", ["To eat ammunition", "To hurt teeth", "To be violent"]), + ("Cost an arm and a leg", "Very expensive", ["Physical injury", "Amputation", "Medical procedure"]), + ("Let the cat out of the bag", "To reveal a secret", ["To free an animal", "To open a bag", "To go shopping"]), + ("Once in a blue moon", "Very rarely", ["During full moon", "Monthly", "Nightly"]), + ("Raining cats and dogs", "Raining very heavily", ["Animals falling", "Pet show", "Zoo visit"]), + ("Spill the beans", "To reveal secret information", ["To cook", "To waste food", "To plant seeds"]), + ("The ball is in your court", "It is your turn to take action", ["Playing tennis", "Court hearing", "Ball game"]), + ("Under the weather", "Feeling unwell", ["In rain", "Below clouds", "Bad climate"]), + ("Actions speak louder than words", "What you do matters more than what you say", ["Being noisy", "Shouting", "Speaking loudly"]), + ("Beat around the bush", "To avoid the main topic", ["Gardening", "Playing in bush", "Walking in forest"]), + ("Burning bridges", "Destroying relationships", ["Arson", "Building fire", "Demolition"]), + ("Every cloud has a silver lining", "Good things come after bad", ["Weather forecast", "Cloud watching", "Silver mining"]), + ("Keep your chin up", "Stay positive", ["Posture advice", "Exercise tip", "Looking upward"]), + ("Back to the drawing board", "Start over", ["Art class", "Going backwards", "Drawing pictures"]), + ("Barking up the wrong tree", "Making a wrong assumption", ["Disturbing a dog", "Climbing trees", "Forest activity"]), +] + +# ============ GENERATORS ============ + +def gen_synonyms(conn, count=2500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Vocabulary", "Synonyms", "Choose synonym") + if not qtid: return questions + for _ in range(count): + word, syn, wrongs = random.choice(SYNONYMS) + questions.append(make_question(qtid, + f"Choose the synonym of '{word}':", + syn, wrongs, f"'{word}' means '{syn}'", 1)) + return questions + + +def gen_antonyms(conn, count=2500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Vocabulary", "Antonyms", "Choose antonym") + if not qtid: return questions + for _ in range(count): + word, ant = random.choice(ANTONYMS) + other_ants = [a[1] for a in random.sample(ANTONYMS, 3) if a[0] != word][:3] + if len(other_ants) < 3: + other_ants = ["None", "All", "Some"][:3] + questions.append(make_question(qtid, + f"Choose the antonym of '{word}':", + ant, other_ants, f"Opposite of '{word}' is '{ant}'", 1)) + return questions + + +def gen_one_word(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Vocabulary", "One Word Substitution", "Find one word for phrase") + if not qtid: return questions + for _ in range(count): + phrase, word, wrongs = random.choice(ONE_WORD_SUBS) + questions.append(make_question(qtid, + f"One word for: '{phrase}'", + word, wrongs, f"'{phrase}' = {word}", 1)) + return questions + + +def gen_idioms(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Vocabulary", "Idioms and Phrases", "Meaning of idiom") + if not qtid: return questions + for _ in range(count): + idiom, meaning, wrongs = random.choice(IDIOMS) + questions.append(make_question(qtid, + f"What does the idiom '{idiom}' mean?", + meaning, wrongs, f"'{idiom}' = {meaning}", 1)) + return questions + + +def gen_spelling(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Vocabulary", "Spelling Correction", "Choose correct spelling") + if not qtid: return questions + words = ["Accommodation", "Achievement", "Acknowledge", "Acquaintance", "Aggressive", + "Apparently", "Argument", "Assassination", "Beautiful", "Beginning", + "Believe", "Bureaucracy", "Calendar", "Changeable", "Committed", + "Conscience", "Conscious", "Definitely", "Dilemma", "Disappear", + "Disappoint", "Discipline", "Embarrass", "Environment", "Exaggerate", + "Existence", "Experience", "Fascinate", "February", "Fluorescent", + "Foreign", "Forty", "Government", "Guarantee", "Harass", + "Hierarchy", "Humorous", "Hygiene", "Immediately", "Independent", + "Intelligence", "Jewellery", "Judgement", "Knowledge", "Leisure", + "License", "Maintenance", "Mediterranean", "Millennium", "Necessary", + "Noticeable", "Occasion", "Occurrence", "Parliament", "Perseverance", + "Pneumonia", "Possession", "Privilege", "Pronunciation", "Psychology", + "Questionnaire", "Receive", "Recommend", "Rhythm", "Schedule", + "Separate", "Successful", "Supersede", "Surprise", "Threshold", + "Tomorrow", "Tyranny", "Unnecessary", "Vacuum", "Vegetable", + "Wednesday", "Weird"] + for _ in range(count): + w = random.choice(words) + # Create misspellings + misspells = [] + for _ in range(3): + idx = random.randint(1, len(w) - 2) + chars = list(w) + chars[idx] = random.choice('aeiou') if chars[idx] not in 'aeiou' else random.choice('bcdfg') + m = "".join(chars) + if m != w: + misspells.append(m) + while len(misspells) < 3: + misspells.append(w[:-1] + random.choice('aeioust')) + questions.append(make_question(qtid, + f"Choose the correctly spelled word:", + w, misspells[:3], f"Correct spelling: {w}", 1)) + return questions + + +def gen_tenses(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Grammar", "Tenses", "Fill in correct tense") + if not qtid: return questions + templates = [ + ("She ___ to school every day.", "goes", ["go", "went", "going"], "Simple Present"), + ("They ___ playing football yesterday.", "were", ["was", "are", "is"], "Past Continuous"), + ("He ___ the work by tomorrow.", "will finish", ["finished", "finishes", "finishing"], "Simple Future"), + ("I ___ this book already.", "have read", ["had read", "read", "reading"], "Present Perfect"), + ("She ___ dinner when I arrived.", "was cooking", ["cooked", "cooks", "cooking"], "Past Continuous"), + ("We ___ here since morning.", "have been", ["are", "were", "was"], "Present Perfect Continuous"), + ("The train ___ before we reached.", "had left", ["left", "leaves", "leaving"], "Past Perfect"), + ("By next year, I ___ my degree.", "will have completed", ["complete", "completed", "completing"], "Future Perfect"), + ("He ___ a letter now.", "is writing", ["writes", "wrote", "written"], "Present Continuous"), + ("They ___ the match last week.", "won", ["win", "wins", "winning"], "Simple Past"), + ("She ___ the piano since childhood.", "has been playing", ["plays", "played", "play"], "Present Perfect Continuous"), + ("I ___ you tomorrow.", "will call", ["called", "call", "calling"], "Simple Future"), + ] + for _ in range(count): + q_text, correct, wrongs, tense = random.choice(templates) + questions.append(make_question(qtid, f"Fill in the blank: {q_text}", + correct, wrongs, f"Tense: {tense}", 1)) + return questions + + +def gen_articles(conn, count=1000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Grammar", "Articles", "Fill in correct article") + if not qtid: return questions + templates = [ + ("___ apple a day keeps the doctor away.", "An", ["A", "The", "No article"]), + ("He is ___ honest man.", "an", ["a", "the", "no article"]), + ("___ sun rises in the east.", "The", ["A", "An", "No article"]), + ("She is ___ doctor.", "a", ["an", "the", "no article"]), + ("I saw ___ elephant in the zoo.", "an", ["a", "the", "no article"]), + ("___ Ganges is a holy river.", "The", ["A", "An", "No article"]), + ("He gave me ___ useful tip.", "a", ["an", "the", "no article"]), + ("___ gold is a precious metal.", "No article", ["A", "An", "The"]), + ("She is ___ European.", "a", ["an", "the", "no article"]), + ("I need ___ umbrella.", "an", ["a", "the", "no article"]), + ] + for _ in range(count): + q_text, correct, wrongs = random.choice(templates) + questions.append(make_question(qtid, f"Fill in the correct article: {q_text}", + correct, wrongs, f"Article rule applied", 1)) + return questions + + +def gen_prepositions(conn, count=1000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Grammar", "Prepositions", "Fill in preposition") + if not qtid: return questions + templates = [ + ("The book is ___ the table.", "on", ["in", "at", "by"]), + ("She arrived ___ Monday.", "on", ["in", "at", "by"]), + ("He lives ___ Mumbai.", "in", ["on", "at", "by"]), + ("The meeting is ___ 3 PM.", "at", ["in", "on", "by"]), + ("I have been waiting ___ morning.", "since", ["for", "from", "by"]), + ("She is good ___ mathematics.", "at", ["in", "on", "with"]), + ("He is fond ___ music.", "of", ["with", "in", "at"]), + ("The cat jumped ___ the wall.", "over", ["on", "in", "at"]), + ("She is interested ___ painting.", "in", ["on", "at", "by"]), + ("He walked ___ the park.", "through", ["in", "on", "at"]), + ("They traveled ___ train.", "by", ["in", "on", "with"]), + ("The match starts ___ 5 o'clock.", "at", ["in", "on", "by"]), + ] + for _ in range(count): + q_text, correct, wrongs = random.choice(templates) + questions.append(make_question(qtid, f"Fill in the correct preposition: {q_text}", + correct, wrongs, f"Preposition: {correct}", 1)) + return questions + + +def gen_voice(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Sentence Structure", "Active and Passive Voice", "Convert to passive") + if not qtid: return questions + templates = [ + ("She writes a letter.", "A letter is written by her.", ["A letter was written by her.", "A letter were written by her.", "A letter has written by her."]), + ("He plays cricket.", "Cricket is played by him.", ["Cricket was played by him.", "Cricket were played by him.", "Cricket has played by him."]), + ("They are building a house.", "A house is being built by them.", ["A house was being built by them.", "A house has been built by them.", "A house is built by them."]), + ("She cooked food.", "Food was cooked by her.", ["Food is cooked by her.", "Food has been cooked by her.", "Food was being cooked by her."]), + ("I have finished the work.", "The work has been finished by me.", ["The work was finished by me.", "The work is finished by me.", "The work had been finished by me."]), + ("The teacher teaches the students.", "The students are taught by the teacher.", ["The students were taught by the teacher.", "The students has been taught by the teacher.", "The students is taught by the teacher."]), + ("He will write a book.", "A book will be written by him.", ["A book would be written by him.", "A book shall be written by him.", "A book is written by him."]), + ("Ram killed Ravana.", "Ravana was killed by Ram.", ["Ravana is killed by Ram.", "Ravana has been killed by Ram.", "Ravana were killed by Ram."]), + ] + for _ in range(count): + active, passive, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Convert to passive voice: '{active}'", + passive, wrongs, f"Passive: {passive}", 1)) + return questions + + +def gen_direct_indirect(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Sentence Structure", "Direct and Indirect Speech", "Convert to indirect speech") + if not qtid: return questions + templates = [ + ('He said, "I am happy."', 'He said that he was happy.', + ['He said that he is happy.', 'He said that I am happy.', 'He told that he was happy.']), + ('She said, "I will come tomorrow."', 'She said that she would come the next day.', + ['She said that she will come tomorrow.', 'She told she would come next day.', 'She said she will come.']), + ('He asked, "Where do you live?"', 'He asked where I lived.', + ['He asked where do I live.', 'He asked that where I lived.', 'He asked me where I live.']), + ('She said, "I have finished my work."', 'She said that she had finished her work.', + ['She said that she has finished her work.', 'She told she had finished work.', 'She said she finished her work.']), + ('The teacher said, "The Earth revolves around the Sun."', 'The teacher said that the Earth revolves around the Sun.', + ['The teacher said the Earth revolved around the Sun.', 'The teacher told the Earth revolves around Sun.', 'The teacher said Earth revolving around Sun.']), + ] + for _ in range(count): + direct, indirect, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Convert to indirect speech: {direct}", + indirect, wrongs, f"Indirect: {indirect}", 1)) + return questions + + +def gen_sentence_improvement(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Sentence Structure", "Sentence Improvement", "Replace underlined part") + if not qtid: return questions + templates = [ + ("He don't know the answer.", "doesn't know", ["don't knows", "didn't knew", "not know"]), + ("She is more taller than her sister.", "taller", ["most taller", "more tall", "tallest"]), + ("I am going to market.", "to the market", ["in market", "for market", "at market"]), + ("He told to me a story.", "told me", ["said to me", "tell me", "telling me"]), + ("Each of the boys have done their work.", "has done his", ["have done his", "has did their", "have done their"]), + ("One should do his duty.", "one's duty", ["their duty", "your duty", "our duty"]), + ("She is knowing the answer.", "knows", ["is know", "was knowing", "has knowing"]), + ("I am having a car.", "have", ["is having", "has", "having"]), + ("He prevented me to go.", "from going", ["for going", "about going", "of going"]), + ("She is elder than me.", "older than I", ["elder than I", "more elder than me", "oldest than me"]), + ] + for _ in range(count): + sentence, correct, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Improve the sentence: '{sentence}'", + correct, wrongs, f"Correct: {correct}", 2)) + return questions + + +def gen_error_detection(conn, count=2500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Error Detection", "Spot the Error", "Identify erroneous part") + if not qtid: return questions + templates = [ + ("He go to school daily.", "Part A: 'go' should be 'goes'", ["Part B: 'to' is wrong", "Part C: 'daily' is wrong", "No error"]), + ("She don't like ice cream.", "Part A: 'don't' should be 'doesn't'", ["Part B: 'like' is wrong", "Part C is wrong", "No error"]), + ("The news are good.", "Part A: 'are' should be 'is'", ["Part B: 'good' is wrong", "Part C is wrong", "No error"]), + ("Mathematics are my favourite subject.", "Part A: 'are' should be 'is'", ["Part B is wrong", "Part C is wrong", "No error"]), + ("Each of the students have passed.", "Part B: 'have' should be 'has'", ["Part A is wrong", "Part C is wrong", "No error"]), + ("He is more stronger than me.", "Part A: 'more stronger' should be 'stronger'", ["Part B is wrong", "Part C is wrong", "No error"]), + ("I am agree with you.", "Part A: 'am agree' should be 'agree'", ["Part B is wrong", "Part C is wrong", "No error"]), + ("The furniture are expensive.", "Part A: 'are' should be 'is'", ["Part B is wrong", "Part C is wrong", "No error"]), + ("He gave me a advise.", "Part B: 'advise' should be 'advice'", ["Part A is wrong", "Part C is wrong", "No error"]), + ("She discuss about the matter.", "Part A: 'discuss about' should be 'discussed'", ["Part B is wrong", "Part C is wrong", "No error"]), + ] + for _ in range(count): + sentence, error, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Find the error in: '{sentence}'", + error, wrongs, f"Error: {error}", 2)) + return questions + + +def gen_fill_blanks(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Sentence Structure", "Sentence Completion", "Fill in the blank (single)") + if not qtid: return questions + templates = [ + ("Hard work is the key to ___.", "success", ["failure", "defeat", "loss"]), + ("The weather is ___ today.", "pleasant", ["unpleasant", "horrible", "terrible"]), + ("She showed great ___ in the face of danger.", "courage", ["cowardice", "fear", "hesitation"]), + ("The doctor ___ the patient carefully.", "examined", ["ignored", "neglected", "avoided"]), + ("He has a strong ___ for justice.", "passion", ["hatred", "dislike", "disregard"]), + ("The students were ___ to learn the new topic.", "eager", ["reluctant", "unwilling", "indifferent"]), + ("Her ___ attitude made everyone uncomfortable.", "arrogant", ["humble", "polite", "modest"]), + ("The company achieved remarkable ___ this year.", "growth", ["decline", "loss", "failure"]), + ("He is ___ of solving complex problems.", "capable", ["incapable", "unable", "unfit"]), + ("The ___ of the river was very strong after the rain.", "current", ["calm", "stillness", "silence"]), + ("She has an ___ personality that attracts people.", "amiable", ["hostile", "rude", "cold"]), + ("The government ___ new policies for education.", "introduced", ["removed", "cancelled", "deleted"]), + ] + for _ in range(count): + sentence, correct, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Fill in the blank: {sentence}", + correct, wrongs, f"Answer: {correct}", 1)) + return questions + + +def gen_sentence_rearrangement(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Sentence Structure", "Sentence Rearrangement", "Arrange sentence parts") + if not qtid: return questions + templates = [ + (["The early bird", "catches", "the worm"], "The early bird catches the worm", + ["catches the worm the early bird", "the worm catches the early bird", "the worm the early bird catches"]), + (["Knowledge is", "better than", "wealth"], "Knowledge is better than wealth", + ["better than wealth knowledge is", "wealth is better than knowledge", "is knowledge better than wealth"]), + (["Honesty", "is the", "best policy"], "Honesty is the best policy", + ["is the best policy honesty", "the best policy is honesty", "policy best is the honesty"]), + (["United we stand", "divided", "we fall"], "United we stand divided we fall", + ["divided we fall united we stand", "we fall divided united we stand", "stand united we divided fall we"]), + (["Practice makes", "a man", "perfect"], "Practice makes a man perfect", + ["a man perfect practice makes", "makes practice a man perfect", "perfect a man makes practice"]), + ] + for _ in range(count): + parts, correct, wrongs = random.choice(templates) + random.shuffle(parts) + questions.append(make_question(qtid, + f"Arrange in correct order: {' / '.join(parts)}", + correct, wrongs, f"Correct order: {correct}", 2)) + return questions + + +def gen_cloze_test(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Comprehension", "Cloze Test", "Fill appropriate word") + if not qtid: return questions + templates = [ + ("Education is the most powerful ___ to change the world.", "weapon", ["weakness", "problem", "barrier"]), + ("The ___ of success is hard work and dedication.", "foundation", ["destruction", "failure", "absence"]), + ("A healthy mind lives in a healthy ___.", "body", ["house", "room", "place"]), + ("Books are the ___ teachers of all time.", "best", ["worst", "slowest", "latest"]), + ("Time and ___ wait for none.", "tide", ["money", "people", "luck"]), + ("Prevention is better than ___.", "cure", ["disease", "medicine", "treatment"]), + ("All that ___ is not gold.", "glitters", ["shines", "sparkles", "reflects"]), + ("A rolling stone gathers no ___.", "moss", ["grass", "dust", "speed"]), + ("Where there is a will, there is a ___.", "way", ["wall", "path", "road"]), + ("Rome was not built in a ___.", "day", ["week", "month", "hour"]), + ] + for _ in range(count): + sentence, correct, wrongs = random.choice(templates) + if isinstance(wrongs, str): # Fix the Rome template + wrongs = [wrongs, "year", "hour"] + questions.append(make_question(qtid, + f"Fill in the blank: {sentence}", + correct, wrongs, f"Answer: {correct}", 1)) + return questions + + +def gen_subject_verb(conn, count=1000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Grammar", "Subject-Verb Agreement", "Choose correct verb") + if not qtid: return questions + templates = [ + ("The team ___ playing well this season.", "is", ["are", "were", "have been"]), + ("Neither the teacher nor the students ___ present.", "were", ["was", "is", "has been"]), + ("Each of the boys ___ given a prize.", "was", ["were", "are", "have been"]), + ("Either you or I ___ going to attend.", "am", ["are", "is", "were"]), + ("The quality of these apples ___ good.", "is", ["are", "were", "have been"]), + ("Bread and butter ___ my favourite breakfast.", "is", ["are", "were", "have been"]), + ("One of my friends ___ from Delhi.", "is", ["are", "were", "have been"]), + ("The news ___ very surprising.", "was", ["were", "are", "have been"]), + ("No news ___ good news.", "is", ["are", "were", "have been"]), + ("Mathematics ___ my favourite subject.", "is", ["are", "were", "have been"]), + ] + for _ in range(count): + sentence, correct, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Choose the correct verb: {sentence}", + correct, wrongs, f"Correct: {correct}", 2)) + return questions + + +def generate_all(conn): + """Generate all English Language questions.""" + generators = [ + ("Synonyms", gen_synonyms, 2500), + ("Antonyms", gen_antonyms, 2500), + ("One Word Substitution", gen_one_word, 2000), + ("Idioms & Phrases", gen_idioms, 2000), + ("Spelling", gen_spelling, 1500), + ("Tenses", gen_tenses, 1500), + ("Articles", gen_articles, 1000), + ("Prepositions", gen_prepositions, 1000), + ("Subject-Verb Agreement", gen_subject_verb, 1000), + ("Active/Passive Voice", gen_voice, 1500), + ("Direct/Indirect Speech", gen_direct_indirect, 1500), + ("Sentence Improvement", gen_sentence_improvement, 2000), + ("Error Detection", gen_error_detection, 2500), + ("Fill in Blanks", gen_fill_blanks, 2000), + ("Sentence Rearrangement", gen_sentence_rearrangement, 1500), + ("Cloze Test", gen_cloze_test, 1500), + ] + + total = 0 + all_questions = [] + for name, gen_func, count in generators: + questions = gen_func(conn, count) + all_questions.extend(questions) + print(f" {name}: {len(questions)} questions") + total += len(questions) + + batch_size = 5000 + for i in range(0, len(all_questions), batch_size): + insert_questions_batch(conn, all_questions[i:i+batch_size]) + + print(f" TOTAL English: {total}") + return total + + +if __name__ == '__main__': + conn = get_db() + print("Generating English Language questions...") + generate_all(conn) + conn.close() diff --git a/generators/gk_generator.py b/generators/gk_generator.py new file mode 100644 index 0000000..b8ba7a6 --- /dev/null +++ b/generators/gk_generator.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +""" +General Awareness Question Generator for SSC CGL. +Generates ~20,000+ questions from curated fact banks covering History, Geography, +Polity, Economics, Science, and Static GK. +""" +import random +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +from generators.base import make_question, get_qtid, get_db, insert_questions_batch + +SUBJECT = "General Awareness" + +# ============ FACT BANKS ============ + +ANCIENT_INDIA = [ + ("The Indus Valley Civilization was discovered in", "1921", ["1911", "1931", "1901"]), + ("Harappa was discovered by", "Daya Ram Sahni", ["R.D. Banerji", "John Marshall", "Mortimer Wheeler"]), + ("Mohenjo-daro was discovered by", "R.D. Banerji", ["Daya Ram Sahni", "John Marshall", "Alexander Cunningham"]), + ("The Great Bath was found at", "Mohenjo-daro", ["Harappa", "Lothal", "Kalibangan"]), + ("The founder of the Maurya dynasty was", "Chandragupta Maurya", ["Ashoka", "Bindusara", "Bimbisara"]), + ("Ashoka embraced Buddhism after the battle of", "Kalinga", ["Hydaspes", "Plassey", "Panipat"]), + ("The author of Arthashastra was", "Kautilya", ["Kalidasa", "Banabhatta", "Vishakhadatta"]), + ("Who was the court poet of Chandragupta Vikramaditya?", "Kalidasa", ["Banabhatta", "Harisena", "Amarsimha"]), + ("The capital of the Chola dynasty was", "Thanjavur", ["Madurai", "Kanchipuram", "Hampi"]), + ("The Iron Pillar at Delhi was erected by", "Chandragupta II", ["Ashoka", "Kanishka", "Samudragupta"]), + ("Nalanda University was founded by", "Kumaragupta I", ["Ashoka", "Harsha", "Chandragupta II"]), + ("The Gupta period is known as the", "Golden Age of India", ["Silver Age", "Bronze Age", "Iron Age"]), + ("Sangam literature belongs to", "Tamil Nadu", ["Kerala", "Karnataka", "Andhra Pradesh"]), + ("The first Jain council was held at", "Pataliputra", ["Vaishali", "Rajgir", "Vallabhi"]), + ("Gautama Buddha attained enlightenment at", "Bodh Gaya", ["Sarnath", "Kushinagar", "Lumbini"]), + ("The Ajanta caves are famous for", "Paintings", ["Sculptures", "Architecture", "Inscriptions"]), + ("Who built the Sanchi Stupa?", "Ashoka", ["Kanishka", "Harsha", "Chandragupta"]), + ("The Vedic period is divided into how many parts?", "Two (Early and Later)", ["Three", "Four", "Five"]), + ("The oldest Veda is", "Rigveda", ["Samaveda", "Yajurveda", "Atharvaveda"]), + ("Alexander invaded India in", "326 BC", ["321 BC", "331 BC", "316 BC"]), +] + +MEDIEVAL_INDIA = [ + ("The Delhi Sultanate was established in", "1206", ["1192", "1210", "1290"]), + ("Who founded the Mughal Empire in India?", "Babur", ["Akbar", "Humayun", "Shah Jahan"]), + ("The Battle of Panipat (1526) was fought between", "Babur and Ibrahim Lodi", ["Akbar and Hemu", "Babur and Rana Sanga", "Humayun and Sher Shah"]), + ("Taj Mahal was built by", "Shah Jahan", ["Akbar", "Jahangir", "Aurangzeb"]), + ("Akbar founded the religion", "Din-i-Ilahi", ["Islam", "Sikhism", "Jainism"]), + ("The court language of the Mughals was", "Persian", ["Urdu", "Arabic", "Hindi"]), + ("Who built the Red Fort in Delhi?", "Shah Jahan", ["Akbar", "Aurangzeb", "Babur"]), + ("The last Mughal emperor was", "Bahadur Shah Zafar", ["Aurangzeb", "Shah Alam II", "Muhammad Shah"]), + ("Sher Shah Suri introduced", "Rupee coin", ["Gold coin", "Copper coin", "Silver coin"]), + ("Who built Qutub Minar?", "Qutb-ud-din Aibak", ["Iltutmish", "Alauddin Khilji", "Muhammad Tughlaq"]), + ("The Bhakti movement was started by", "Ramanuja", ["Kabir", "Nanak", "Tulsidas"]), + ("Vijayanagara Empire was founded by", "Harihara and Bukka", ["Krishna Deva Raya", "Rama Raya", "Sangama"]), + ("The capital of Vijayanagara was", "Hampi", ["Thanjavur", "Madurai", "Warangal"]), + ("Who introduced the Mansabdari system?", "Akbar", ["Babur", "Shah Jahan", "Aurangzeb"]), + ("The famous poet Amir Khusrau was associated with", "Alauddin Khilji", ["Akbar", "Babur", "Iltutmish"]), +] + +MODERN_INDIA = [ + ("Who is known as the Father of the Nation?", "Mahatma Gandhi", ["Jawaharlal Nehru", "Subhas Chandra Bose", "B.R. Ambedkar"]), + ("The first war of Indian Independence was in", "1857", ["1847", "1867", "1877"]), + ("Who gave the slogan 'Do or Die'?", "Mahatma Gandhi", ["Subhas Chandra Bose", "Bal Gangadhar Tilak", "Bhagat Singh"]), + ("The Indian National Congress was founded in", "1885", ["1875", "1895", "1905"]), + ("Who founded the Indian National Congress?", "A.O. Hume", ["Dadabhai Naoroji", "Surendranath Banerjee", "W.C. Bonnerjee"]), + ("The Jallianwala Bagh massacre occurred in", "1919", ["1920", "1918", "1921"]), + ("The Salt March took place in", "1930", ["1929", "1931", "1932"]), + ("India gained independence on", "15 August 1947", ["26 January 1947", "15 August 1946", "26 January 1950"]), + ("Who was the first President of India?", "Dr. Rajendra Prasad", ["Dr. S. Radhakrishnan", "Jawaharlal Nehru", "B.R. Ambedkar"]), + ("Who was the first Prime Minister of India?", "Jawaharlal Nehru", ["Sardar Patel", "Dr. Rajendra Prasad", "Mahatma Gandhi"]), + ("The Quit India Movement was launched in", "1942", ["1940", "1943", "1945"]), + ("Who gave the slogan 'Jai Hind'?", "Subhas Chandra Bose", ["Mahatma Gandhi", "Jawaharlal Nehru", "Bhagat Singh"]), + ("The Rowlatt Act was passed in", "1919", ["1918", "1920", "1921"]), + ("The Non-Cooperation Movement was launched in", "1920", ["1919", "1921", "1922"]), + ("Who was known as the Iron Man of India?", "Sardar Vallabhbhai Patel", ["Subhas Chandra Bose", "Bhagat Singh", "Lal Bahadur Shastri"]), + ("The Partition of Bengal took place in", "1905", ["1903", "1907", "1909"]), + ("Who founded the Arya Samaj?", "Swami Dayanand Saraswati", ["Swami Vivekananda", "Raja Ram Mohan Roy", "Ramakrishna"]), + ("The Lucknow Pact was signed in", "1916", ["1915", "1917", "1918"]), + ("Who wrote 'Vande Mataram'?", "Bankim Chandra Chattopadhyay", ["Rabindranath Tagore", "Kazi Nazrul Islam", "Sarojini Naidu"]), + ("Who composed the National Anthem of India?", "Rabindranath Tagore", ["Bankim Chandra", "Sarojini Naidu", "Muhammad Iqbal"]), +] + +GEOGRAPHY_FACTS = [ + ("The largest continent by area is", "Asia", ["Africa", "North America", "Europe"]), + ("The longest river in the world is", "Nile", ["Amazon", "Yangtze", "Mississippi"]), + ("The highest peak in the world is", "Mount Everest", ["K2", "Kangchenjunga", "Lhotse"]), + ("The largest ocean is", "Pacific Ocean", ["Atlantic Ocean", "Indian Ocean", "Arctic Ocean"]), + ("The largest desert in the world is", "Sahara", ["Arabian", "Gobi", "Kalahari"]), + ("The longest river in India is", "Ganga", ["Godavari", "Krishna", "Brahmaputra"]), + ("The highest peak in India is", "Kangchenjunga", ["Nanda Devi", "K2", "Annapurna"]), + ("Which state in India has the longest coastline?", "Gujarat", ["Maharashtra", "Tamil Nadu", "Andhra Pradesh"]), + ("The Tropic of Cancer passes through how many Indian states?", "8", ["6", "7", "9"]), + ("The largest freshwater lake in India is", "Wular Lake", ["Dal Lake", "Chilika Lake", "Loktak Lake"]), + ("Which Indian state is known as the 'Spice Garden of India'?", "Kerala", ["Karnataka", "Tamil Nadu", "Goa"]), + ("The Western Ghats are also known as", "Sahyadri", ["Vindhya", "Aravalli", "Nilgiri"]), + ("The northernmost point of India is", "Indira Col", ["Kanyakumari", "Rann of Kutch", "Indira Point"]), + ("Which river is known as the 'Sorrow of Bengal'?", "Damodar", ["Hooghly", "Teesta", "Mahanadi"]), + ("The largest state in India by area is", "Rajasthan", ["Madhya Pradesh", "Maharashtra", "Uttar Pradesh"]), + ("The Thar Desert is located in", "Rajasthan", ["Gujarat", "Haryana", "Punjab"]), + ("The capital of Arunachal Pradesh is", "Itanagar", ["Shillong", "Imphal", "Kohima"]), + ("Chilika Lake is located in", "Odisha", ["Andhra Pradesh", "Tamil Nadu", "Kerala"]), + ("The Deccan Plateau lies in", "Southern India", ["Northern India", "Eastern India", "Western India"]), + ("The soil best suited for cotton cultivation is", "Black soil", ["Red soil", "Alluvial soil", "Laterite soil"]), +] + +POLITY_FACTS = [ + ("The Constitution of India came into effect on", "26 January 1950", ["15 August 1947", "26 November 1949", "15 August 1950"]), + ("How many Fundamental Rights are there?", "6", ["5", "7", "8"]), + ("Right to Education was added by which amendment?", "86th Amendment", ["42nd Amendment", "44th Amendment", "91st Amendment"]), + ("The Preamble declares India as a", "Sovereign Socialist Secular Democratic Republic", ["Federal Republic", "Parliamentary Republic", "Constitutional Monarchy"]), + ("Who is known as the Father of Indian Constitution?", "Dr. B.R. Ambedkar", ["Jawaharlal Nehru", "Mahatma Gandhi", "Rajendra Prasad"]), + ("The total number of members in Lok Sabha is", "545", ["250", "500", "552"]), + ("Rajya Sabha members are elected for", "6 years", ["5 years", "4 years", "3 years"]), + ("The minimum age to become President of India is", "35 years", ["25 years", "30 years", "40 years"]), + ("The Chief Justice of India is appointed by", "The President", ["The Prime Minister", "The Parliament", "The Law Minister"]), + ("Which Article deals with the Right to Equality?", "Article 14", ["Article 19", "Article 21", "Article 32"]), + ("Article 21 deals with", "Right to Life and Personal Liberty", ["Right to Freedom", "Right to Equality", "Right against Exploitation"]), + ("The Directive Principles are in which Part of the Constitution?", "Part IV", ["Part III", "Part V", "Part VI"]), + ("How many schedules are there in the Indian Constitution?", "12", ["8", "10", "14"]), + ("The Vice President is the ex-officio Chairman of", "Rajya Sabha", ["Lok Sabha", "Parliament", "NITI Aayog"]), + ("Emergency provisions are in which Article?", "Article 352", ["Article 356", "Article 360", "Article 370"]), + ("The 73rd Amendment is related to", "Panchayati Raj", ["Municipal Corporation", "Fundamental Rights", "DPSP"]), + ("Who appoints the Governor of a state?", "The President", ["The Prime Minister", "The Chief Minister", "The Parliament"]), + ("The Finance Commission is appointed every", "5 years", ["3 years", "4 years", "6 years"]), + ("Judicial review is the power of", "Supreme Court", ["Parliament", "President", "Prime Minister"]), + ("The CAG is appointed by", "The President", ["The Prime Minister", "The Parliament", "The Finance Minister"]), +] + +ECONOMICS_FACTS = [ + ("The Reserve Bank of India was established in", "1935", ["1947", "1950", "1921"]), + ("The current GST council is chaired by", "Union Finance Minister", ["Prime Minister", "RBI Governor", "Revenue Secretary"]), + ("Which Five Year Plan focused on rapid industrialization?", "Second Five Year Plan", ["First Plan", "Third Plan", "Fourth Plan"]), + ("NITI Aayog was established in", "2015", ["2014", "2016", "2017"]), + ("NITI Aayog replaced", "Planning Commission", ["Finance Commission", "UGC", "UPSC"]), + ("GDP stands for", "Gross Domestic Product", ["Grand Domestic Product", "General Domestic Product", "Gross Development Product"]), + ("The fiscal year in India starts from", "April 1", ["January 1", "March 1", "July 1"]), + ("WTO was established in", "1995", ["1947", "1991", "2000"]), + ("IMF headquarters is in", "Washington D.C.", ["New York", "Geneva", "London"]), + ("World Bank headquarters is in", "Washington D.C.", ["New York", "Geneva", "Paris"]), + ("BRICS includes India, Brazil, Russia, China and", "South Africa", ["Sri Lanka", "Singapore", "Saudi Arabia"]), + ("National income is calculated by", "Central Statistics Office", ["RBI", "NITI Aayog", "Finance Ministry"]), + ("The first bank in India was", "Bank of Hindustan", ["State Bank of India", "Bank of Bombay", "RBI"]), + ("SBI was formed from", "Imperial Bank of India", ["Bank of Bengal", "Bank of Bombay", "Bank of Madras"]), + ("Repo rate is the rate at which", "RBI lends to commercial banks", ["Banks lend to public", "Government borrows", "FDI flows"]), + ("CRR stands for", "Cash Reserve Ratio", ["Central Reserve Ratio", "Cash Recovery Rate", "Credit Reserve Ratio"]), + ("The currency of Japan is", "Yen", ["Yuan", "Won", "Baht"]), + ("Which organization gives the 'Ease of Doing Business' ranking?", "World Bank", ["IMF", "WTO", "UN"]), + ("Make in India was launched in", "2014", ["2015", "2016", "2013"]), + ("Digital India was launched in", "2015", ["2014", "2016", "2017"]), +] + +SCIENCE_FACTS = [ + ("The SI unit of force is", "Newton", ["Joule", "Watt", "Pascal"]), + ("The speed of light is approximately", "3 Ɨ 10⁸ m/s", ["3 Ɨ 10⁶ m/s", "3 Ɨ 10¹⁰ m/s", "3 Ɨ 10⁵ m/s"]), + ("The chemical formula of water is", "Hā‚‚O", ["Hā‚‚Oā‚‚", "HOā‚‚", "Hā‚ƒO"]), + ("Photosynthesis occurs in", "Chloroplast", ["Mitochondria", "Ribosome", "Nucleus"]), + ("The powerhouse of the cell is", "Mitochondria", ["Nucleus", "Ribosome", "Chloroplast"]), + ("The hardest natural substance is", "Diamond", ["Quartz", "Topaz", "Ruby"]), + ("The chemical symbol for gold is", "Au", ["Ag", "Fe", "Go"]), + ("Blood is purified in", "Kidneys", ["Liver", "Heart", "Lungs"]), + ("The largest organ of the human body is", "Skin", ["Liver", "Brain", "Heart"]), + ("The total number of bones in an adult human body is", "206", ["208", "204", "210"]), + ("Vitamin C deficiency causes", "Scurvy", ["Rickets", "Beriberi", "Night blindness"]), + ("Vitamin D deficiency causes", "Rickets", ["Scurvy", "Beriberi", "Pellagra"]), + ("The gas responsible for global warming is", "Carbon dioxide", ["Oxygen", "Nitrogen", "Hydrogen"]), + ("Sound travels fastest in", "Solids", ["Liquids", "Gases", "Vacuum"]), + ("The pH of pure water is", "7", ["5", "8", "6"]), + ("Newton's first law is also known as", "Law of Inertia", ["Law of Acceleration", "Law of Action-Reaction", "Law of Gravity"]), + ("The center of an atom is called", "Nucleus", ["Electron", "Proton", "Neutron"]), + ("Insulin is produced by", "Pancreas", ["Liver", "Kidney", "Thyroid"]), + ("The boiling point of water is", "100°C", ["90°C", "110°C", "120°C"]), + ("The chemical formula of common salt is", "NaCl", ["KCl", "NaOH", "HCl"]), + ("The study of fungi is called", "Mycology", ["Zoology", "Botany", "Virology"]), + ("The gas used in fire extinguishers is", "COā‚‚", ["Oā‚‚", "Nā‚‚", "Hā‚‚"]), + ("The lightest gas is", "Hydrogen", ["Helium", "Oxygen", "Nitrogen"]), + ("The element with atomic number 1 is", "Hydrogen", ["Helium", "Lithium", "Carbon"]), + ("Malaria is caused by", "Plasmodium", ["Bacteria", "Virus", "Fungus"]), + ("The human heart has how many chambers?", "4", ["2", "3", "5"]), + ("The instrument used to measure atmospheric pressure is", "Barometer", ["Thermometer", "Hygrometer", "Anemometer"]), + ("Who discovered Penicillin?", "Alexander Fleming", ["Louis Pasteur", "Edward Jenner", "Robert Koch"]), + ("The study of earthquake is called", "Seismology", ["Geology", "Volcanology", "Meteorology"]), + ("DNA stands for", "Deoxyribonucleic Acid", ["Deoxyribose Nucleic Acid", "Dinucleic Acid", "Deoxyribo Amino Acid"]), +] + +COMPUTER_FACTS = [ + ("The full form of CPU is", "Central Processing Unit", ["Central Program Unit", "Computer Processing Unit", "Central Process Utility"]), + ("RAM stands for", "Random Access Memory", ["Read Access Memory", "Random Alloc Memory", "Read All Memory"]), + ("The father of computers is", "Charles Babbage", ["Alan Turing", "John von Neumann", "Tim Berners-Lee"]), + ("HTML stands for", "HyperText Markup Language", ["HyperText Machine Language", "High Text Markup Language", "Hyper Transfer Markup Language"]), + ("Which shortcut key is used to copy?", "Ctrl + C", ["Ctrl + V", "Ctrl + X", "Ctrl + Z"]), + ("Which shortcut key is used to undo?", "Ctrl + Z", ["Ctrl + Y", "Ctrl + X", "Ctrl + C"]), + ("The brain of the computer is", "CPU", ["RAM", "Hard Disk", "Monitor"]), + ("1 KB equals", "1024 bytes", ["1000 bytes", "512 bytes", "2048 bytes"]), + ("An IP address is used to", "Identify a device on a network", ["Store data", "Display web pages", "Send emails"]), + ("HTTP stands for", "HyperText Transfer Protocol", ["High Text Transfer Protocol", "Hyper Transfer Text Protocol", "HyperText Transport Protocol"]), + ("The extension of a Word document is", ".docx", [".xlsx", ".pptx", ".pdf"]), + ("Which software is used for spreadsheets?", "MS Excel", ["MS Word", "MS PowerPoint", "MS Access"]), + ("A computer virus is a", "Malicious software program", ["Hardware defect", "Network issue", "Browser plugin"]), + ("Wi-Fi stands for", "Wireless Fidelity", ["Wireless Finder", "Wide Fidelity", "Wired Fidelity"]), + ("The first search engine on the internet was", "Archie", ["Google", "Yahoo", "Bing"]), + ("URL stands for", "Uniform Resource Locator", ["Universal Resource Link", "Uniform Retrieval Locator", "Universal Resource Locator"]), + ("LAN stands for", "Local Area Network", ["Large Area Network", "Long Access Network", "Local Access Network"]), + ("Which key is used to refresh a web page?", "F5", ["F1", "F2", "F12"]), + ("The default file extension for Excel is", ".xlsx", [".docx", ".pptx", ".csv"]), + ("Phishing is a type of", "Cyber fraud", ["Computer virus", "Software update", "Network protocol"]), +] + +STATIC_GK = [ + ("The national bird of India is", "Peacock", ["Sparrow", "Parrot", "Eagle"]), + ("The national animal of India is", "Tiger", ["Lion", "Elephant", "Leopard"]), + ("The national flower of India is", "Lotus", ["Rose", "Jasmine", "Sunflower"]), + ("The national game of India is", "Hockey", ["Cricket", "Football", "Badminton"]), + ("The national fruit of India is", "Mango", ["Apple", "Banana", "Guava"]), + ("The national river of India is", "Ganga", ["Yamuna", "Godavari", "Brahmaputra"]), + ("India's national currency is", "Indian Rupee", ["Dollar", "Pound", "Euro"]), + ("World Environment Day is celebrated on", "June 5", ["March 22", "April 22", "October 16"]), + ("International Women's Day is observed on", "March 8", ["February 14", "May 1", "June 21"]), + ("World Health Day is celebrated on", "April 7", ["March 7", "May 7", "June 7"]), + ("Teachers' Day in India is celebrated on", "September 5", ["November 14", "October 2", "January 26"]), + ("Children's Day in India is celebrated on", "November 14", ["September 5", "October 2", "January 26"]), + ("Republic Day is celebrated on", "January 26", ["August 15", "October 2", "November 14"]), + ("Who wrote the book 'Wings of Fire'?", "A.P.J. Abdul Kalam", ["Jawaharlal Nehru", "Mahatma Gandhi", "R.K. Narayan"]), + ("Who wrote 'Discovery of India'?", "Jawaharlal Nehru", ["Mahatma Gandhi", "Rabindranath Tagore", "S. Radhakrishnan"]), + ("The Nobel Prize for Literature was won by Rabindranath Tagore in", "1913", ["1910", "1920", "1930"]), + ("The Olympic Games are held every", "4 years", ["2 years", "3 years", "5 years"]), + ("The headquarters of UN is in", "New York", ["Geneva", "London", "Washington D.C."]), + ("Who was the first Indian woman to win an Olympic medal?", "Karnam Malleswari", ["P.T. Usha", "Saina Nehwal", "Mary Kom"]), + ("The Booker Prize is associated with", "Literature", ["Science", "Peace", "Economics"]), + ("The Grammy Award is associated with", "Music", ["Films", "Literature", "Sports"]), + ("The first Indian satellite was", "Aryabhata", ["Bhaskara", "INSAT-1A", "Rohini"]), + ("ISRO headquarters is in", "Bengaluru", ["Chennai", "Hyderabad", "New Delhi"]), + ("Who was the first Indian in space?", "Rakesh Sharma", ["Kalpana Chawla", "Sunita Williams", "Ravish Malhotra"]), + ("The Bharat Ratna is the highest", "Civilian award", ["Military award", "Sports award", "Literary award"]), +] + +GOVT_SCHEMES = [ + ("Swachh Bharat Mission was launched in", "2014", ["2015", "2016", "2013"]), + ("Pradhan Mantri Jan Dhan Yojana provides", "Bank accounts for all", ["Free healthcare", "Education scholarship", "Housing"]), + ("Ayushman Bharat scheme provides", "Health insurance coverage", ["Bank accounts", "Education", "Housing"]), + ("PM Kisan scheme provides", "₹6000 per year to farmers", ["Free seeds", "Loan waiver", "Insurance"]), + ("Beti Bachao Beti Padhao focuses on", "Girl child welfare and education", ["Boy child education", "Senior citizens", "Farmers"]), + ("Start-up India was launched in", "2016", ["2015", "2017", "2014"]), + ("Skill India Mission aims at", "Training youth in skills", ["Providing jobs", "Building schools", "Health camps"]), + ("MUDRA scheme provides", "Loans for small businesses", ["Education loans", "Home loans", "Car loans"]), + ("Ujjwala Yojana provides", "Free LPG connections", ["Free electricity", "Free water", "Free internet"]), + ("Atal Pension Yojana is for", "Retirement pension for unorganized sector", ["Health insurance", "Education", "Housing"]), +] + +SPORTS_FACTS = [ + ("The Cricket World Cup 2023 was held in", "India", ["England", "Australia", "South Africa"]), + ("The FIFA World Cup 2022 was held in", "Qatar", ["Russia", "Brazil", "Japan"]), + ("The Ranji Trophy is associated with", "Cricket", ["Football", "Hockey", "Tennis"]), + ("The Davis Cup is associated with", "Tennis", ["Cricket", "Football", "Badminton"]), + ("The Thomas Cup is associated with", "Badminton", ["Tennis", "Table Tennis", "Cricket"]), + ("The Durand Cup is the oldest football tournament in", "Asia", ["Europe", "Africa", "South America"]), + ("Wimbledon is played on", "Grass court", ["Clay court", "Hard court", "Carpet court"]), + ("A marathon race covers a distance of", "42.195 km", ["40 km", "45 km", "50 km"]), + ("The term 'Grand Slam' is used in", "Tennis", ["Cricket", "Football", "Hockey"]), + ("How many players are there in a cricket team?", "11", ["9", "13", "15"]), + ("How many players are there in a football team?", "11", ["9", "13", "15"]), + ("The term 'Checkmate' is used in", "Chess", ["Cricket", "Football", "Hockey"]), + ("The Summer Olympics 2024 was held in", "Paris", ["Tokyo", "Los Angeles", "London"]), + ("Who holds the record for most centuries in international cricket?", "Sachin Tendulkar", ["Virat Kohli", "Ricky Ponting", "Kumar Sangakkara"]), + ("The Dronacharya Award is given to", "Sports coaches", ["Players", "Scientists", "Teachers"]), +] + + +def _generate_from_facts(conn, facts, subtopic, topic, qtype_name, multiplier=3): + """Generate questions from a fact bank with variations.""" + questions = [] + qtid = get_qtid(conn, SUBJECT, subtopic, topic, qtype_name) + if not qtid: + return questions + for _ in range(len(facts) * multiplier): + q_text, correct, wrongs = random.choice(facts) + difficulty = random.choice([1, 1, 2]) + questions.append(make_question(qtid, q_text, correct, wrongs, f"Answer: {correct}", difficulty)) + return questions + + +def gen_history(conn, count_per=600): + q = [] + q.extend(_generate_from_facts(conn, ANCIENT_INDIA, "History", "Ancient India", "Who/What/When", 50)) + q.extend(_generate_from_facts(conn, MEDIEVAL_INDIA, "History", "Medieval India", "Who/What/When", 50)) + q.extend(_generate_from_facts(conn, MODERN_INDIA, "History", "Modern India", "Freedom fighter identification", 50)) + return q + + +def gen_geography(conn, count=2000): + return _generate_from_facts(conn, GEOGRAPHY_FACTS, "Geography", "Indian Geography", "River system", 130) + + +def gen_polity(conn, count=2000): + return _generate_from_facts(conn, POLITY_FACTS, "Indian Polity", "Indian Constitution", "Article identification", 130) + + +def gen_economics(conn, count=2000): + return _generate_from_facts(conn, ECONOMICS_FACTS, "Economics", "Indian Economy Basics", "Economic indicator", 130) + + +def gen_science(conn, count=3000): + return _generate_from_facts(conn, SCIENCE_FACTS, "Science", "Physics", "Law/principle identification", 135) + + +def gen_computer(conn, count=2000): + return _generate_from_facts(conn, COMPUTER_FACTS, "Science", "Computer Awareness", "Term definition", 100) + + +def gen_static_gk(conn, count=2000): + return _generate_from_facts(conn, STATIC_GK, "Static GK", "National Symbols and Firsts", "Identify symbol", 80) + + +def gen_schemes(conn, count=1000): + return _generate_from_facts(conn, GOVT_SCHEMES, "Economics", "Government Schemes", "Scheme objective", 100) + + +def gen_sports(conn, count=1000): + return _generate_from_facts(conn, SPORTS_FACTS, "Static GK", "Sports", "Trophy-sport", 70) + + +def generate_all(conn): + """Generate all General Awareness questions.""" + generators = [ + ("History", gen_history), + ("Geography", gen_geography), + ("Polity", gen_polity), + ("Economics", gen_economics), + ("Science", gen_science), + ("Computer Awareness", gen_computer), + ("Static GK", gen_static_gk), + ("Govt Schemes", gen_schemes), + ("Sports", gen_sports), + ] + + total = 0 + all_questions = [] + for name, gen_func in generators: + questions = gen_func(conn) + all_questions.extend(questions) + print(f" {name}: {len(questions)} questions") + total += len(questions) + + batch_size = 5000 + for i in range(0, len(all_questions), batch_size): + insert_questions_batch(conn, all_questions[i:i+batch_size]) + + print(f" TOTAL General Awareness: {total}") + return total + + +if __name__ == '__main__': + conn = get_db() + print("Generating General Awareness questions...") + generate_all(conn) + conn.close() diff --git a/generators/quant_generator.py b/generators/quant_generator.py new file mode 100644 index 0000000..a598552 --- /dev/null +++ b/generators/quant_generator.py @@ -0,0 +1,721 @@ +#!/usr/bin/env python3 +""" +Quantitative Aptitude Question Generator for SSC CGL. +Generates ~30,000 template-based math questions across all topics. +""" +import random +import math +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +from generators.base import make_question, get_qtid, nearby_wrong, nearby_wrong_float, get_db, insert_questions_batch + +SUBJECT = "Quantitative Aptitude" + + +def gen_number_system(conn, count=800): + questions = [] + # Unit digit questions + qtid = get_qtid(conn, SUBJECT, "Arithmetic", "Number System", "Unit digit") + if qtid: + for _ in range(count // 4): + base = random.randint(2, 99) + exp = random.randint(10, 200) + unit = pow(base, exp, 10) + q = make_question(qtid, + f"What is the unit digit of {base}^{exp}?", + str(unit), nearby_wrong(unit, 4), + f"Unit digit of {base}^{exp} = {unit}", random.choice([1,1,2])) + questions.append(q) + + # Divisibility + qtid = get_qtid(conn, SUBJECT, "Arithmetic", "Number System", "Divisibility test") + if qtid: + divs = [2, 3, 4, 5, 6, 7, 8, 9, 11] + for _ in range(count // 4): + d = random.choice(divs) + num = random.randint(100, 9999) * d + q = make_question(qtid, + f"Which of the following numbers is divisible by {d}?", + str(num), [str(num + random.randint(1, d-1)) for _ in range(3)], + f"{num} Ć· {d} = {num//d}", 1) + questions.append(q) + + # Remainder + qtid = get_qtid(conn, SUBJECT, "Arithmetic", "Number System", "Remainder problems") + if qtid: + for _ in range(count // 4): + divisor = random.randint(3, 20) + quotient = random.randint(10, 200) + remainder = random.randint(0, divisor - 1) + num = divisor * quotient + remainder + q = make_question(qtid, + f"What is the remainder when {num} is divided by {divisor}?", + str(remainder), nearby_wrong(remainder, max(3, divisor)), + f"{num} = {divisor} Ɨ {quotient} + {remainder}", 1) + questions.append(q) + + # Find the value + qtid = get_qtid(conn, SUBJECT, "Arithmetic", "Number System", "Find the value") + if qtid: + for _ in range(count // 4): + a, b = random.randint(10, 99), random.randint(10, 99) + ops = [(f"{a} Ɨ {b}", a*b), (f"{a}² - {b}²", a*a - b*b), + (f"{a}² + {b}²", a*a + b*b)] + expr, ans = random.choice(ops) + q = make_question(qtid, f"Find the value of {expr}.", + str(ans), nearby_wrong(ans), + f"{expr} = {ans}", random.choice([1,2])) + questions.append(q) + return questions + + +def gen_hcf_lcm(conn, count=500): + questions = [] + qtid_hcf = get_qtid(conn, SUBJECT, "Arithmetic", "HCF and LCM", "Find HCF") + qtid_lcm = get_qtid(conn, SUBJECT, "Arithmetic", "HCF and LCM", "Find LCM") + qtid_word = get_qtid(conn, SUBJECT, "Arithmetic", "HCF and LCM", "Word problems on HCF/LCM") + + for _ in range(count // 3): + a, b = random.randint(10, 200), random.randint(10, 200) + h = math.gcd(a, b) + l = (a * b) // h + if qtid_hcf: + questions.append(make_question(qtid_hcf, + f"Find the HCF of {a} and {b}.", str(h), nearby_wrong(h), + f"HCF({a}, {b}) = {h}", 1)) + if qtid_lcm: + questions.append(make_question(qtid_lcm, + f"Find the LCM of {a} and {b}.", str(l), nearby_wrong(l), + f"LCM({a}, {b}) = {l}", 1)) + if qtid_word: + for _ in range(count // 3): + h = random.randint(5, 30) + m1, m2 = random.randint(2, 6), random.randint(2, 6) + a, b = h * m1, h * m2 + questions.append(make_question(qtid_word, + f"Two ropes of lengths {a} cm and {b} cm are to be cut into pieces of equal length. What is the maximum length of each piece?", + str(h) + " cm", [str(h+i) + " cm" for i in [1, 2, -1]], + f"HCF({a}, {b}) = {h} cm", 1)) + return questions + + +def gen_simplification(conn, count=600): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Arithmetic", "Simplification", "Simplify expression") + if not qtid: + return questions + for _ in range(count): + a, b, c = random.randint(2, 50), random.randint(2, 50), random.randint(1, 30) + templates = [ + (f"{a} + {b} Ɨ {c}", a + b * c), + (f"{a} Ɨ {b} - {c}", a * b - c), + (f"({a} + {b}) Ɨ {c}", (a + b) * c), + (f"{a}² - {b} Ɨ {c}", a*a - b * c), + (f"{a} + {b}² - {c}", a + b*b - c), + ] + expr, ans = random.choice(templates) + questions.append(make_question(qtid, f"Simplify: {expr}", + str(ans), nearby_wrong(ans), f"{expr} = {ans}", random.choice([1,1,2]))) + return questions + + +def gen_percentage(conn, count=2000): + questions = [] + # Basic percentage + qtid = get_qtid(conn, SUBJECT, "Percentage", "Basic Percentage", "Find X% of Y") + if qtid: + for _ in range(count // 4): + pct = random.choice([5, 10, 12, 15, 16, 20, 25, 30, 33, 40, 50, 60, 75]) + val = random.randint(50, 5000) + ans = val * pct / 100 + if ans == int(ans): + ans = int(ans) + questions.append(make_question(qtid, + f"What is {pct}% of {val}?", str(ans), nearby_wrong(ans), + f"{pct}% of {val} = {val} Ɨ {pct}/100 = {ans}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Percentage", "Basic Percentage", "What percent is X of Y") + if qtid: + for _ in range(count // 4): + total = random.randint(50, 1000) + part = random.randint(1, total) + pct = round(part * 100 / total, 2) + questions.append(make_question(qtid, + f"What percentage is {part} of {total}?", str(pct) + "%", + [str(round(pct + random.uniform(-10, 10), 2)) + "%" for _ in range(3)], + f"{part}/{total} Ɨ 100 = {pct}%", 1)) + + qtid = get_qtid(conn, SUBJECT, "Percentage", "Basic Percentage", "Percentage change") + if qtid: + for _ in range(count // 4): + old = random.randint(100, 5000) + change = random.randint(5, 50) + direction = random.choice(["increased", "decreased"]) + new_val = old + old * change // 100 if direction == "increased" else old - old * change // 100 + questions.append(make_question(qtid, + f"If a value {direction} from {old} to {new_val}, what is the percentage change?", + str(change) + "%", [str(change + i) + "%" for i in [2, -3, 5]], + f"Change = {abs(new_val-old)}/{old} Ɨ 100 = {change}%", 1)) + + # Successive percentage + qtid = get_qtid(conn, SUBJECT, "Percentage", "Successive Percentage", "Net percentage change") + if qtid: + for _ in range(count // 4): + p1, p2 = random.randint(5, 40), random.randint(5, 40) + net = round(p1 + p2 + p1 * p2 / 100, 2) + questions.append(make_question(qtid, + f"If a price increases by {p1}% and then by {p2}%, what is the net percentage increase?", + str(net) + "%", nearby_wrong_float(net), + f"Net = {p1} + {p2} + ({p1}Ɨ{p2})/100 = {net}%", 2)) + return questions + + +def gen_profit_loss(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Profit and Loss", "Basic Profit and Loss", "Find profit/loss percentage") + if qtid: + for _ in range(count // 3): + cp = random.randint(100, 5000) + margin = random.randint(5, 50) + is_profit = random.choice([True, False]) + sp = cp + cp * margin // 100 if is_profit else cp - cp * margin // 100 + word = "profit" if is_profit else "loss" + questions.append(make_question(qtid, + f"An article bought for ₹{cp} is sold for ₹{sp}. Find the {word} percentage.", + str(margin) + "%", [str(margin + i) + "%" for i in [2, -3, 5]], + f"{word.title()} = {abs(sp-cp)}/{cp} Ɨ 100 = {margin}%", 1)) + + qtid = get_qtid(conn, SUBJECT, "Profit and Loss", "Discount and Marked Price", "Find selling price after discount") + if qtid: + for _ in range(count // 3): + mp = random.randint(200, 10000) + d = random.choice([5, 10, 15, 20, 25, 30, 40, 50]) + sp = mp - mp * d // 100 + questions.append(make_question(qtid, + f"The marked price of an article is ₹{mp}. If a discount of {d}% is given, find the selling price.", + f"₹{sp}", [f"₹{sp + i*10}" for i in [1, -2, 3]], + f"SP = {mp} - {d}% of {mp} = ₹{sp}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Profit and Loss", "Discount and Marked Price", "Find single equivalent discount") + if qtid: + for _ in range(count // 3): + d1, d2 = random.choice([10,15,20,25,30]), random.choice([5,10,15,20]) + eq = round(d1 + d2 - d1 * d2 / 100, 2) + questions.append(make_question(qtid, + f"Find the single equivalent discount for successive discounts of {d1}% and {d2}%.", + str(eq) + "%", nearby_wrong_float(eq), + f"Equivalent = {d1} + {d2} - ({d1}Ɨ{d2})/100 = {eq}%", 2)) + return questions + + +def gen_ratio(conn, count=1800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Ratio and Proportion", "Basic Ratio", "Divide in given ratio") + if qtid: + for _ in range(count // 4): + a, b = random.randint(1, 10), random.randint(1, 10) + total = (a + b) * random.randint(10, 100) + part_a, part_b = total * a // (a + b), total * b // (a + b) + questions.append(make_question(qtid, + f"Divide ₹{total} in the ratio {a}:{b}. Find the larger share.", + f"₹{max(part_a, part_b)}", [f"₹{max(part_a,part_b) + i*10}" for i in [1,-2,3]], + f"Larger share = {total} Ɨ {max(a,b)}/{a+b} = ₹{max(part_a,part_b)}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Ratio and Proportion", "Mixture and Alligation", "Mean price of mixture") + if qtid: + for _ in range(count // 4): + p1, p2 = random.randint(20, 60), random.randint(60, 120) + q1, q2 = random.randint(1, 10), random.randint(1, 10) + mean = round((p1*q1 + p2*q2) / (q1+q2), 2) + questions.append(make_question(qtid, + f"If {q1} kg of rice at ₹{p1}/kg is mixed with {q2} kg of rice at ₹{p2}/kg, find the price of the mixture per kg.", + f"₹{mean}", [f"₹{round(mean+i,2)}" for i in [2,-3,5]], + f"Mean = ({p1}Ɨ{q1} + {p2}Ɨ{q2})/{q1+q2} = ₹{mean}", 2)) + + qtid = get_qtid(conn, SUBJECT, "Ratio and Proportion", "Partnership", "Divide profit among partners") + if qtid: + for _ in range(count // 4): + inv_a = random.randint(1000, 10000) + inv_b = random.randint(1000, 10000) + profit = random.randint(5000, 50000) + share_a = round(profit * inv_a / (inv_a + inv_b)) + questions.append(make_question(qtid, + f"A invests ₹{inv_a} and B invests ₹{inv_b}. Total profit is ₹{profit}. Find A's share.", + f"₹{share_a}", [f"₹{share_a + i*100}" for i in [1,-2,3]], + f"A's share = {profit} Ɨ {inv_a}/({inv_a}+{inv_b}) = ₹{share_a}", 2)) + + qtid = get_qtid(conn, SUBJECT, "Ratio and Proportion", "Proportion", "Find fourth proportional") + if qtid: + for _ in range(count // 4): + a, b, c = random.randint(2, 20), random.randint(2, 20), random.randint(2, 20) + d = b * c // a if a != 0 else 1 + if a * d == b * c: + questions.append(make_question(qtid, + f"Find the fourth proportional to {a}, {b}, and {c}.", + str(d), nearby_wrong(d), + f"a:b = c:d → d = bƗc/a = {b}Ɨ{c}/{a} = {d}", 1)) + return questions + + +def gen_average(conn, count=1200): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Average", "Simple Average", "Find average") + if qtid: + for _ in range(count // 3): + n = random.randint(3, 8) + nums = [random.randint(10, 100) for _ in range(n)] + avg = round(sum(nums) / n, 2) + nums_str = ", ".join(map(str, nums)) + questions.append(make_question(qtid, + f"Find the average of {nums_str}.", + str(avg), nearby_wrong_float(avg), + f"Average = {sum(nums)}/{n} = {avg}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Average", "Simple Average", "Average after adding/removing") + if qtid: + for _ in range(count // 3): + n = random.randint(5, 15) + avg = random.randint(20, 80) + new_val = random.randint(avg + 5, avg + 50) + new_avg = round((avg * n + new_val) / (n + 1), 2) + questions.append(make_question(qtid, + f"The average of {n} numbers is {avg}. When a new number {new_val} is added, what is the new average?", + str(new_avg), nearby_wrong_float(new_avg), + f"New avg = ({avg}Ɨ{n} + {new_val})/{n+1} = {new_avg}", 2)) + + qtid = get_qtid(conn, SUBJECT, "Average", "Age Problems", "Average age of family") + if qtid: + for _ in range(count // 3): + n = random.randint(3, 6) + avg_age = random.randint(20, 40) + baby_age = random.randint(1, 5) + new_avg = round((avg_age * n + baby_age) / (n + 1), 2) + questions.append(make_question(qtid, + f"The average age of {n} members of a family is {avg_age} years. A baby of {baby_age} year(s) is born. Find the new average age.", + str(new_avg) + " years", [str(round(new_avg+i,2)) + " years" for i in [1,-2,3]], + f"New avg = ({avg_age}Ɨ{n} + {baby_age})/{n+1} = {new_avg} years", 1)) + return questions + + +def gen_time_work(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Time and Work", "Basic Time and Work", "Find combined time") + if qtid: + for _ in range(count // 3): + a, b = random.randint(5, 30), random.randint(5, 30) + combined = round(a * b / (a + b), 2) + questions.append(make_question(qtid, + f"A can do a work in {a} days and B can do it in {b} days. In how many days can they do it together?", + str(combined) + " days", [str(round(combined+i,2)) + " days" for i in [1,-2,3]], + f"Together = {a}Ɨ{b}/({a}+{b}) = {combined} days", 2)) + + qtid = get_qtid(conn, SUBJECT, "Time and Work", "Pipes and Cisterns", "Time to fill tank") + if qtid: + for _ in range(count // 3): + a, b = random.randint(5, 30), random.randint(5, 30) + combined = round(a * b / (a + b), 2) + questions.append(make_question(qtid, + f"Two pipes can fill a tank in {a} hours and {b} hours respectively. How long to fill the tank if both are opened?", + str(combined) + " hours", [str(round(combined+i,2)) + " hours" for i in [1,-2,3]], + f"Together = {a}Ɨ{b}/({a}+{b}) = {combined} hours", 2)) + + qtid = get_qtid(conn, SUBJECT, "Time and Work", "Work and Wages", "Divide wages") + if qtid: + for _ in range(count // 3): + da, db = random.randint(5, 20), random.randint(5, 20) + wage = random.randint(1000, 10000) + eff_a, eff_b = 1/da, 1/db + share_a = round(wage * eff_a / (eff_a + eff_b)) + questions.append(make_question(qtid, + f"A can do a work in {da} days and B in {db} days. For a total wage of ₹{wage}, find A's share.", + f"₹{share_a}", [f"₹{share_a + i*50}" for i in [1,-2,3]], + f"A's share = ₹{wage} Ɨ (1/{da}) / (1/{da} + 1/{db}) = ₹{share_a}", 2)) + return questions + + +def gen_speed_distance(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Time, Speed and Distance", "Basic Speed and Distance", "Average speed") + if qtid: + for _ in range(count // 4): + s1, s2 = random.randint(20, 80), random.randint(20, 80) + avg = round(2 * s1 * s2 / (s1 + s2), 2) + questions.append(make_question(qtid, + f"A person goes from A to B at {s1} km/h and returns at {s2} km/h. Find the average speed.", + str(avg) + " km/h", [str(round(avg+i,2)) + " km/h" for i in [2,-3,5]], + f"Avg speed = 2Ɨ{s1}Ɨ{s2}/({s1}+{s2}) = {avg} km/h", 2)) + + qtid = get_qtid(conn, SUBJECT, "Time, Speed and Distance", "Trains", "Train passing pole") + if qtid: + for _ in range(count // 4): + length = random.randint(100, 500) + speed_kmh = random.randint(36, 144) + speed_ms = round(speed_kmh * 5 / 18, 2) + time = round(length / speed_ms, 2) + questions.append(make_question(qtid, + f"A train {length}m long passes a pole in {time} seconds. Find its speed in km/h.", + str(speed_kmh) + " km/h", [str(speed_kmh + i) + " km/h" for i in [4,-6,9]], + f"Speed = {length}/{time} m/s = {speed_kmh} km/h", 2)) + + qtid = get_qtid(conn, SUBJECT, "Time, Speed and Distance", "Boats and Streams", "Find speed in still water") + if qtid: + for _ in range(count // 4): + boat = random.randint(10, 30) + stream = random.randint(2, 8) + ds = boat + stream + us = boat - stream + questions.append(make_question(qtid, + f"A boat goes {ds} km/h downstream and {us} km/h upstream. Find speed in still water.", + str(boat) + " km/h", [str(boat + i) + " km/h" for i in [1, -2, 3]], + f"Speed = ({ds}+{us})/2 = {boat} km/h", 1)) + + qtid = get_qtid(conn, SUBJECT, "Time, Speed and Distance", "Basic Speed and Distance", "Find distance") + if qtid: + for _ in range(count // 4): + speed = random.randint(20, 100) + time = random.randint(1, 10) + dist = speed * time + questions.append(make_question(qtid, + f"A car travels at {speed} km/h for {time} hours. Find the distance covered.", + str(dist) + " km", nearby_wrong(dist), + f"Distance = {speed} Ɨ {time} = {dist} km", 1)) + return questions + + +def gen_interest(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Interest", "Simple Interest", "Find SI") + if qtid: + for _ in range(count // 3): + p = random.choice([1000, 2000, 5000, 8000, 10000, 15000, 20000]) + r = random.choice([4, 5, 6, 8, 10, 12, 15]) + t = random.randint(1, 5) + si = p * r * t // 100 + questions.append(make_question(qtid, + f"Find the simple interest on ₹{p} at {r}% per annum for {t} years.", + f"₹{si}", [f"₹{si + i*50}" for i in [1,-2,3]], + f"SI = {p}Ɨ{r}Ɨ{t}/100 = ₹{si}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Interest", "Compound Interest", "Find CI") + if qtid: + for _ in range(count // 3): + p = random.choice([1000, 2000, 5000, 10000]) + r = random.choice([5, 10, 15, 20]) + t = random.choice([1, 2, 3]) + amt = round(p * (1 + r/100)**t, 2) + ci = round(amt - p, 2) + questions.append(make_question(qtid, + f"Find the compound interest on ₹{p} at {r}% for {t} year(s).", + f"₹{ci}", [f"₹{round(ci+i*20,2)}" for i in [1,-2,3]], + f"CI = {p}(1+{r}/100)^{t} - {p} = ₹{ci}", 2)) + + qtid = get_qtid(conn, SUBJECT, "Interest", "Compound Interest", "Difference between CI and SI") + if qtid: + for _ in range(count // 3): + p = random.choice([1000, 2000, 5000, 10000]) + r = random.choice([5, 10, 15, 20]) + si = p * r * 2 // 100 + ci = round(p * (1 + r/100)**2 - p, 2) + diff = round(ci - si, 2) + questions.append(make_question(qtid, + f"Find the difference between CI and SI on ₹{p} at {r}% for 2 years.", + f"₹{diff}", [f"₹{round(diff+i*5,2)}" for i in [1,-2,3]], + f"Diff = PƗ(r/100)² = {p}Ɨ({r}/100)² = ₹{diff}", 2)) + return questions + + +def gen_algebra(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Algebra", "Linear Equations", "Solve for x") + if qtid: + for _ in range(count // 4): + a = random.randint(2, 15) + x = random.randint(1, 30) + b = random.randint(1, 50) + c = a * x + b + questions.append(make_question(qtid, + f"Solve: {a}x + {b} = {c}", + f"x = {x}", [f"x = {x+i}" for i in [1,-2,3]], + f"{a}x = {c} - {b} = {c-b}, x = {x}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Algebra", "Algebraic Identities", "Find value of expression") + if qtid: + for _ in range(count // 4): + a, b = random.randint(2, 20), random.randint(1, 15) + val = a*a + b*b + 2*a*b # (a+b)² + questions.append(make_question(qtid, + f"If a = {a} and b = {b}, find a² + b² + 2ab.", + str(val), nearby_wrong(val), + f"a² + b² + 2ab = (a+b)² = ({a}+{b})² = {val}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Algebra", "Surds and Indices", "Find value") + if qtid: + for _ in range(count // 4): + base = random.randint(2, 10) + exp = random.randint(2, 5) + val = base ** exp + questions.append(make_question(qtid, + f"Find the value of {base}^{exp}.", + str(val), nearby_wrong(val), + f"{base}^{exp} = {val}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Algebra", "Quadratic Equations", "Sum and product of roots") + if qtid: + for _ in range(count // 4): + r1, r2 = random.randint(-10, 10), random.randint(-10, 10) + a = 1 + b = -(r1 + r2) + c = r1 * r2 + s = r1 + r2 + questions.append(make_question(qtid, + f"Find the sum of roots of x² + ({b})x + ({c}) = 0.", + str(s), nearby_wrong(s), + f"Sum = -b/a = {s}", 1)) + return questions + + +def gen_geometry(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Geometry", "Triangles", "Find angle in triangle") + if qtid: + for _ in range(count // 4): + a = random.randint(20, 80) + b = random.randint(20, 160 - a) + c = 180 - a - b + questions.append(make_question(qtid, + f"In a triangle, two angles are {a}° and {b}°. Find the third angle.", + f"{c}°", [f"{c+i}°" for i in [5,-10,15]], + f"Third angle = 180 - {a} - {b} = {c}°", 1)) + + qtid = get_qtid(conn, SUBJECT, "Geometry", "Circles", "Tangent properties") + if qtid: + for _ in range(count // 4): + r = random.randint(3, 20) + d = random.randint(r + 5, r + 30) + tangent = round(math.sqrt(d*d - r*r), 2) + questions.append(make_question(qtid, + f"The radius of a circle is {r} cm and a point is {d} cm from the center. Find the length of the tangent.", + str(tangent) + " cm", [str(round(tangent+i,2)) + " cm" for i in [1,-2,3]], + f"Tangent = √({d}²-{r}²) = {tangent} cm", 2)) + + qtid = get_qtid(conn, SUBJECT, "Geometry", "Coordinate Geometry", "Find distance between points") + if qtid: + for _ in range(count // 4): + x1, y1 = random.randint(-10, 10), random.randint(-10, 10) + x2, y2 = random.randint(-10, 10), random.randint(-10, 10) + dist = round(math.sqrt((x2-x1)**2 + (y2-y1)**2), 2) + questions.append(make_question(qtid, + f"Find the distance between ({x1}, {y1}) and ({x2}, {y2}).", + str(dist), nearby_wrong_float(dist), + f"Distance = √[({x2}-{x1})² + ({y2}-{y1})²] = {dist}", 2)) + + qtid = get_qtid(conn, SUBJECT, "Geometry", "Lines and Angles", "Find angle value") + if qtid: + for _ in range(count // 4): + angle = random.randint(10, 170) + supp = 180 - angle + questions.append(make_question(qtid, + f"Find the supplement of {angle}°.", + f"{supp}°", [f"{supp+i}°" for i in [5,-10,15]], + f"Supplement = 180 - {angle} = {supp}°", 1)) + return questions + + +def gen_mensuration(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Mensuration", "2D Figures", "Find area") + if qtid: + for _ in range(count // 4): + l, w = random.randint(5, 50), random.randint(5, 50) + area = l * w + questions.append(make_question(qtid, + f"Find the area of a rectangle with length {l} cm and width {w} cm.", + f"{area} cm²", [f"{area+i*5} cm²" for i in [1,-2,3]], + f"Area = {l} Ɨ {w} = {area} cm²", 1)) + for _ in range(count // 4): + r = random.randint(3, 25) + area = round(math.pi * r * r, 2) + questions.append(make_question(qtid, + f"Find the area of a circle with radius {r} cm. (Use Ļ€ = 3.14)", + str(round(3.14 * r * r, 2)) + " cm²", + [str(round(3.14 * r * r + i * 5, 2)) + " cm²" for i in [1, -2, 3]], + f"Area = Ļ€r² = 3.14 Ɨ {r}² = {round(3.14*r*r,2)} cm²", 1)) + + qtid = get_qtid(conn, SUBJECT, "Mensuration", "3D Figures", "Find volume") + if qtid: + for _ in range(count // 4): + l, w, h = random.randint(3, 20), random.randint(3, 20), random.randint(3, 20) + vol = l * w * h + questions.append(make_question(qtid, + f"Find the volume of a cuboid with dimensions {l}Ɨ{w}Ɨ{h} cm.", + f"{vol} cm³", [f"{vol+i*10} cm³" for i in [1,-2,3]], + f"Volume = {l}Ɨ{w}Ɨ{h} = {vol} cm³", 1)) + + qtid = get_qtid(conn, SUBJECT, "Mensuration", "3D Figures", "Find surface area") + if qtid: + for _ in range(count // 4): + a = random.randint(3, 20) + sa = 6 * a * a + questions.append(make_question(qtid, + f"Find the total surface area of a cube with side {a} cm.", + f"{sa} cm²", [f"{sa+i*6} cm²" for i in [1,-2,3]], + f"TSA = 6a² = 6Ɨ{a}² = {sa} cm²", 1)) + return questions + + +def gen_trigonometry(conn, count=1500): + questions = [] + trig_vals = {0: {'sin': 0, 'cos': 1, 'tan': 0}, + 30: {'sin': 0.5, 'cos': 0.866, 'tan': 0.577}, + 45: {'sin': 0.707, 'cos': 0.707, 'tan': 1}, + 60: {'sin': 0.866, 'cos': 0.5, 'tan': 1.732}, + 90: {'sin': 1, 'cos': 0, 'tan': 'undefined'}} + + qtid = get_qtid(conn, SUBJECT, "Trigonometry", "Trigonometric Ratios", "Find value of expression") + if qtid: + for _ in range(count // 3): + ang = random.choice([0, 30, 45, 60]) + func = random.choice(['sin', 'cos', 'tan']) + val = trig_vals[ang][func] + if isinstance(val, (int, float)): + questions.append(make_question(qtid, + f"Find the value of {func}({ang}°).", + str(val), nearby_wrong_float(val if val != 0 else 0.5), + f"{func}({ang}°) = {val}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Trigonometry", "Trigonometric Identities", "Find value given condition") + if qtid: + for _ in range(count // 3): + ang = random.choice([30, 45, 60]) + val = trig_vals[ang]['sin']**2 + trig_vals[ang]['cos']**2 + questions.append(make_question(qtid, + f"Find the value of sin²({ang}°) + cos²({ang}°).", + "1", ["0", "2", "0.5"], + f"sin²θ + cos²θ = 1 (identity)", 1)) + + qtid = get_qtid(conn, SUBJECT, "Trigonometry", "Heights and Distances", "Find height of tower") + if qtid: + for _ in range(count // 3): + dist = random.randint(20, 100) + ang = random.choice([30, 45, 60]) + height = round(dist * trig_vals[ang]['tan'], 2) if ang != 90 else dist + questions.append(make_question(qtid, + f"From a point {dist}m from the base of a tower, the angle of elevation is {ang}°. Find the height.", + str(height) + " m", [str(round(height+i,2)) + " m" for i in [2,-5,8]], + f"h = {dist} Ɨ tan({ang}°) = {height} m", 2)) + return questions + + +def gen_di(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Data Interpretation", "Table", "Calculate from table") + if qtid: + for _ in range(count // 2): + vals = [random.randint(100, 1000) for _ in range(5)] + total = sum(vals) + avg = round(total / 5, 2) + years = ["2019", "2020", "2021", "2022", "2023"] + table = ", ".join(f"{y}: {v}" for y, v in zip(years, vals)) + questions.append(make_question(qtid, + f"Production (in units) - {table}. Find the total production.", + str(total), nearby_wrong(total), + f"Total = {'+'.join(map(str, vals))} = {total}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Data Interpretation", "Table", "Percentage calculation") + if qtid: + for _ in range(count // 2): + vals = [random.randint(100, 1000) for _ in range(5)] + idx = random.randint(0, 4) + total = sum(vals) + pct = round(vals[idx] * 100 / total, 2) + years = ["2019", "2020", "2021", "2022", "2023"] + table = ", ".join(f"{y}: {v}" for y, v in zip(years, vals)) + questions.append(make_question(qtid, + f"Sales - {table}. What percentage of total sales occurred in {years[idx]}?", + str(pct) + "%", [str(round(pct+i,2)) + "%" for i in [2,-3,5]], + f"{vals[idx]}/{total} Ɨ 100 = {pct}%", 2)) + return questions + + +def gen_statistics(conn, count=800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Statistics", "Mean, Median and Mode", "Find mean") + if qtid: + for _ in range(count // 3): + n = random.randint(5, 10) + nums = sorted([random.randint(1, 100) for _ in range(n)]) + mean = round(sum(nums) / n, 2) + questions.append(make_question(qtid, + f"Find the mean of: {', '.join(map(str, nums))}", + str(mean), nearby_wrong_float(mean), + f"Mean = {sum(nums)}/{n} = {mean}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Statistics", "Mean, Median and Mode", "Find median") + if qtid: + for _ in range(count // 3): + n = random.choice([5, 7, 9]) + nums = sorted([random.randint(1, 100) for _ in range(n)]) + median = nums[n // 2] + questions.append(make_question(qtid, + f"Find the median of: {', '.join(map(str, nums))}", + str(median), nearby_wrong(median), + f"Sorted: {nums}, Median = {median}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Statistics", "Mean, Median and Mode", "Find mode") + if qtid: + for _ in range(count // 3): + mode_val = random.randint(1, 50) + nums = [mode_val] * 3 + [random.randint(1, 100) for _ in range(4)] + random.shuffle(nums) + questions.append(make_question(qtid, + f"Find the mode of: {', '.join(map(str, nums))}", + str(mode_val), nearby_wrong(mode_val), + f"Mode = {mode_val} (appears most)", 1)) + return questions + + +def generate_all(conn): + """Generate all Quantitative Aptitude questions.""" + generators = [ + ("Number System", gen_number_system, 800), + ("HCF/LCM", gen_hcf_lcm, 500), + ("Simplification", gen_simplification, 600), + ("Percentage", gen_percentage, 2500), + ("Profit & Loss", gen_profit_loss, 2500), + ("Ratio & Proportion", gen_ratio, 2200), + ("Average", gen_average, 1500), + ("Time & Work", gen_time_work, 1800), + ("Speed & Distance", gen_speed_distance, 2500), + ("Interest", gen_interest, 2000), + ("Algebra", gen_algebra, 2500), + ("Geometry", gen_geometry, 2500), + ("Mensuration", gen_mensuration, 2500), + ("Trigonometry", gen_trigonometry, 2000), + ("Data Interpretation", gen_di, 2000), + ("Statistics", gen_statistics, 1200), + ] + + total = 0 + all_questions = [] + for name, gen_func, count in generators: + questions = gen_func(conn, count) + all_questions.extend(questions) + print(f" {name}: {len(questions)} questions") + total += len(questions) + + # Insert in batches + batch_size = 5000 + for i in range(0, len(all_questions), batch_size): + insert_questions_batch(conn, all_questions[i:i+batch_size]) + + print(f" TOTAL Quantitative Aptitude: {total}") + return total + + +if __name__ == '__main__': + conn = get_db() + print("Generating Quantitative Aptitude questions...") + generate_all(conn) + conn.close() diff --git a/generators/reasoning_generator.py b/generators/reasoning_generator.py new file mode 100644 index 0000000..859029b --- /dev/null +++ b/generators/reasoning_generator.py @@ -0,0 +1,510 @@ +#!/usr/bin/env python3 +""" +General Intelligence & Reasoning Question Generator for SSC CGL. +Generates ~25,000 template-based reasoning questions. +""" +import random +import string +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +from generators.base import make_question, get_qtid, nearby_wrong, get_db, insert_questions_batch + +SUBJECT = "General Intelligence and Reasoning" + + +# ============ VERBAL REASONING ============ + +def gen_number_analogy(conn, count=700): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Analogy", "Number analogy") + if not qtid: return questions + for _ in range(count): + a = random.randint(2, 20) + ops = [ + (a*a, lambda x: x*x, "square"), + (a*a*a, lambda x: x*x*x, "cube"), + (a*2, lambda x: x*2, "double"), + (a+5, lambda x: x+5, "add 5"), + ] + result_a, func, rule = random.choice(ops) + b = random.randint(2, 20) + while b == a: + b = random.randint(2, 20) + result_b = func(b) + questions.append(make_question(qtid, + f"{a} : {result_a} :: {b} : ?", + str(result_b), nearby_wrong(result_b), + f"Rule: {rule}. {a}→{result_a}, {b}→{result_b}", 1)) + return questions + + +def gen_letter_analogy(conn, count=500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Analogy", "Letter analogy") + if not qtid: return questions + for _ in range(count): + shift = random.randint(1, 5) + a = random.randint(0, 20) + pair1 = chr(65+a) + chr(65+a+shift) + b = random.randint(0, 20) + while b == a: + b = random.randint(0, 20) + pair2_q = chr(65+b) + pair2_a = chr(65+b+shift) + wrongs = [chr(65 + (b+shift+i) % 26) for i in [1, 2, -1]] + questions.append(make_question(qtid, + f"{pair1} : {pair2_q}?", + pair2_q + pair2_a, [pair2_q + w for w in wrongs], + f"Shift by {shift}: {pair1} → {pair2_q}{pair2_a}", 1)) + return questions + + +def gen_classification(conn, count=1500): + questions = [] + # Number classification (odd one out) + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Classification", "Number classification") + if qtid: + for _ in range(count // 2): + t = random.choice(["even", "odd", "prime", "square"]) + if t == "even": + group = [random.randint(1, 50) * 2 for _ in range(3)] + odd_one = random.randint(1, 50) * 2 + 1 + elif t == "odd": + group = [random.randint(0, 49) * 2 + 1 for _ in range(3)] + odd_one = random.randint(1, 50) * 2 + elif t == "prime": + primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] + group = random.sample(primes, 3) + odd_one = random.choice([4,6,8,9,10,12,14,15,16,18,20,21,22]) + else: + squares = [1,4,9,16,25,36,49,64,81,100] + group = random.sample(squares, 3) + odd_one = random.choice([2,3,5,6,7,8,10,11,12,13,14,15]) + all_opts = group + [odd_one] + random.shuffle(all_opts) + questions.append(make_question(qtid, + f"Find the odd one out: {', '.join(map(str, all_opts))}", + str(odd_one), [str(x) for x in group], + f"{odd_one} is not {t}", 1)) + + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Classification", "Word classification") + if qtid: + word_groups = [ + (["Apple", "Mango", "Banana", "Orange"], "Carrot", "Fruits"), + (["Dog", "Cat", "Lion", "Tiger"], "Eagle", "Mammals"), + (["Red", "Blue", "Green", "Yellow"], "Square", "Colors"), + (["Delhi", "Mumbai", "Chennai", "Kolkata"], "India", "Cities"), + (["Pen", "Pencil", "Marker", "Crayon"], "Book", "Writing tools"), + (["Piano", "Guitar", "Violin", "Flute"], "Painting", "Instruments"), + (["January", "March", "May", "July"], "Monday", "Months"), + (["Mercury", "Venus", "Mars", "Jupiter"], "Moon", "Planets"), + (["Nile", "Amazon", "Ganges", "Thames"], "Sahara", "Rivers"), + (["Football", "Cricket", "Tennis", "Hockey"], "Chess", "Outdoor sports"), + ] + for _ in range(count // 2): + group, odd, reason = random.choice(word_groups) + display = random.sample(group[:3], 3) + [odd] + random.shuffle(display) + questions.append(make_question(qtid, + f"Find the odd one out: {', '.join(display)}", + odd, [x for x in display if x != odd][:3], + f"{odd} is not in the category: {reason}", 1)) + return questions + + +def gen_number_series(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Number Series", "Find next number") + if not qtid: return questions + for _ in range(count): + series_type = random.choice(["add", "multiply", "square", "alternate", "diff"]) + if series_type == "add": + start = random.randint(1, 50) + d = random.randint(2, 15) + series = [start + i * d for i in range(5)] + ans = start + 5 * d + elif series_type == "multiply": + start = random.randint(1, 5) + r = random.choice([2, 3]) + series = [start * (r ** i) for i in range(5)] + ans = start * (r ** 5) + elif series_type == "square": + start = random.randint(1, 8) + series = [(start + i) ** 2 for i in range(5)] + ans = (start + 5) ** 2 + elif series_type == "alternate": + a, b = random.randint(1, 10), random.randint(1, 10) + series = [] + for i in range(5): + series.append(series[-1] + a if i % 2 == 0 else series[-1] + b) if series else series.append(random.randint(1, 20)) + if i == 0: + continue + if i % 2 == 1: + series[-1] = series[-2] + a + else: + series[-1] = series[-2] + b + ans = series[-1] + (a if len(series) % 2 == 1 else b) + else: # increasing difference + start = random.randint(1, 10) + series = [start] + d = random.randint(1, 5) + for i in range(4): + series.append(series[-1] + d + i) + ans = series[-1] + d + 4 + + series_str = ", ".join(map(str, series)) + questions.append(make_question(qtid, + f"Find the next number in the series: {series_str}, ?", + str(ans), nearby_wrong(ans), + f"Pattern: {series_type}. Next = {ans}", random.choice([1, 2]))) + return questions + + +def gen_coding_decoding(conn, count=2000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Coding-Decoding", "Letter coding") + if qtid: + for _ in range(count // 2): + shift = random.randint(1, 5) + word = random.choice(["COME", "GONE", "HELP", "LOVE", "MIND", "PLAY", "ROSE", "SING", "TALK", "WIND", + "BACK", "DEEP", "FAST", "GIRL", "HOME", "JUST", "KING", "LAMP", "NAME", "OPEN"]) + coded = "".join(chr((ord(c) - 65 + shift) % 26 + 65) for c in word) + word2 = random.choice(["BALL", "CAKE", "DARK", "EASY", "FISH", "GOOD", "HAND", "IDOL", "JOKE", "KEEP"]) + coded2 = "".join(chr((ord(c) - 65 + shift) % 26 + 65) for c in word2) + wrongs = [] + for s in [shift+1, shift-1, shift+2]: + wrongs.append("".join(chr((ord(c) - 65 + s) % 26 + 65) for c in word2)) + questions.append(make_question(qtid, + f"If {word} is coded as {coded}, then {word2} is coded as?", + coded2, wrongs, + f"Each letter shifted by +{shift}", 2)) + + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Coding-Decoding", "Number coding") + if qtid: + for _ in range(count // 2): + word = random.choice(["CAT", "DOG", "SUN", "PEN", "CUP", "BOX", "HAT", "MAP", "JAR", "FAN"]) + code = [random.randint(1, 9) for _ in word] + code_str = "".join(map(str, code)) + word2 = random.choice(["BAT", "LOG", "RUN", "HEN", "BUS", "FOX", "RAT", "TAP"]) + # Same position mapping + mapping = {c: str(v) for c, v in zip(word, code)} + code2 = "".join(mapping.get(c, str(random.randint(1, 9))) for c in word2) + wrongs = [str(int(code2) + i) for i in [11, -22, 33]] + questions.append(make_question(qtid, + f"If {word} = {code_str}, then {word2} = ?", + code2, wrongs, + f"Letter-to-number mapping from {word}={code_str}", 2)) + return questions + + +# ============ LOGICAL REASONING ============ + +def gen_blood_relations(conn, count=1500): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Logical Reasoning", "Blood Relations", "Direct relation") + if not qtid: return questions + + templates = [ + ("A is the father of B. B is the sister of C. What is A to C?", "Father", ["Uncle", "Brother", "Grandfather"]), + ("A is the mother of B. B is the brother of C. What is A to C?", "Mother", ["Aunt", "Sister", "Grandmother"]), + ("A is the brother of B. B is the son of C. What is A to C?", "Son", ["Nephew", "Brother", "Father"]), + ("A is the sister of B. B is the daughter of C. What is A to C?", "Daughter", ["Niece", "Sister", "Mother"]), + ("A is the husband of B. B is the mother of C. What is A to C?", "Father", ["Uncle", "Brother", "Grandfather"]), + ("A is the wife of B. B is the father of C. What is A to C?", "Mother", ["Aunt", "Sister", "Grandmother"]), + ("A's father is B's son. What is B to A?", "Grandfather", ["Father", "Uncle", "Brother"]), + ("A's mother is B's daughter. What is B to A?", "Grandmother", ["Mother", "Aunt", "Sister"]), + ("A is B's brother's wife. What is A to B?", "Sister-in-law", ["Sister", "Cousin", "Aunt"]), + ("A is B's father's brother. What is A to B?", "Uncle", ["Father", "Cousin", "Grandfather"]), + ] + + for _ in range(count): + q_text, correct, wrongs = random.choice(templates) + names = random.sample(["P", "Q", "R", "S", "T", "M", "N", "X", "Y", "Z"], 3) + q_text = q_text.replace("A", names[0]).replace("B", names[1]).replace("C", names[2]) + questions.append(make_question(qtid, q_text, correct, wrongs, + f"Following family relationships, the answer is {correct}", 2)) + return questions + + +def gen_direction(conn, count=1200): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Logical Reasoning", "Direction and Distance", "Find final direction") + if not qtid: return questions + + directions = ["North", "South", "East", "West"] + turns = {"North": {"right": "East", "left": "West"}, + "South": {"right": "West", "left": "East"}, + "East": {"right": "South", "left": "North"}, + "West": {"right": "North", "left": "South"}} + + for _ in range(count): + start = random.choice(directions) + num_turns = random.randint(1, 3) + current = start + steps_desc = [f"starts facing {start}"] + for _ in range(num_turns): + turn = random.choice(["right", "left"]) + current = turns[current][turn] + steps_desc.append(f"turns {turn}") + wrong_dirs = [d for d in directions if d != current] + questions.append(make_question(qtid, + f"A person {', '.join(steps_desc)}. Which direction is the person facing now?", + current, wrong_dirs[:3], + f"After turns: {current}", 1)) + return questions + + +def gen_ranking(conn, count=1200): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Logical Reasoning", "Order and Ranking", "Find rank from top/bottom") + if not qtid: return questions + for _ in range(count): + total = random.randint(20, 60) + from_top = random.randint(1, total) + from_bottom = total - from_top + 1 + ask = random.choice(["top", "bottom"]) + if ask == "top": + questions.append(make_question(qtid, + f"In a row of {total} students, a student is {from_bottom}th from the bottom. What is the student's position from the top?", + str(from_top), nearby_wrong(from_top), + f"From top = Total - From bottom + 1 = {total} - {from_bottom} + 1 = {from_top}", 1)) + else: + questions.append(make_question(qtid, + f"In a row of {total} students, a student is {from_top}th from the top. What is the student's position from the bottom?", + str(from_bottom), nearby_wrong(from_bottom), + f"From bottom = Total - From top + 1 = {total} - {from_top} + 1 = {from_bottom}", 1)) + return questions + + +def gen_syllogism(conn, count=1000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Logical Reasoning", "Syllogism", "All/Some/No conclusions") + if not qtid: return questions + + templates = [ + ("All A are B. All B are C.", "All A are C", ["No A is C", "Some A are not C", "All C are A"]), + ("All A are B. Some B are C.", "Some A may be C", ["All A are C", "No A is C", "All C are A"]), + ("No A is B. All B are C.", "Some C are not A", ["All A are C", "No C is A", "All C are A"]), + ("Some A are B. All B are C.", "Some A are C", ["All A are C", "No A is C", "All C are A"]), + ("All A are B. No B is C.", "No A is C", ["Some A are C", "All A are C", "All C are A"]), + ] + categories = ["dogs", "cats", "birds", "students", "teachers", "doctors", "players", "singers", + "dancers", "painters", "writers", "engineers", "lawyers", "flowers", "trees"] + + for _ in range(count): + template, correct, wrongs = random.choice(templates) + cats = random.sample(categories, 3) + stmt = template.replace("A", cats[0].title()).replace("B", cats[1].title()).replace("C", cats[2].title()) + ans = correct.replace("A", cats[0].title()).replace("B", cats[1].title()).replace("C", cats[2].title()) + wrong_list = [w.replace("A", cats[0].title()).replace("B", cats[1].title()).replace("C", cats[2].title()) for w in wrongs] + questions.append(make_question(qtid, + f"Statements: {stmt}\nConclusion: Which follows?", + ans, wrong_list, f"Based on Venn diagram logic", 2)) + return questions + + +# ============ NON-VERBAL REASONING ============ + +def gen_mirror_image(conn, count=800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Non-Verbal Reasoning", "Mirror and Water Image", "Mirror image of text/numbers") + if not qtid: return questions + for _ in range(count): + num = random.randint(100, 9999) + mirror = str(num)[::-1] + wrongs = [str(num + random.randint(1, 100)) for _ in range(3)] + questions.append(make_question(qtid, + f"What is the mirror image of the number {num} when a mirror is placed on the right side?", + mirror, wrongs, + f"Mirror reverses left-right: {num} → {mirror}", 1)) + return questions + + +def gen_dice(conn, count=800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Non-Verbal Reasoning", "Dice and Cube", "Opposite face of dice") + if not qtid: return questions + for _ in range(count): + faces = list(range(1, 7)) + # Standard dice: opposite faces sum to 7 + num = random.randint(1, 6) + opp = 7 - num + questions.append(make_question(qtid, + f"On a standard die, what number is opposite to {num}?", + str(opp), [str(x) for x in range(1, 7) if x != num and x != opp][:3], + f"On a standard die, opposite faces sum to 7: {num} + {opp} = 7", 1)) + return questions + + +def gen_cube_painting(conn, count=600): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Non-Verbal Reasoning", "Dice and Cube", "Painted cube counting") + if not qtid: return questions + for _ in range(count): + n = random.randint(2, 6) + total = n ** 3 + three_face = 8 # corners + two_face = (n - 2) * 12 if n > 2 else 0 + one_face = (n - 2) ** 2 * 6 if n > 2 else 0 + no_face = (n - 2) ** 3 if n > 2 else 0 + ask = random.choice(["three", "two", "one", "no"]) + ans_map = {"three": three_face, "two": two_face, "one": one_face, "no": no_face} + ans = ans_map[ask] + questions.append(make_question(qtid, + f"A cube of side {n} is painted on all faces and then cut into {total} unit cubes. How many cubes have {ask} face(s) painted?", + str(ans), nearby_wrong(ans), + f"For {n}Ɨ{n}Ɨ{n} cube: {ask} faces painted = {ans}", 2)) + return questions + + +# ============ MATHEMATICAL REASONING ============ + +def gen_math_operations(conn, count=1000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Mathematical Reasoning", "Mathematical Operations", "Symbol substitution") + if not qtid: return questions + ops = {'+': lambda a, b: a + b, '-': lambda a, b: a - b, + 'Ɨ': lambda a, b: a * b, 'Ć·': lambda a, b: a // b} + symbols = ['@', '#', '$', '&', '*', '!'] + for _ in range(count): + op_pairs = random.sample(list(ops.keys()), 2) + sym_pairs = random.sample(symbols, 2) + a, b, c = random.randint(2, 20), random.randint(2, 20), random.randint(2, 20) + mapping_text = f"{sym_pairs[0]} means '{op_pairs[0]}' and {sym_pairs[1]} means '{op_pairs[1]}'" + expr_text = f"{a} {sym_pairs[0]} {b} {sym_pairs[1]} {c}" + result = ops[op_pairs[1]](ops[op_pairs[0]](a, b), c) + questions.append(make_question(qtid, + f"If {mapping_text}, find: {expr_text}", + str(result), nearby_wrong(result), + f"Replace symbols: {a} {op_pairs[0]} {b} {op_pairs[1]} {c} = {result}", 2)) + return questions + + +def gen_number_puzzles(conn, count=800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Mathematical Reasoning", "Number Puzzles", "Find missing number in grid") + if not qtid: return questions + for _ in range(count): + # 3x3 grid where rows/cols sum to same value + a, b = random.randint(1, 20), random.randint(1, 20) + c = a + b + d = random.randint(1, 20) + e = c - d + random.randint(1, 10) + missing = a + d - e + b # Some pattern + # Simpler: row sums are equal + r1 = [random.randint(1, 20) for _ in range(3)] + target = sum(r1) + r2_a, r2_b = random.randint(1, 15), random.randint(1, 15) + r2_c = target - r2_a - r2_b + if r2_c > 0: + questions.append(make_question(qtid, + f"In a grid, row 1 is [{r1[0]}, {r1[1]}, {r1[2]}] (sum={target}). Row 2 is [{r2_a}, {r2_b}, ?]. Find the missing number if row sums are equal.", + str(r2_c), nearby_wrong(r2_c), + f"? = {target} - {r2_a} - {r2_b} = {r2_c}", 1)) + return questions + + +def gen_venn_diagram(conn, count=800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Mathematical Reasoning", "Venn Diagram", "Count elements in region") + if not qtid: return questions + for _ in range(count): + total_a = random.randint(20, 100) + total_b = random.randint(20, 100) + both = random.randint(5, min(total_a, total_b)) + only_a = total_a - both + only_b = total_b - both + questions.append(make_question(qtid, + f"In a group, {total_a} like tea, {total_b} like coffee, and {both} like both. How many like only tea?", + str(only_a), nearby_wrong(only_a), + f"Only tea = {total_a} - {both} = {only_a}", 1)) + return questions + + +# ============ CRITICAL THINKING ============ + +def gen_statement_conclusion(conn, count=800): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Critical Thinking", "Statement and Conclusion", "Which conclusion follows") + if not qtid: return questions + templates = [ + ("All students who study hard pass the exam.", "Some who pass studied hard", ["No one studies hard", "Everyone fails", "Studying is not needed"]), + ("Regular exercise improves health.", "People who exercise are healthier", ["Exercise is harmful", "Health has no relation to exercise", "Only medicine improves health"]), + ("Reading improves vocabulary.", "People who read more have better vocabulary", ["Reading is useless", "Vocabulary cannot be improved", "TV improves vocabulary more"]), + ("Smoking causes cancer.", "Smokers are at higher risk of cancer", ["All smokers get cancer", "Cancer has no cause", "Smoking is healthy"]), + ("Water pollution affects marine life.", "Marine life is harmed by water pollution", ["Marine life thrives in pollution", "Pollution has no effect", "Only air pollution matters"]), + ] + for _ in range(count): + stmt, correct, wrongs = random.choice(templates) + questions.append(make_question(qtid, + f"Statement: {stmt}\nWhich conclusion logically follows?", + correct, wrongs, f"Direct logical inference", 2)) + return questions + + +def gen_letter_series(conn, count=1000): + questions = [] + qtid = get_qtid(conn, SUBJECT, "Verbal Reasoning", "Letter Series", "Find next letters") + if not qtid: return questions + for _ in range(count): + start = random.randint(0, 15) + skip = random.randint(1, 4) + series = [chr(65 + start + i * skip) for i in range(4) if start + i * skip < 26] + if len(series) < 4: + continue + nxt_idx = start + 4 * skip + if nxt_idx < 26: + ans = chr(65 + nxt_idx) + wrongs = [chr(65 + (nxt_idx + i) % 26) for i in [1, 2, -1]] + questions.append(make_question(qtid, + f"Find the next letter: {', '.join(series)}, ?", + ans, wrongs, + f"Skip {skip}: next = {ans}", 1)) + return questions + + +def generate_all(conn): + """Generate all Reasoning questions.""" + generators = [ + ("Number Analogy", gen_number_analogy, 700), + ("Letter Analogy", gen_letter_analogy, 500), + ("Classification", gen_classification, 2000), + ("Number Series", gen_number_series, 2000), + ("Letter Series", gen_letter_series, 1500), + ("Coding-Decoding", gen_coding_decoding, 2500), + ("Blood Relations", gen_blood_relations, 2000), + ("Direction & Distance", gen_direction, 1500), + ("Order & Ranking", gen_ranking, 1500), + ("Syllogism", gen_syllogism, 1500), + ("Mirror Image", gen_mirror_image, 1000), + ("Dice", gen_dice, 1000), + ("Cube Painting", gen_cube_painting, 800), + ("Math Operations", gen_math_operations, 1500), + ("Number Puzzles", gen_number_puzzles, 1200), + ("Venn Diagram", gen_venn_diagram, 1200), + ("Statement & Conclusion", gen_statement_conclusion, 1200), + ] + + total = 0 + all_questions = [] + for name, gen_func, count in generators: + questions = gen_func(conn, count) + all_questions.extend(questions) + print(f" {name}: {len(questions)} questions") + total += len(questions) + + batch_size = 5000 + for i in range(0, len(all_questions), batch_size): + insert_questions_batch(conn, all_questions[i:i+batch_size]) + + print(f" TOTAL Reasoning: {total}") + return total + + +if __name__ == '__main__': + conn = get_db() + print("Generating Reasoning questions...") + generate_all(conn) + conn.close() diff --git a/generators/run_all.py b/generators/run_all.py new file mode 100644 index 0000000..12662f4 --- /dev/null +++ b/generators/run_all.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +""" +Master script to generate all 100,000 SSC CGL questions. +Usage: python3 generators/run_all.py [--force] +""" +import sys +import os +import time + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +from db.init import init_db, get_db, get_stats + +def main(): + force = '--force' in sys.argv + start = time.time() + + print("=" * 60) + print("SSCTopper - Question Generation Pipeline") + print("=" * 60) + + # Step 1: Initialize DB + print("\n[1/5] Initializing database...") + init_db(force=force) + + conn = get_db() + + # Step 2: Quantitative Aptitude + print("\n[2/5] Generating Quantitative Aptitude questions...") + from generators.quant_generator import generate_all as gen_quant + gen_quant(conn) + + # Step 3: Reasoning + print("\n[3/5] Generating Reasoning questions...") + from generators.reasoning_generator import generate_all as gen_reason + gen_reason(conn) + + # Step 4: English + print("\n[4/5] Generating English Language questions...") + from generators.english_generator import generate_all as gen_eng + gen_eng(conn) + + # Step 5: General Awareness + print("\n[5/5] Generating General Awareness questions...") + from generators.gk_generator import generate_all as gen_gk + gen_gk(conn) + + # Final stats + stats = get_stats(conn) + elapsed = time.time() - start + + print("\n" + "=" * 60) + print("GENERATION COMPLETE") + print("=" * 60) + for subject, count in stats.items(): + print(f" {subject}: {count:,}") + print(f"\n Time taken: {elapsed:.1f} seconds") + print("=" * 60) + + conn.close() + + +if __name__ == '__main__': + main() diff --git a/server.py b/server.py new file mode 100644 index 0000000..d680755 --- /dev/null +++ b/server.py @@ -0,0 +1,492 @@ +#!/usr/bin/env python3 +""" +SSCTopper Web Application - Zero-dependency Python web server. +Serves the SSC CGL question bank with syllabus browser and practice interface. +""" +import http.server +import json +import sqlite3 +import os +import sys +import urllib.parse +import hashlib +import uuid +import http.cookies +import urllib.request +import signal + +sys.path.insert(0, os.path.join(os.path.dirname(__file__))) +from db.init import DB_PATH, init_db, get_db + +PORT = 8080 +ROOT = os.path.dirname(os.path.abspath(__file__)) +GOOGLE_CLIENT_ID = "273072123939-dd82h4o1rt3k7811sri6qgsig73b3916.apps.googleusercontent.com" + + +class SSCHandler(http.server.BaseHTTPRequestHandler): + """HTTP request handler for SSCTopper.""" + + sessions = {} # session_id -> user_id + + def get_user_id(self): + cookie_header = self.headers.get('Cookie') + if not cookie_header: + return None + cookie = http.cookies.SimpleCookie(cookie_header) + session_id = cookie.get('session_id') + if not session_id: + return None + return self.sessions.get(session_id.value) + + def do_POST(self): + parsed = urllib.parse.urlparse(self.path) + path = parsed.path + + content_length = int(self.headers.get('Content-Length', 0)) + post_data = self.rfile.read(content_length).decode('utf-8') + try: + data = json.loads(post_data) + except: + data = {} + + if path == '/api/auth/signup': + self.handle_signup(data) + elif path == '/api/auth/login': + self.handle_login(data) + elif path == '/api/auth/google': + self.handle_google_login(data) + elif path == '/api/user/progress': + self.handle_progress(data) + else: + self.send_error(404) + + def handle_google_login(self, data): + id_token = data.get('id_token') + if not id_token: + return self.json_response({'error': 'Missing ID token'}, 400) + + # Verify token with Google API (Zero-dependency way) + try: + url = f"https://oauth2.googleapis.com/tokeninfo?id_token={id_token}" + with urllib.request.urlopen(url) as response: + google_data = json.loads(response.read().decode()) + + # Check for error in Google response + if 'error_description' in google_data: + return self.json_response({'error': google_data['error_description']}, 401) + + # Security check: Verify audience (aud) matches our Client ID + aud = google_data.get('aud') + if aud != GOOGLE_CLIENT_ID: + return self.json_response({'error': 'Token was not issued for this application'}, 401) + + email = google_data.get('email') + name = google_data.get('name', email.split('@')[0]) + + if not email: + return self.json_response({'error': 'Email not provided by Google'}, 400) + + conn = get_db() + user = conn.execute("SELECT id, username FROM users WHERE email=?", (email,)).fetchone() + + if not user: + # Create new user with random password (cannot be guessed) + random_pass = str(uuid.uuid4()) + pass_hash = hashlib.sha256(random_pass.encode()).hexdigest() + try: + # Use email handle as username if possible, otherwise use full email + username = email.split('@')[0] + # Ensure username uniqueness (this is simple, could be better) + cursor = conn.execute("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)", + (username, email, pass_hash)) + user_id = cursor.lastrowid + conn.commit() + username_final = username + except sqlite3.IntegrityError: + # Fallback to email as username + cursor = conn.execute("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)", + (email, email, pass_hash)) + user_id = cursor.lastrowid + conn.commit() + username_final = email + else: + user_id = user[0] + username_final = user[1] + + conn.close() + + # Set session + session_id = str(uuid.uuid4()) + self.sessions[session_id] = user_id + + cookie = http.cookies.SimpleCookie() + cookie['session_id'] = session_id + cookie['session_id']['path'] = '/' + cookie['session_id']['httponly'] = True + + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Set-Cookie', cookie.output(header='')) + self.end_headers() + self.wfile.write(json.dumps({'success': True, 'username': username_final}).encode()) + + except Exception as e: + print(f"Google Auth Error: {e}") + self.json_response({'error': 'Failed to verify Google account'}, 500) + + def handle_signup(self, data): + username = data.get('username') + email = data.get('email') + password = data.get('password') + + if not all([username, email, password]): + return self.json_response({'error': 'Missing fields'}, 400) + + password_hash = hashlib.sha256(password.encode()).hexdigest() + + conn = get_db() + try: + conn.execute("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)", + (username, email, password_hash)) + conn.commit() + self.json_response({'success': True}) + except sqlite3.IntegrityError: + self.json_response({'error': 'Username or email already exists'}, 400) + finally: + conn.close() + + def handle_login(self, data): + username = data.get('username') + password = data.get('password') + + if not all([username, password]): + return self.json_response({'error': 'Missing fields'}, 400) + + password_hash = hashlib.sha256(password.encode()).hexdigest() + + conn = get_db() + user = conn.execute("SELECT id FROM users WHERE username=? AND password_hash=?", + (username, password_hash)).fetchone() + conn.close() + + if user: + session_id = str(uuid.uuid4()) + self.sessions[session_id] = user[0] + + cookie = http.cookies.SimpleCookie() + cookie['session_id'] = session_id + cookie['session_id']['path'] = '/' + cookie['session_id']['httponly'] = True + + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Set-Cookie', cookie.output(header='')) + self.end_headers() + self.wfile.write(json.dumps({'success': True, 'username': username}).encode()) + else: + self.json_response({'error': 'Invalid credentials'}, 401) + + def handle_progress(self, data): + user_id = self.get_user_id() + if not user_id: + return self.json_response({'error': 'Unauthorized'}, 401) + + question_id = data.get('question_id') + is_correct = data.get('is_correct') + time_taken = data.get('time_taken', 0.0) + + if question_id is None or is_correct is None: + return self.json_response({'error': 'Missing fields'}, 400) + + conn = get_db() + conn.execute("INSERT INTO user_answers (user_id, question_id, is_correct, time_taken) VALUES (?, ?, ?, ?)", + (user_id, int(question_id), bool(is_correct), float(time_taken))) + conn.commit() + conn.close() + self.json_response({'success': True}) + + def do_GET(self): + parsed = urllib.parse.urlparse(self.path) + path = parsed.path + params = urllib.parse.parse_qs(parsed.query) + + if path == '/' or path == '/index.html': + self.serve_html() + elif path == '/robots.txt': + self.serve_static('robots.txt', 'text/plain') + elif path == '/sitemap.xml': + self.serve_static('sitemap.xml', 'application/xml') + elif path == '/api/syllabus': + self.api_syllabus() + elif path == '/api/questions': + self.api_questions(params) + elif path == '/api/stats': + self.api_stats() + elif path == '/api/mock-test': + self.api_mock_test(params) + elif path == '/api/user/profile': + self.api_user_profile() + else: + self.send_error(404) + + def api_user_profile(self): + user_id = self.get_user_id() + if not user_id: + return self.json_response({'error': 'Unauthorized'}, 401) + + # Parse timeframe + parsed = urllib.parse.urlparse(self.path) + params = urllib.parse.parse_qs(parsed.query) + timeframe = params.get('timeframe', ['overall'])[0] + + time_filter = "" + if timeframe == 'daily': + time_filter = "AND ua.answered_at >= datetime('now', '-1 day')" + elif timeframe == 'weekly': + time_filter = "AND ua.answered_at >= datetime('now', '-7 days')" + elif timeframe == 'monthly': + time_filter = "AND ua.answered_at >= datetime('now', 'start of month')" + + conn = get_db() + user = conn.execute("SELECT username, email, created_at FROM users WHERE id=?", (user_id,)).fetchone() + + # Get overall stats for timeframe + stats_query = f"SELECT COUNT(*), SUM(CASE WHEN is_correct=1 THEN 1 ELSE 0 END), AVG(time_taken) FROM user_answers ua WHERE user_id=? {time_filter}" + stats_row = conn.execute(stats_query, (user_id,)).fetchone() + total_attempts = stats_row[0] or 0 + correct_attempts = stats_row[1] or 0 + avg_time_overall = round(stats_row[2] or 0, 1) + + # Topic-wise progress with time tracking + topic_progress = [] + rows = conn.execute(f""" + SELECT t.id, t.name, st.name as subtopic, s.name as subject, + COUNT(DISTINCT q.id) as total_questions, + COUNT(DISTINCT ua.question_id) as answered_questions, + AVG(ua.time_taken) as avg_time + FROM topics t + JOIN subtopics st ON t.subtopic_id = st.id + JOIN subjects s ON st.subject_id = s.id + LEFT JOIN question_types qt ON qt.topic_id = t.id + LEFT JOIN questions q ON q.question_type_id = qt.id + LEFT JOIN user_answers ua ON ua.question_id = q.id AND ua.user_id = ? AND ua.is_correct = 1 {time_filter} + GROUP BY t.id + """, (user_id,)).fetchall() + + for r in rows: + topic_progress.append({ + 'topic_id': r[0], 'topic': r[1], 'subtopic': r[2], 'subject': r[3], + 'total': r[4], 'answered': r[5], + 'percent': round(r[5] * 100 / r[4], 1) if r[4] > 0 else 0, + 'avg_time': round(r[6] or 0, 1) + }) + + conn.close() + self.json_response({ + 'username': user[0], + 'email': user[1], + 'joined': user[2], + 'stats': { + 'total_attempts': total_attempts, + 'correct_attempts': correct_attempts, + 'accuracy': round(correct_attempts * 100 / total_attempts, 1) if total_attempts > 0 else 0, + 'avg_time': avg_time_overall + }, + 'topic_progress': topic_progress + }) + + def serve_html(self): + html_path = os.path.join(ROOT, 'static', 'index.html') + with open(html_path, 'r') as f: + content = f.read() + self.send_response(200) + self.send_header('Content-Type', 'text/html; charset=utf-8') + self.end_headers() + self.wfile.write(content.encode('utf-8')) + + def serve_static(self, filename, content_type): + file_path = os.path.join(ROOT, 'static', filename) + if not os.path.exists(file_path): + self.send_error(404) + return + with open(file_path, 'rb') as f: + content = f.read() + self.send_response(200) + self.send_header('Content-Type', content_type) + self.end_headers() + self.wfile.write(content) + + def json_response(self, data, status=200): + self.send_response(status) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8')) + + def api_syllabus(self): + conn = get_db() + subjects = [] + for s in conn.execute("SELECT id, name, tier, description, target_questions FROM subjects").fetchall(): + subject = {'id': s[0], 'name': s[1], 'tier': s[2], 'description': s[3], 'target': s[4], 'subtopics': []} + for st in conn.execute("SELECT id, name, description FROM subtopics WHERE subject_id=?", (s[0],)).fetchall(): + subtopic = {'id': st[0], 'name': st[1], 'description': st[2], 'topics': []} + for t in conn.execute("SELECT id, name, description FROM topics WHERE subtopic_id=?", (st[0],)).fetchall(): + q_count = conn.execute("SELECT COUNT(*) FROM questions q JOIN question_types qt ON q.question_type_id=qt.id WHERE qt.topic_id=?", (t[0],)).fetchone()[0] + qtypes = [{'id': qt[0], 'name': qt[1]} for qt in conn.execute("SELECT id, name FROM question_types WHERE topic_id=?", (t[0],)).fetchall()] + subtopic['topics'].append({'id': t[0], 'name': t[1], 'description': t[2], 'question_count': q_count, 'question_types': qtypes}) + subject['subtopics'].append(subtopic) + # Count total questions for subject + subject['question_count'] = conn.execute(""" + SELECT COUNT(*) FROM questions q + JOIN question_types qt ON q.question_type_id = qt.id + JOIN topics t ON qt.topic_id = t.id + JOIN subtopics st ON t.subtopic_id = st.id + WHERE st.subject_id = ? + """, (s[0],)).fetchone()[0] + subjects.append(subject) + conn.close() + self.json_response(subjects) + + def api_questions(self, params): + conn = get_db() + topic_id = params.get('topic_id', [None])[0] + qtype_id = params.get('qtype_id', [None])[0] + subject_id = params.get('subject_id', [None])[0] + difficulty = params.get('difficulty', [None])[0] + limit = int(params.get('limit', ['20'])[0]) + offset = int(params.get('offset', ['0'])[0]) + + query = """SELECT q.id, q.question_text, q.option_a, q.option_b, q.option_c, q.option_d, + q.correct_option, q.explanation, q.difficulty, + qt.name as qtype_name, t.name as topic_name, st.name as subtopic_name, s.name as subject_name + FROM questions q + JOIN question_types qt ON q.question_type_id = qt.id + JOIN topics t ON qt.topic_id = t.id + JOIN subtopics st ON t.subtopic_id = st.id + JOIN subjects s ON st.subject_id = s.id + WHERE 1=1""" + args = [] + + if topic_id: + query += " AND t.id = ?" + args.append(int(topic_id)) + if qtype_id: + query += " AND qt.id = ?" + args.append(int(qtype_id)) + if subject_id: + query += " AND s.id = ?" + args.append(int(subject_id)) + if difficulty: + query += " AND q.difficulty = ?" + args.append(int(difficulty)) + + # Get total count + count_query = query.replace("SELECT q.id, q.question_text, q.option_a, q.option_b, q.option_c, q.option_d,\n q.correct_option, q.explanation, q.difficulty,\n qt.name as qtype_name, t.name as topic_name, st.name as subtopic_name, s.name as subject_name", "SELECT COUNT(*)") + total = conn.execute(count_query, args).fetchone()[0] + + query += " ORDER BY RANDOM() LIMIT ? OFFSET ?" + args.extend([limit, offset]) + + rows = conn.execute(query, args).fetchall() + questions = [] + for r in rows: + questions.append({ + 'id': r[0], 'question_text': r[1], + 'options': {'A': r[2], 'B': r[3], 'C': r[4], 'D': r[5]}, + 'correct_option': r[6], 'explanation': r[7], 'difficulty': r[8], + 'qtype': r[9], 'topic': r[10], 'subtopic': r[11], 'subject': r[12] + }) + conn.close() + self.json_response({'total': total, 'questions': questions}) + + def api_stats(self): + conn = get_db() + stats = {} + rows = conn.execute(""" + SELECT s.name, COUNT(q.id) FROM subjects s + LEFT JOIN subtopics st ON st.subject_id = s.id + LEFT JOIN topics t ON t.subtopic_id = st.id + LEFT JOIN question_types qt ON qt.topic_id = t.id + LEFT JOIN questions q ON q.question_type_id = qt.id + GROUP BY s.id + """).fetchall() + for r in rows: + stats[r[0]] = r[1] + total = conn.execute("SELECT COUNT(*) FROM questions").fetchone()[0] + stats['total'] = total + topic_count = conn.execute("SELECT COUNT(*) FROM topics").fetchone()[0] + stats['topic_count'] = topic_count + stats['subject_count'] = conn.execute("SELECT COUNT(*) FROM subjects").fetchone()[0] + conn.close() + self.json_response(stats) + + def api_mock_test(self, params): + conn = get_db() + subject_id = params.get('subject_id', [None])[0] + num = int(params.get('num', ['25'])[0]) + + query = """SELECT q.id, q.question_text, q.option_a, q.option_b, q.option_c, q.option_d, + q.correct_option, q.explanation, q.difficulty, + qt.name, t.name, st.name, s.name + FROM questions q + JOIN question_types qt ON q.question_type_id = qt.id + JOIN topics t ON qt.topic_id = t.id + JOIN subtopics st ON t.subtopic_id = st.id + JOIN subjects s ON st.subject_id = s.id""" + args = [] + if subject_id: + query += " WHERE s.id = ?" + args.append(int(subject_id)) + query += " ORDER BY RANDOM() LIMIT ?" + args.append(num) + + rows = conn.execute(query, args).fetchall() + questions = [] + for r in rows: + questions.append({ + 'id': r[0], 'question_text': r[1], + 'options': {'A': r[2], 'B': r[3], 'C': r[4], 'D': r[5]}, + 'correct_option': r[6], 'explanation': r[7], 'difficulty': r[8], + 'qtype': r[9], 'topic': r[10], 'subtopic': r[11], 'subject': r[12] + }) + conn.close() + self.json_response({'questions': questions, 'total': len(questions)}) + + def log_message(self, format, *args): + pass # Suppress access logs + + +def main(): + # Initialize DB if needed + if not os.path.exists(DB_PATH): + print("Database not found. Running generation pipeline...") + init_db() + + # Check if we have questions, if not, generate some + conn = get_db() + try: + count = conn.execute("SELECT COUNT(*) FROM questions").fetchone()[0] + if count == 0: + print("Database is empty. Generating question bank...") + from generators.run_all import main as generate + generate() + except Exception as e: + print(f"Error checking question count: {e}") + finally: + conn.close() + + server = http.server.HTTPServer(('0.0.0.0', PORT), SSCHandler) + print(f"\nšŸš€ SSCTopper running at http://localhost:{PORT}") + print(f" Database: {DB_PATH}") + print(f" Press Ctrl+C to stop\n") + + signal.signal(signal.SIGINT, lambda s, f: (server.shutdown(), sys.exit(0))) + + try: + server.serve_forever() + except KeyboardInterrupt: + server.shutdown() + + +if __name__ == '__main__': + main() diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..5edc483 --- /dev/null +++ b/static/index.html @@ -0,0 +1,1036 @@ + + + + + + + SSCTopper | 100,000+ SSC CGL Practice Questions & Mock Tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
+

Master the SSC CGL

+

Access 100,000+ template-generated questions across Quantitative Aptitude, Reasoning, English, and + General Awareness.

+ +
+ +
+ +
+
+ + +
+
← Back to Home
+

Full Syllabus Browser

+

Explore topics and start practicing specifically for + each type.

+
+ +
+
+ + +
+
← Exit Practice
+
+ +
+ +
+
+ +
+
+
+ + +
+
← Back to Home
+
+ +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..0f88040 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://ssctopper.com/sitemap.xml diff --git a/static/sitemap.xml b/static/sitemap.xml new file mode 100644 index 0000000..efef55c --- /dev/null +++ b/static/sitemap.xml @@ -0,0 +1,9 @@ + + + + https://ssctopper.com/ + 2026-03-29 + daily + 1.0 + +