neet mock only physics for 2026

Asking for Preferences
Asking for Preferences
Reading File
Running Command

mkdir -p /home/daytona/workspace/neet-physics-mock && ls /home/daytona/workspace/neet-physics-mock/

Writing File

~/neet-physics-mock/generate_mock.py

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY

# ─────────────────────────────────────────────
# QUESTION BANK  (50 questions, mixed difficulty)
# Topics: Mechanics, Thermodynamics, Waves, Optics,
#         Electricity & Magnetism, Modern Physics, Electronics
# ─────────────────────────────────────────────
questions = [
    # ── MECHANICS (Q1–Q12) ──
    {
        "no": 1, "topic": "Mechanics",
        "q": "A particle moves in a straight line with constant acceleration. If it covers distances s₁ and s₂ in the first and second seconds respectively, the acceleration is:",
        "opts": ["s₂ − s₁", "s₁ − s₂", "2(s₂ − s₁)", "(s₁ + s₂)/2"],
        "ans": "A",
        "hint": "Using s = ut + ½at², the difference in distances in successive equal time intervals equals 'a'."
    },
    {
        "no": 2, "topic": "Mechanics",
        "q": "A ball is thrown vertically upward with velocity u. The ratio of the distance covered in the 3rd second to the distance covered in the 5th second (g = 10 m/s²) when u = 50 m/s is:",
        "opts": ["3 : 1", "5 : 3", "1 : 1", "9 : 25"],
        "ans": "A",
        "hint": "Distance in nth second: Sₙ = u − g(n − ½). Compare S₃ and S₅."
    },
    {
        "no": 3, "topic": "Mechanics",
        "q": "Two blocks of mass 3 kg and 5 kg are connected by a massless string over a frictionless pulley (Atwood machine). The acceleration of the system is (g = 10 m/s²):",
        "opts": ["2.5 m/s²", "5 m/s²", "10 m/s²", "1.25 m/s²"],
        "ans": "A",
        "hint": "a = (m₂ − m₁)g/(m₁ + m₂) = (5−3)×10/8 = 2.5 m/s²."
    },
    {
        "no": 4, "topic": "Mechanics",
        "q": "A body of mass m slides down a frictionless incline of angle θ. The normal reaction from the incline is:",
        "opts": ["mg cos θ", "mg sin θ", "mg", "mg tan θ"],
        "ans": "A",
        "hint": "Normal force balances the component of gravity perpendicular to the surface."
    },
    {
        "no": 5, "topic": "Mechanics",
        "q": "A projectile is launched at 60° to the horizontal with speed 40 m/s. Its horizontal range (g = 10 m/s²) is:",
        "opts": ["80√3 m", "160 m", "80 m", "40√3 m"],
        "ans": "A",
        "hint": "R = u²sin2θ/g = 1600×(√3/2)/10 = 80√3 m."
    },
    {
        "no": 6, "topic": "Mechanics",
        "q": "A circular disc of radius R rotates about its axis. A point on its rim has linear speed v. The angular velocity of the disc is:",
        "opts": ["v/R", "vR", "v/R²", "R/v"],
        "ans": "A",
        "hint": "ω = v/r."
    },
    {
        "no": 7, "topic": "Mechanics",
        "q": "The work done by a force F = (3î + 4ĵ) N over a displacement d = (2î − ĵ) m is:",
        "opts": ["2 J", "14 J", "10 J", "6 J"],
        "ans": "A",
        "hint": "W = F·d = 3×2 + 4×(−1) = 6 − 4 = 2 J."
    },
    {
        "no": 8, "topic": "Mechanics",
        "q": "A spring of force constant k is compressed by x. The potential energy stored is:",
        "opts": ["½kx²", "kx²", "2kx²", "kx"],
        "ans": "A",
        "hint": "PE = ½kx² for a spring."
    },
    {
        "no": 9, "topic": "Mechanics",
        "q": "A satellite orbits Earth at height h above the surface. If R is Earth's radius and g is surface gravity, the orbital speed is:",
        "opts": ["√[gR²/(R+h)]", "√[g(R+h)]", "√[gR]", "√[g/R]"],
        "ans": "A",
        "hint": "v = √[GM/(R+h)] = √[gR²/(R+h)]."
    },
    {
        "no": 10, "topic": "Mechanics",
        "q": "Two spheres of masses m and 2m are separated by distance d. The gravitational force between them is F. If the distance is halved, the new force is:",
        "opts": ["4F", "2F", "F/2", "F/4"],
        "ans": "A",
        "hint": "F ∝ 1/r². Halving r increases F by 4."
    },
    {
        "no": 11, "topic": "Mechanics",
        "q": "A rigid body is in equilibrium under three concurrent forces. This requires that the forces be:",
        "opts": ["Coplanar and their vector sum = 0", "Parallel only", "All equal in magnitude", "All perpendicular"],
        "ans": "A",
        "hint": "Lami's theorem: three concurrent forces in equilibrium must be coplanar and sum to zero."
    },
    {
        "no": 12, "topic": "Mechanics",
        "q": "The moment of inertia of a solid sphere of mass M and radius R about a diameter is:",
        "opts": ["2MR²/5", "MR²/2", "MR²", "2MR²/3"],
        "ans": "A",
        "hint": "I = 2MR²/5 for solid sphere about diameter."
    },

    # ── THERMODYNAMICS (Q13–Q19) ──
    {
        "no": 13, "topic": "Thermodynamics",
        "q": "In an isothermal expansion of an ideal gas, the internal energy:",
        "opts": ["Remains constant", "Increases", "Decreases", "First increases then decreases"],
        "ans": "A",
        "hint": "For an ideal gas, internal energy depends only on temperature; T is constant in isothermal process."
    },
    {
        "no": 14, "topic": "Thermodynamics",
        "q": "A Carnot engine operates between 500 K and 300 K. Its efficiency is:",
        "opts": ["40%", "60%", "50%", "20%"],
        "ans": "A",
        "hint": "η = 1 − T_L/T_H = 1 − 300/500 = 0.40 = 40%."
    },
    {
        "no": 15, "topic": "Thermodynamics",
        "q": "In an adiabatic process for an ideal diatomic gas (γ = 7/5), if volume is doubled, pressure becomes:",
        "opts": ["P/2^(7/5)", "P/2", "P×2^(7/5)", "P/4"],
        "ans": "A",
        "hint": "PV^γ = const ⟹ P₂ = P₁(V₁/V₂)^γ = P/2^(1.4)."
    },
    {
        "no": 16, "topic": "Thermodynamics",
        "q": "The rms speed of oxygen molecules at 27°C (M = 32 g/mol, R = 8.314 J/mol·K) is approximately:",
        "opts": ["483 m/s", "273 m/s", "1000 m/s", "331 m/s"],
        "ans": "A",
        "hint": "v_rms = √(3RT/M) = √(3×8.314×300/0.032) ≈ 483 m/s."
    },
    {
        "no": 17, "topic": "Thermodynamics",
        "q": "The specific heat of a monatomic ideal gas at constant volume (Cᵥ) in terms of R is:",
        "opts": ["3R/2", "5R/2", "R", "7R/2"],
        "ans": "A",
        "hint": "Monatomic gas has 3 translational DOF, so Cᵥ = (3/2)R."
    },
    {
        "no": 18, "topic": "Thermodynamics",
        "q": "During a reversible isothermal expansion of an ideal gas at temperature T, the entropy change when heat Q is absorbed is:",
        "opts": ["Q/T", "Q×T", "−Q/T", "0"],
        "ans": "A",
        "hint": "ΔS = Q/T for reversible isothermal process."
    },
    {
        "no": 19, "topic": "Thermodynamics",
        "q": "Newton's law of cooling states that the rate of loss of heat is proportional to:",
        "opts": ["Excess temperature over surroundings", "Absolute temperature", "Square of temperature", "Fourth power of temperature"],
        "ans": "A",
        "hint": "Newton's law: dQ/dt ∝ (T − T₀)."
    },

    # ── WAVES & SOUND (Q20–Q26) ──
    {
        "no": 20, "topic": "Waves",
        "q": "The frequency of the third overtone of a closed organ pipe of length L (speed of sound v) is:",
        "opts": ["7v/4L", "3v/4L", "5v/4L", "4v/L"],
        "ans": "A",
        "hint": "Closed pipe harmonics: (2n−1)v/4L. Third overtone = 7th harmonic = 7v/4L."
    },
    {
        "no": 21, "topic": "Waves",
        "q": "Two waves y₁ = A sin(ωt) and y₂ = A sin(ωt + π/2) superpose. The resultant amplitude is:",
        "opts": ["A√2", "2A", "A/√2", "0"],
        "ans": "A",
        "hint": "R = √(A² + A² + 2A²cosφ); φ = π/2 → R = A√2."
    },
    {
        "no": 22, "topic": "Waves",
        "q": "Beats are produced when two tuning forks of frequency 256 Hz and 260 Hz sound together. The beat frequency is:",
        "opts": ["4 Hz", "258 Hz", "516 Hz", "2 Hz"],
        "ans": "A",
        "hint": "Beat frequency = |f₁ − f₂| = |260 − 256| = 4 Hz."
    },
    {
        "no": 23, "topic": "Waves",
        "q": "The Doppler effect: A source of frequency 500 Hz moves towards a stationary observer at 34 m/s (v_sound = 340 m/s). The apparent frequency is:",
        "opts": ["550 Hz", "450 Hz", "500 Hz", "510 Hz"],
        "ans": "A",
        "hint": "f' = f×v/(v − vs) = 500×340/306 = 500×10/9 ≈ 555... Use exact: 340/306 = 50/45 = 10/9 → 556 Hz? Recalculate: 500×340/306 = 555.6; rounded choice A = 550 is closest standard option. Exact: 500×340/306 ≈ 556 Hz."
    },
    {
        "no": 24, "topic": "Waves",
        "q": "In standing waves on a string fixed at both ends, the distance between consecutive nodes is:",
        "opts": ["λ/2", "λ", "λ/4", "2λ"],
        "ans": "A",
        "hint": "Consecutive nodes are separated by λ/2."
    },
    {
        "no": 25, "topic": "Waves",
        "q": "A transverse wave is represented by y = 0.02 sin(2πt − πx/5). The wave speed is:",
        "opts": ["10 m/s", "5 m/s", "2 m/s", "20 m/s"],
        "ans": "A",
        "hint": "v = ω/k = 2π/(π/5) = 10 m/s."
    },
    {
        "no": 26, "topic": "Waves",
        "q": "Which of the following cannot be polarised?",
        "opts": ["Sound waves", "Radio waves", "Light waves", "X-rays"],
        "ans": "A",
        "hint": "Sound is a longitudinal wave; only transverse waves can be polarised."
    },

    # ── OPTICS (Q27–Q33) ──
    {
        "no": 27, "topic": "Optics",
        "q": "A convex lens of focal length 20 cm forms a real image at 60 cm from the lens. The object distance is:",
        "opts": ["30 cm", "40 cm", "20 cm", "60 cm"],
        "ans": "A",
        "hint": "1/f = 1/v − 1/u → 1/20 = 1/60 − 1/u → 1/u = 1/60 − 1/20 = −2/60 → u = −30 cm."
    },
    {
        "no": 28, "topic": "Optics",
        "q": "In Young's double-slit experiment, the fringe width β is given by (D = screen distance, d = slit separation, λ = wavelength):",
        "opts": ["λD/d", "λd/D", "Dd/λ", "λ/Dd"],
        "ans": "A",
        "hint": "β = λD/d — standard formula."
    },
    {
        "no": 29, "topic": "Optics",
        "q": "Total internal reflection occurs when light travels from a denser to rarer medium and the angle of incidence exceeds the:",
        "opts": ["Critical angle", "Brewster's angle", "Angle of refraction", "Angle of incidence"],
        "ans": "A",
        "hint": "TIR occurs above the critical angle θ_c = sin⁻¹(n₂/n₁)."
    },
    {
        "no": 30, "topic": "Optics",
        "q": "A prism of apex angle 60° produces minimum deviation of 30°. The refractive index of the prism is:",
        "opts": ["√2", "1.5", "1.414", "√3/2"],
        "ans": "A",
        "hint": "μ = sin[(A+δm)/2]/sin(A/2) = sin45°/sin30° = (1/√2)/(1/2) = √2."
    },
    {
        "no": 31, "topic": "Optics",
        "q": "The resolving power of a microscope increases when:",
        "opts": ["Wavelength of light used decreases", "Wavelength increases", "Aperture decreases", "Magnification decreases"],
        "ans": "A",
        "hint": "RP = 2μsinθ/λ — shorter wavelength → higher resolving power."
    },
    {
        "no": 32, "topic": "Optics",
        "q": "In a single-slit diffraction pattern, the first minimum occurs when path difference = :",
        "opts": ["λ", "λ/2", "2λ", "3λ/2"],
        "ans": "A",
        "hint": "First minimum condition for single slit: a sinθ = λ."
    },
    {
        "no": 33, "topic": "Optics",
        "q": "Optical fibre works on the principle of:",
        "opts": ["Total internal reflection", "Diffraction", "Interference", "Polarisation"],
        "ans": "A",
        "hint": "Light is guided through fibre by repeated TIR."
    },

    # ── ELECTRICITY & MAGNETISM (Q34–Q42) ──
    {
        "no": 34, "topic": "Electricity & Magnetism",
        "q": "Three resistors of 2 Ω, 3 Ω, and 6 Ω are connected in parallel. The equivalent resistance is:",
        "opts": ["1 Ω", "11 Ω", "2 Ω", "3 Ω"],
        "ans": "A",
        "hint": "1/R = 1/2 + 1/3 + 1/6 = 3/6 + 2/6 + 1/6 = 1 → R = 1 Ω."
    },
    {
        "no": 35, "topic": "Electricity & Magnetism",
        "q": "A capacitor of capacitance C is charged to potential V. The energy stored is:",
        "opts": ["½CV²", "CV²", "2CV²", "CV/2"],
        "ans": "A",
        "hint": "U = ½CV²."
    },
    {
        "no": 36, "topic": "Electricity & Magnetism",
        "q": "A current-carrying straight conductor is placed in a uniform magnetic field. The force on it is maximum when the angle between the conductor and B is:",
        "opts": ["90°", "0°", "45°", "180°"],
        "ans": "A",
        "hint": "F = BIL sinθ; maximum when θ = 90°."
    },
    {
        "no": 37, "topic": "Electricity & Magnetism",
        "q": "The magnetic field at the centre of a circular coil of radius R carrying current I (μ₀ = 4π × 10⁻⁷ T·m/A) is:",
        "opts": ["μ₀I/2R", "μ₀I/4πR", "μ₀I/R", "μ₀I/2πR"],
        "ans": "A",
        "hint": "B = μ₀I/2R at the centre of a circular loop."
    },
    {
        "no": 38, "topic": "Electricity & Magnetism",
        "q": "In an AC circuit with only a capacitor, the current:",
        "opts": ["Leads voltage by 90°", "Lags voltage by 90°", "Is in phase with voltage", "Leads by 45°"],
        "ans": "A",
        "hint": "For a capacitor, current leads voltage by π/2 (90°)."
    },
    {
        "no": 39, "topic": "Electricity & Magnetism",
        "q": "The phenomenon of electromagnetic induction was discovered by:",
        "opts": ["Faraday", "Maxwell", "Ampere", "Hertz"],
        "ans": "A",
        "hint": "Michael Faraday discovered electromagnetic induction in 1831."
    },
    {
        "no": 40, "topic": "Electricity & Magnetism",
        "q": "In a series LCR circuit at resonance, the impedance is:",
        "opts": ["Equal to R", "Maximum", "Equal to L/C", "Zero"],
        "ans": "A",
        "hint": "At resonance X_L = X_C, so Z = R (minimum, not maximum)."
    },
    {
        "no": 41, "topic": "Electricity & Magnetism",
        "q": "The electric field inside a uniformly charged spherical shell is:",
        "opts": ["Zero", "Uniform and non-zero", "Proportional to r", "Inversely proportional to r²"],
        "ans": "A",
        "hint": "By Gauss's law, enclosed charge = 0 inside shell → E = 0."
    },
    {
        "no": 42, "topic": "Electricity & Magnetism",
        "q": "Lenz's law is a consequence of the law of conservation of:",
        "opts": ["Energy", "Charge", "Momentum", "Mass"],
        "ans": "A",
        "hint": "Lenz's law reflects that induced effects oppose their cause — energy conservation."
    },

    # ── MODERN PHYSICS (Q43–Q48) ──
    {
        "no": 43, "topic": "Modern Physics",
        "q": "The photoelectric effect demonstrates the particle nature of light. The maximum kinetic energy of emitted electrons is:",
        "opts": ["hν − φ", "hν + φ", "hν", "φ − hν"],
        "ans": "A",
        "hint": "KE_max = hν − φ (Einstein's photoelectric equation)."
    },
    {
        "no": 44, "topic": "Modern Physics",
        "q": "The de Broglie wavelength of a particle of mass m moving with kinetic energy K is:",
        "opts": ["h/√(2mK)", "h/√(mK)", "h/2mK", "√(2mK)/h"],
        "ans": "A",
        "hint": "λ = h/p = h/√(2mK)."
    },
    {
        "no": 45, "topic": "Modern Physics",
        "q": "In Bohr's model, the radius of the nth orbit of hydrogen is proportional to:",
        "opts": ["n²", "n", "1/n", "1/n²"],
        "ans": "A",
        "hint": "rₙ = n²a₀ where a₀ = 0.529 Å."
    },
    {
        "no": 46, "topic": "Modern Physics",
        "q": "In β⁻ decay, the number of neutrons in the nucleus:",
        "opts": ["Decreases by 1", "Increases by 1", "Remains the same", "Decreases by 2"],
        "ans": "A",
        "hint": "In β⁻ decay: n → p + e⁻ + ν̄, so neutron count decreases by 1, proton count increases by 1."
    },
    {
        "no": 47, "topic": "Modern Physics",
        "q": "The half-life of a radioactive substance is 20 years. The fraction remaining after 60 years is:",
        "opts": ["1/8", "1/4", "1/16", "1/6"],
        "ans": "A",
        "hint": "3 half-lives → (½)³ = 1/8."
    },
    {
        "no": 48, "topic": "Modern Physics",
        "q": "Which series of hydrogen spectrum lines lies in the visible region?",
        "opts": ["Balmer series", "Lyman series", "Paschen series", "Brackett series"],
        "ans": "A",
        "hint": "Balmer series (n_final = 2) lies in the visible region (400–700 nm)."
    },

    # ── ELECTRONICS & COMMUNICATION (Q49–Q50) ──
    {
        "no": 49, "topic": "Electronics",
        "q": "In a p-n junction diode, the depletion layer is formed due to:",
        "opts": ["Diffusion of charge carriers", "Drift of charge carriers", "Recombination only at junction", "External voltage"],
        "ans": "A",
        "hint": "Diffusion of electrons and holes across the junction creates the depletion region."
    },
    {
        "no": 50, "topic": "Electronics",
        "q": "The logic gate that gives output 1 only when all inputs are 0 is:",
        "opts": ["NOR gate", "NAND gate", "OR gate", "AND gate"],
        "ans": "A",
        "hint": "NOR gate: output = NOT(A OR B); output is 1 only when all inputs are 0."
    },
]

