codeboosterstech commited on
Commit
ff123a5
Β·
verified Β·
1 Parent(s): f2a8c3c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +311 -0
app.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ from datetime import datetime
4
+ import json
5
+ import re
6
+ import zipfile
7
+ import docx
8
+ from docx import Document
9
+ import pdfplumber
10
+ from langchain_groq import ChatGroq
11
+ from langchain_community.utilities import SerpAPIWrapper
12
+ from langchain_core.messages import HumanMessage
13
+
14
+ # Initialize models (will use environment variables)
15
+ def init_models():
16
+ groq_key = os.getenv("GROQ_API_KEY")
17
+ serp_key = os.getenv("SERPAPI_API_KEY")
18
+
19
+ if not groq_key or not serp_key:
20
+ return None, None, None, None
21
+
22
+ model_question_gen = ChatGroq(model="llama-3.3-70b-versatile", api_key=groq_key)
23
+ model_answer_gen = ChatGroq(model="llama-3.3-70b-versatile", api_key=groq_key)
24
+ model_trend_analyzer = ChatGroq(model="groq/compound", api_key=groq_key)
25
+ serp = SerpAPIWrapper(serpapi_api_key=serp_key)
26
+
27
+ return model_question_gen, model_answer_gen, model_trend_analyzer, serp
28
+
29
+ # Utility functions
30
+ def extract_docx(path):
31
+ d = docx.Document(path)
32
+ return "\n".join(p.text for p in d.paragraphs if p.text.strip())
33
+
34
+ def extract_pdf(path):
35
+ out = []
36
+ with pdfplumber.open(path) as pdf:
37
+ for p in pdf.pages:
38
+ t = p.extract_text()
39
+ if t: out.append(t)
40
+ return "\n".join(out)
41
+
42
+ def extract_text(path):
43
+ if path.endswith(".pdf"): return extract_pdf(path)
44
+ if path.endswith(".docx"): return extract_docx(path)
45
+ return open(path, 'r', encoding='utf-8').read()
46
+
47
+ def sanitize_json(text):
48
+ text = text.replace("```json", "").replace("```", "")
49
+ m = re.search(r"(\{.*\})", text, flags=re.DOTALL)
50
+ if not m:
51
+ raise RuntimeError(f"ERROR: LLM did NOT return JSON.")
52
+ blob = m.group(1)
53
+ blob = re.sub(r",\s*(\}|\])", r"\1", blob)
54
+ return json.loads(blob)
55
+
56
+ def extract_units(syllabus_text, unit_range):
57
+ unit_range = unit_range.replace(" ", "")
58
+ if "-" in unit_range:
59
+ start, end = map(int, unit_range.split("-"))
60
+ units = list(range(start, end + 1))
61
+ else:
62
+ units = [int(u) for u in unit_range.split(",")]
63
+
64
+ unit_header_pattern = r"(UNIT[\s\-β€”:]*([0-9IVX]+))"
65
+ matches = list(re.finditer(unit_header_pattern, syllabus_text, flags=re.I))
66
+
67
+ if not matches:
68
+ return syllabus_text
69
+
70
+ unit_blocks = []
71
+ for i, m in enumerate(matches):
72
+ raw_num = m.group(2).strip()
73
+ try:
74
+ if raw_num.isdigit():
75
+ num = int(raw_num)
76
+ else:
77
+ roman_map = {"I":1,"II":2,"III":3,"IV":4,"V":5,"VI":6,"VII":7,"VIII":8,"IX":9,"X":10}
78
+ num = roman_map.get(raw_num.upper(), None)
79
+ except:
80
+ num = None
81
+
82
+ if num:
83
+ start = m.start()
84
+ end = matches[i+1].start() if i+1 < len(matches) else len(syllabus_text)
85
+ unit_blocks.append((num, start, end))
86
+
87
+ extracted = ""
88
+ for u in units:
89
+ for block in unit_blocks:
90
+ if block[0] == u:
91
+ extracted += syllabus_text[block[1]:block[2]].strip() + "\n\n"
92
+
93
+ return extracted.strip() if extracted else syllabus_text
94
+
95
+ def apply_MAANGO_BIG15_framework(base_prompt):
96
+ maango_block = """
97
+ === MAANGO BIG15 ADVANCED QUESTION ENGINE FRAMEWORK ===
98
+ You MUST follow ALL 15 pillars while generating the question paper:
99
+ 1. M β€” Multi-Cognitive Bloom Alignment
100
+ 2. A β€” Applyβ€”Analyze Weightage Boost
101
+ 3. A β€” Adaptive Difficulty Index
102
+ 4. N β€” Non-Repetitive Deep Coverage
103
+ 5. G β€” Granular Unit Balancing
104
+ 6. O β€” Outcome Mapping Discipline
105
+ 7. B β€” BIG15 Industry Integration
106
+ 8. I β€” Industry Application Layer
107
+ 9. G β€” GATE Layer Injection
108
+ 10. 1 β€” First-Half / Second-Half Coverage Integrity
109
+ 11. 5 β€” Five-Unit Symmetry
110
+ 12. S β€” Structured Output Discipline
111
+ 13. E β€” Exam-Mode Smart Switching
112
+ 14. T β€” Technical Depth Enforcement
113
+ 15. H β€” Holistic Coherence
114
+ === END OF MAANGO BIG15 FRAMEWORK ===
115
+ """
116
+ return maango_block + "\n\n" + base_prompt
117
+
118
+ def build_question_prompt(subject, syllabus, numA, numB, numC, exam_mode):
119
+ base_prompt = f"""
120
+ You are an exam generator for {exam_mode} mode. Output ONLY VALID JSON.
121
+
122
+ STRICT JSON SCHEMA:
123
+ {{
124
+ "metadata": {{"subject": "{subject}", "date": "{datetime.now().strftime('%Y-%m-%d')}"}},
125
+ "partA": [{{"question_text": "string", "marks": 2, "unit": 1, "bloom_level": "Remember", "company_tag": "Generic"}}],
126
+ "partB": [{{"either": {{"question_text": "string", "marks": 10, "unit": 1, "bloom_level": "Analyze", "company_tag": "TCS"}}, "or": {{"question_text": "string", "marks": 10, "unit": 1, "bloom_level": "Analyze", "company_tag": "TCS"}}}}],
127
+ "partC": [{{"either": {{"question_text": "string", "marks": 15, "unit": 1, "bloom_level": "Create", "company_tag": "Infosys"}}, "or": {{"question_text": "string", "marks": 15, "unit": 1, "bloom_level": "Create", "company_tag": "Infosys"}}}}]
128
+ }}
129
+
130
+ Generate EXACTLY {numA} questions in partA, {numB} pairs in partB, {numC} pairs in partC.
131
+ Syllabus: {syllabus}
132
+ Return ONLY pure JSON. No commentary.
133
+ """
134
+ return apply_MAANGO_BIG15_framework(base_prompt)
135
+
136
+ def create_question_paper(code, name, partA, partB, partC, output_path):
137
+ doc = Document()
138
+ doc.add_heading("SNS College of Technology", level=1)
139
+ doc.add_paragraph(f"Subject Code: {code} Subject: {name}")
140
+ doc.add_paragraph(f"Date: {datetime.now().strftime('%Y-%m-%d')}\n")
141
+
142
+ doc.add_heading("Part A (Short Answer)", level=2)
143
+ for idx, q in enumerate(partA, 1):
144
+ doc.add_paragraph(f"{idx}. {q.get('question_text','')} (Marks: {q.get('marks',2)})")
145
+
146
+ doc.add_heading("Part B (Either/Or Questions)", level=2)
147
+ for idx, pair in enumerate(partB, len(partA)+1):
148
+ doc.add_paragraph(f"{idx}. Either: {pair['either']['question_text']} (10 marks)")
149
+ doc.add_paragraph(f" Or: {pair['or']['question_text']} (10 marks)")
150
+
151
+ doc.add_heading("Part C (Case/Design Questions)", level=2)
152
+ for idx, pair in enumerate(partC, len(partA)+len(partB)+1):
153
+ doc.add_paragraph(f"{idx}. Either: {pair['either']['question_text']} (15 marks)")
154
+ doc.add_paragraph(f" Or: {pair['or']['question_text']} (15 marks)")
155
+
156
+ doc.save(output_path)
157
+
158
+ def create_answer_key(code, name, answers, output_path):
159
+ doc = Document()
160
+ doc.add_heading(f"{name} - Answer Key", level=1)
161
+ doc.add_paragraph(f"Subject Code: {code}\n")
162
+
163
+ doc.add_heading("Part A Answers", level=2)
164
+ for idx, a in enumerate(answers.get("partA", []), 1):
165
+ doc.add_paragraph(f"{idx}. {a.get('answer','N/A')}")
166
+
167
+ doc.add_heading("Part B Answers", level=2)
168
+ for idx, a in enumerate(answers.get("partB", []), len(answers.get("partA",[]))+1):
169
+ doc.add_paragraph(f"{idx}. {a.get('answer','N/A')}")
170
+
171
+ doc.add_heading("Part C Answers", level=2)
172
+ start = len(answers.get("partA",[]))+len(answers.get("partB",[]))+1
173
+ for idx, a in enumerate(answers.get("partC", []), start):
174
+ doc.add_paragraph(f"{idx}. {a.get('answer','N/A')}")
175
+
176
+ doc.save(output_path)
177
+
178
+ def generate_exam(exam_mode, subject, code, units, numA, numB, numC, syllabus_file, progress=gr.Progress()):
179
+ try:
180
+ progress(0, desc="Initializing...")
181
+
182
+ # Initialize models
183
+ model_q, model_a, model_t, serp = init_models()
184
+ if not model_q:
185
+ return None, "❌ API Keys not configured. Please set GROQ_API_KEY and SERPAPI_API_KEY in Hugging Face Spaces secrets."
186
+
187
+ # Extract syllabus
188
+ progress(0.2, desc="Extracting syllabus...")
189
+ syllabus_text = extract_text(syllabus_file.name)
190
+ selected_syllabus = extract_units(syllabus_text, units)
191
+
192
+ # Generate questions
193
+ progress(0.4, desc="Generating questions...")
194
+ q_prompt = build_question_prompt(subject, selected_syllabus, numA, numB, numC, exam_mode)
195
+ q_raw = model_q.invoke([HumanMessage(content=q_prompt)]).content
196
+ q_json = sanitize_json(q_raw)
197
+
198
+ # Generate answers
199
+ progress(0.7, desc="Generating answer key...")
200
+ a_prompt = f"Generate answers for: {json.dumps(q_json)}"
201
+ a_raw = model_a.invoke([HumanMessage(content=a_prompt)]).content
202
+ a_json = sanitize_json(a_raw)
203
+
204
+ # Create files
205
+ progress(0.9, desc="Creating documents...")
206
+ qp_file = f"{code}_QuestionPaper.docx"
207
+ ak_file = f"{code}_AnswerKey.docx"
208
+
209
+ create_question_paper(code, subject, q_json["partA"], q_json["partB"], q_json["partC"], qp_file)
210
+ create_answer_key(code, subject, a_json, ak_file)
211
+
212
+ # Create zip
213
+ zip_file = f"{code}_ExamPackage.zip"
214
+ with zipfile.ZipFile(zip_file, 'w') as zipf:
215
+ zipf.write(qp_file)
216
+ zipf.write(ak_file)
217
+
218
+ progress(1.0, desc="Complete!")
219
+ return zip_file, f"βœ… Successfully generated exam package for {subject}!"
220
+
221
+ except Exception as e:
222
+ return None, f"❌ Error: {str(e)}"
223
+
224
+ # Custom CSS
225
+ custom_css = """
226
+ .gradio-container {
227
+ font-family: 'Inter', sans-serif;
228
+ max-width: 1200px !important;
229
+ }
230
+ .header-text {
231
+ text-align: center;
232
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
233
+ color: white;
234
+ padding: 2rem;
235
+ border-radius: 10px;
236
+ margin-bottom: 2rem;
237
+ }
238
+ .feature-box {
239
+ background: #f8f9fa;
240
+ padding: 1rem;
241
+ border-radius: 8px;
242
+ border-left: 4px solid #667eea;
243
+ }
244
+ """
245
+
246
+ # Create Gradio interface
247
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
248
+ gr.HTML("""
249
+ <div class="header-text">
250
+ <h1>πŸŽ“ MAANGO BIG15 Exam Generator</h1>
251
+ <p>AI-Powered Question Paper & Answer Key Generator</p>
252
+ <p style="font-size: 0.9rem; opacity: 0.9;">Powered by Advanced LLM Technology | Industry-Standard Framework</p>
253
+ </div>
254
+ """)
255
+
256
+ with gr.Row():
257
+ with gr.Column(scale=1):
258
+ gr.Markdown("### πŸ“‹ Exam Configuration")
259
+ exam_mode = gr.Dropdown(
260
+ choices=["Continuous Assessment (CA)", "End Semester Exam (ESE)", "GATE Style Internal Exam"],
261
+ label="Exam Mode",
262
+ value="End Semester Exam (ESE)"
263
+ )
264
+ subject = gr.Textbox(label="Subject Name", placeholder="e.g., Data Structures")
265
+ code = gr.Textbox(label="Subject Code", placeholder="e.g., CS301")
266
+ units = gr.Textbox(label="Units Range", value="1-5", placeholder="e.g., 1-3 or 1,3,5")
267
+
268
+ gr.Markdown("### πŸ“Š Question Distribution")
269
+ with gr.Row():
270
+ numA = gr.Number(label="Part A (Short)", value=10, precision=0)
271
+ numB = gr.Number(label="Part B (Descriptive)", value=5, precision=0)
272
+ numC = gr.Number(label="Part C (Case Study)", value=1, precision=0)
273
+
274
+ with gr.Column(scale=1):
275
+ gr.Markdown("### πŸ“ Syllabus Upload")
276
+ syllabus_file = gr.File(label="Upload Syllabus", file_types=[".pdf", ".docx", ".txt"])
277
+
278
+ gr.Markdown("""
279
+ <div class="feature-box">
280
+ <h4>✨ Key Features</h4>
281
+ <ul>
282
+ <li>🎯 Bloom's Taxonomy Alignment</li>
283
+ <li>🏒 Industry Tag Integration (TCS/Infosys/Wipro)</li>
284
+ <li>πŸ“ˆ Balanced Unit Coverage</li>
285
+ <li>πŸ” GATE-Style Question Design</li>
286
+ <li>πŸ“ Automatic Answer Key Generation</li>
287
+ </ul>
288
+ </div>
289
+ """)
290
+
291
+ generate_btn = gr.Button("πŸš€ Generate Exam Package", variant="primary", size="lg")
292
+
293
+ with gr.Row():
294
+ output_file = gr.File(label="πŸ“¦ Download Package")
295
+ status_msg = gr.Textbox(label="Status", lines=2)
296
+
297
+ generate_btn.click(
298
+ fn=generate_exam,
299
+ inputs=[exam_mode, subject, code, units, numA, numB, numC, syllabus_file],
300
+ outputs=[output_file, status_msg]
301
+ )
302
+
303
+ gr.Markdown("""
304
+ ---
305
+ <center>
306
+ <p style="color: #666;">Developed with ❀️ using MAANGO BIG15 Framework | © 2024</p>
307
+ </center>
308
+ """)
309
+
310
+ if __name__ == "__main__":
311
+ demo.launch()