-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstudent_answer_analyzer.py
More file actions
294 lines (235 loc) · 9.56 KB
/
Copy pathstudent_answer_analyzer.py
File metadata and controls
294 lines (235 loc) · 9.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/usr/bin/env python3
"""
Student Answer Sheet Analyzer with Trap Category Integration
Loads student answer sheets (Excel format), compares with answer key,
and generates comprehensive reports with trap analysis.
Supports dynamic answer key loading for different questionnaires/exams.
"""
import json
import sys
from pathlib import Path
from openpyxl import load_workbook
from cissp_analyzer.trap_analysis_engine import TrapAnalysisEngine
def load_answer_key(answer_key_path=None):
"""
Load answer key from file or dict.
Args:
answer_key_path: Path to answer key JSON file. If None, checks common locations.
Can also be passed as dict from caller.
Returns:
Dict with integer keys (question numbers) and string values (correct answers A-D)
"""
# If it's already a dict, return as-is
if isinstance(answer_key_path, dict):
return {int(k): v.upper() for k, v in answer_key_path.items()}
# Try specified path first
if answer_key_path:
path = Path(answer_key_path)
if path.exists():
with open(path, "r") as f:
data = json.load(f)
return {int(k): v.upper() for k, v in data.items()}
else:
raise FileNotFoundError(f"Answer key not found at {answer_key_path}")
# Fallback: check common locations (for backward compatibility)
common_paths = [
Path("data/answer_key.json"),
Path("../data/answer_key.json"),
Path("./answer_key.json"),
]
for path in common_paths:
if path.exists():
with open(path, "r") as f:
data = json.load(f)
return {int(k): v.upper() for k, v in data.items()}
return {}
def load_student_answers_from_excel(file_path):
"""Load student answers from Excel file"""
wb = load_workbook(file_path)
ws = wb.active
answers = {}
student_name = None
# Extract student name from filename
filename = Path(file_path).stem
student_name = filename
# Parse answers (skip header row)
for row in ws.iter_rows(min_row=2, values_only=True):
if row[0] is None or row[1] is None:
continue
try:
q_num = int(row[0])
answer = str(row[1]).strip().upper()
answers[q_num] = answer
except (ValueError, TypeError):
continue
return student_name, answers
def generate_student_report(student_name, answers, answer_key, engine):
"""Generate comprehensive report for a single student"""
# Analyze answers
results = engine.analyze_all_answers(answers, answer_key)
# Calculate basic stats
total_questions = len(results)
correct = sum(1 for r in results if r.is_correct)
wrong = total_questions - correct
score_pct = (correct / total_questions * 100) if total_questions > 0 else 0
# Summarize trap vulnerabilities
vulnerabilities = engine.summarize_vulnerabilities(results)
# Generate recommendations
recommendations = engine.generate_recommendations(vulnerabilities)
return {
"student_name": student_name,
"total_questions": total_questions,
"correct": correct,
"wrong": wrong,
"score_percentage": round(score_pct, 1),
"analysis_results": results,
"vulnerabilities": vulnerabilities,
"recommendations": recommendations,
}
def print_student_report(report):
"""Print formatted student report"""
student = report["student_name"]
correct = report["correct"]
total = report["total_questions"]
score = report["score_percentage"]
print(f"\n{'='*80}")
print(f"STUDENT: {student}")
print(f"{'='*80}")
print(f"Score: {correct}/{total} ({score:.1f}%)")
print(f"Status: {'PASS (≥70%)' if score >= 70 else 'NEEDS IMPROVEMENT (<70%)'}")
# Show trap vulnerabilities
vulnerabilities = report["vulnerabilities"]
if vulnerabilities:
print(f"\n🔴 TRAP VULNERABILITIES ({len(vulnerabilities)} identified):")
for vuln in vulnerabilities[:5]: # Top 5
print(f"\n {vuln.trap_category} - {vuln.trap_name}")
print(f" └─ Fell for this trap: {vuln.frequency_count} times")
print(f" └─ Questions: {vuln.affected_questions}")
print(f" └─ Success rate: {vuln.success_rate:.1f}%")
# Show recommendations
recs = report["recommendations"]
if recs and "study_plan" in recs:
print(f"\n📚 PERSONALIZED STUDY PLAN:")
for i, plan in enumerate(recs["study_plan"][:3], 1):
print(f" {i}. {plan}")
print(f"\n{'='*80}\n")
def main(answer_key_path=None, student_files=None, questionnaire_name="CISSP"):
"""
Main analysis workflow with configurable inputs.
Args:
answer_key_path: Path to answer key JSON file or dict of answers
student_files: List of Excel file paths to analyze. If None, uses default files.
questionnaire_name: Name of the questionnaire (e.g., "CISSP", "Mock Test 2", etc.)
"""
# Initialize trap analysis engine
engine = TrapAnalysisEngine()
# Load answer key
try:
answer_key = load_answer_key(answer_key_path)
except FileNotFoundError as e:
print(f"❌ {e}")
return
if not answer_key:
print(f"❌ Answer key not found or empty")
return
# Use provided student files or default ones (backward compatibility)
if student_files is None:
student_files = [
"/Users/sriram/Downloads/kapil-july-12.xlsx",
"/Users/sriram/Downloads/Mock Test Aman 11 july.xlsx",
"/Users/sriram/Downloads/12 July 2026-Mock test 7 - Senthilraj.xlsx",
"/Users/sriram/Downloads/Mock Test - 07 Jul - Praveena.xlsx",
]
all_reports = []
print("\n" + "="*80)
print(f"CISSP ANALYZER - {questionnaire_name} STUDENT ANSWER SHEET PROCESSING")
print("="*80)
print(f"📋 Questionnaire: {questionnaire_name}")
print(f"📊 Total questions: {len(answer_key)}")
print("="*80)
# Process each student file
for file_path in student_files:
file_obj = Path(file_path)
if not file_obj.exists():
print(f"⚠️ File not found: {file_path}")
continue
print(f"\n📄 Processing: {file_obj.name}")
try:
# Load student answers
student_name, answers = load_student_answers_from_excel(file_path)
print(f" ✅ Loaded {len(answers)} answers from {student_name}")
# Generate report with trap analysis
report = generate_student_report(student_name, answers, answer_key, engine)
all_reports.append(report)
# Print report
print_student_report(report)
except Exception as e:
print(f" ❌ Error processing {file_obj.name}: {e}")
import traceback
traceback.print_exc()
# Print summary
if all_reports:
print("\n" + "="*80)
print("CLASS SUMMARY")
print("="*80)
for report in all_reports:
score = report["score_percentage"]
status = "✅ PASS" if score >= 70 else "⚠️ NEEDS IMPROVEMENT"
print(f"{report['student_name']:25s} {report['correct']:3d}/{report['total_questions']:3d} ({score:5.1f}%) {status}")
# Class average
avg_score = sum(r["score_percentage"] for r in all_reports) / len(all_reports)
print(f"\n{'Class Average':25s} {avg_score:5.1f}%")
print("="*80)
return all_reports
def analyze_questionnaire(config):
"""
Analyze a specific questionnaire configuration.
Args:
config (dict): Configuration with keys:
- name: Questionnaire name
- answer_key: Path to answer key file or dict of answers
- student_files: List of student answer sheet paths
- description: Optional description
Example:
config = {
"name": "CISSP July 2026",
"answer_key": "path/to/answer_key.json",
"student_files": ["student1.xlsx", "student2.xlsx"],
"description": "July practice test batch 2"
}
analyze_questionnaire(config)
"""
print(f"\n🎯 Analyzing: {config.get('description', config['name'])}")
return main(
answer_key_path=config["answer_key"],
student_files=config.get("student_files"),
questionnaire_name=config["name"],
)
if __name__ == "__main__":
# Check for command-line arguments
if len(sys.argv) > 1:
if sys.argv[1] == "--help" or sys.argv[1] == "-h":
print("""
CISSP Analyzer - Student Answer Sheet Processor
Usage:
python student_answer_analyzer.py # Run with default files
python student_answer_analyzer.py <answer_key_path> # Specify custom answer key
python student_answer_analyzer.py <answer_key_path> <student_file1> <student_file2> ...
Examples:
python student_answer_analyzer.py
python student_answer_analyzer.py data/mock_test_1_answers.json student1.xlsx
python student_answer_analyzer.py data/cissp_answers.json *.xlsx
Environment:
- Answer keys: JSON file with format {"1": "A", "2": "B", ...}
- Student sheets: Excel files with columns [Question Number, Student Answer]
- Dynamic loading: Pass any answer key path, reuse system for multiple tests
""")
sys.exit(0)
else:
# Custom answer key path
answer_key = sys.argv[1]
student_files = sys.argv[2:] if len(sys.argv) > 2 else None
main(answer_key_path=answer_key, student_files=student_files)
else:
# Default behavior
main()