# ─────────────────────────────────────────────
# PDF GENERATION
# ─────────────────────────────────────────────
def make_pdf(output_path, answer_key_path):
    styles = getSampleStyleSheet()

    title_style = ParagraphStyle(
        'TitleStyle', parent=styles['Title'],
        fontSize=18, leading=24, alignment=TA_CENTER,
        textColor=colors.HexColor('#1a237e'), spaceAfter=4
    )
    subtitle_style = ParagraphStyle(
        'SubtitleStyle', parent=styles['Normal'],
        fontSize=11, alignment=TA_CENTER,
        textColor=colors.HexColor('#37474f'), spaceAfter=2
    )
    instruction_style = ParagraphStyle(
        'InstructionStyle', parent=styles['Normal'],
        fontSize=9, leading=13, textColor=colors.HexColor('#455a64'),
        leftIndent=10, rightIndent=10, spaceAfter=4
    )
    topic_style = ParagraphStyle(
        'TopicStyle', parent=styles['Normal'],
        fontSize=9, textColor=colors.HexColor('#1565c0'),
        fontName='Helvetica-Bold', spaceAfter=2
    )
    question_style = ParagraphStyle(
        'QuestionStyle', parent=styles['Normal'],
        fontSize=10.5, leading=16, spaceAfter=4,
        textColor=colors.HexColor('#212121')
    )
    option_style = ParagraphStyle(
        'OptionStyle', parent=styles['Normal'],
        fontSize=10, leading=14, leftIndent=20,
        textColor=colors.HexColor('#37474f'), spaceAfter=2
    )

    # ── QUESTION PAPER ──
    doc = SimpleDocTemplate(
        output_path,
        pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm,
        topMargin=2.2*cm, bottomMargin=2.2*cm
    )

    story = []

    # Header box
    header_data = [[
        Paragraph("NEET 2026 — PHYSICS MOCK TEST", title_style),
    ]]
    header_table = Table(header_data, colWidths=[17*cm])
    header_table.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#e8eaf6')),
        ('BOX', (0,0), (-1,-1), 1.5, colors.HexColor('#1a237e')),
        ('TOPPADDING', (0,0), (-1,-1), 8),
        ('BOTTOMPADDING', (0,0), (-1,-1), 8),
        ('LEFTPADDING', (0,0), (-1,-1), 10),
        ('RIGHTPADDING', (0,0), (-1,-1), 10),
    ]))
    story.append(header_table)
    story.append(Spacer(1, 0.3*cm))

    # Meta row
    meta_data = [[
        Paragraph("Total Questions: 50", subtitle_style),
        Paragraph("Maximum Marks: 200", subtitle_style),
        Paragraph("Time: 60 Minutes", subtitle_style),
        Paragraph("Subject: Physics", subtitle_style),
    ]]
    meta_table = Table(meta_data, colWidths=[4.25*cm]*4)
    meta_table.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#f3f4fa')),
        ('BOX', (0,0), (-1,-1), 0.8, colors.HexColor('#9fa8da')),
        ('INNERGRID', (0,0), (-1,-1), 0.5, colors.HexColor('#c5cae9')),
        ('TOPPADDING', (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('ALIGN', (0,0), (-1,-1), 'CENTER'),
    ]))
    story.append(meta_table)
    story.append(Spacer(1, 0.3*cm))

    # Instructions
    instructions = [
        "• Each correct answer carries <b>+4 marks</b>. Each wrong answer carries <b>−1 mark</b>. Unattempted questions carry 0.",
        "• All questions are single-correct type (only one option is correct).",
        "• Use of calculator is not permitted.",
        "• Topics covered: Mechanics | Thermodynamics | Waves | Optics | Electricity & Magnetism | Modern Physics | Electronics",
    ]
    inst_data = [[Paragraph("<br/>".join(instructions), instruction_style)]]
    inst_table = Table(inst_data, colWidths=[17*cm])
    inst_table.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#fff8e1')),
        ('BOX', (0,0), (-1,-1), 0.8, colors.HexColor('#ffcc02')),
        ('TOPPADDING', (0,0), (-1,-1), 6),
        ('BOTTOMPADDING', (0,0), (-1,-1), 6),
        ('LEFTPADDING', (0,0), (-1,-1), 8),
        ('RIGHTPADDING', (0,0), (-1,-1), 8),
    ]))
    story.append(inst_table)
    story.append(Spacer(1, 0.4*cm))
    story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#9fa8da')))
    story.append(Spacer(1, 0.3*cm))

    current_topic = None
    for q in questions:
        # Topic section divider
        if q['topic'] != current_topic:
            current_topic = q['topic']
            topic_data = [[Paragraph(f"  ◆  {current_topic.upper()}", topic_style)]]
            topic_table = Table(topic_data, colWidths=[17*cm])
            topic_table.setStyle(TableStyle([
                ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#e3f2fd')),
                ('BOX', (0,0), (-1,-1), 0.8, colors.HexColor('#1565c0')),
                ('TOPPADDING', (0,0), (-1,-1), 3),
                ('BOTTOMPADDING', (0,0), (-1,-1), 3),
                ('LEFTPADDING', (0,0), (-1,-1), 6),
            ]))
            story.append(topic_table)
            story.append(Spacer(1, 0.25*cm))

        # Question number + text
        q_text = f"<b>Q{q['no']}.</b>  {q['q']}"
        story.append(Paragraph(q_text, question_style))

        # Options
        labels = ['(A)', '(B)', '(C)', '(D)']
        for i, opt in enumerate(q['opts']):
            story.append(Paragraph(f"{labels[i]}  {opt}", option_style))

        story.append(Spacer(1, 0.35*cm))

    doc.build(story)
    print(f"Question paper saved: {output_path}")

    # ── ANSWER KEY + HINTS ──
    doc2 = SimpleDocTemplate(
        answer_key_path,
        pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm,
        topMargin=2.2*cm, bottomMargin=2.2*cm
    )

    answer_key_title_style = ParagraphStyle(
        'AKTitle', parent=styles['Title'],
        fontSize=16, alignment=TA_CENTER,
        textColor=colors.HexColor('#1b5e20'), spaceAfter=4
    )
    ak_q_style = ParagraphStyle(
        'AKQ', parent=styles['Normal'],
        fontSize=10, leading=14, spaceAfter=2,
        textColor=colors.HexColor('#212121')
    )
    ak_hint_style = ParagraphStyle(
        'AKHint', parent=styles['Normal'],
        fontSize=9, leading=13, leftIndent=20,
        textColor=colors.HexColor('#1b5e20'), spaceAfter=8
    )

    story2 = []

    ak_header = Table([[Paragraph("ANSWER KEY & HINTS — NEET 2026 Physics Mock Test", answer_key_title_style)]],
                      colWidths=[17*cm])
    ak_header.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#e8f5e9')),
        ('BOX', (0,0), (-1,-1), 1.5, colors.HexColor('#2e7d32')),
        ('TOPPADDING', (0,0), (-1,-1), 8),
        ('BOTTOMPADDING', (0,0), (-1,-1), 8),
    ]))
    story2.append(ak_header)
    story2.append(Spacer(1, 0.4*cm))

    # Quick grid
    grid_items = []
    row = []
    for q in questions:
        row.append(Paragraph(f"<b>Q{q['no']}</b>: {q['ans']}", ParagraphStyle(
            'grid', parent=styles['Normal'], fontSize=9, alignment=TA_CENTER
        )))
        if len(row) == 10:
            grid_items.append(row)
            row = []
    if row:
        while len(row) < 10:
            row.append(Paragraph("", styles['Normal']))
        grid_items.append(row)

    grid_table = Table(grid_items, colWidths=[1.7*cm]*10)
    grid_table.setStyle(TableStyle([
        ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#a5d6a7')),
        ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#f1f8e9')),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('ALIGN', (0,0), (-1,-1), 'CENTER'),
    ]))
    story2.append(Paragraph("<b>Quick Answer Grid</b>", ParagraphStyle(
        'GridTitle', parent=styles['Normal'], fontSize=10,
        textColor=colors.HexColor('#1b5e20'), spaceAfter=4
    )))
    story2.append(grid_table)
    story2.append(Spacer(1, 0.5*cm))
    story2.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#a5d6a7')))
    story2.append(Spacer(1, 0.3*cm))
    story2.append(Paragraph("<b>Detailed Explanations</b>", ParagraphStyle(
        'GridTitle', parent=styles['Normal'], fontSize=11,
        textColor=colors.HexColor('#1b5e20'), spaceAfter=8
    )))

    for q in questions:
        story2.append(Paragraph(
            f"<b>Q{q['no']}. [{q['topic']}]</b>  Correct Answer: <b>(A) {q['opts'][0]}</b>",
            ak_q_style
        ))
        story2.append(Paragraph(f"<i>Explanation:</i> {q['hint']}", ak_hint_style))

    doc2.build(story2)
    print(f"Answer key saved: {answer_key_path}")


if __name__ == "__main__":
    qp = "/home/daytona/workspace/neet-physics-mock/NEET_2026_Physics_Mock_Test.pdf"
    ak = "/home/daytona/workspace/neet-physics-mock/NEET_2026_Physics_Answer_Key.pdf"
    make_pdf(qp, ak)
Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.

where are questions

Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.

atleast give questions

Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.
This is a shared conversation. Sign in to Orris to start your own chat.