NavyDevilDoc commited on
Commit
ccd1e0f
·
verified ·
1 Parent(s): b43a893

Update src/core/QuizEngine.py

Browse files
Files changed (1) hide show
  1. src/core/QuizEngine.py +57 -101
src/core/QuizEngine.py CHANGED
@@ -11,12 +11,9 @@ class QuizEngine:
11
 
12
  # --- MODE 1: ACRONYMS ---
13
  def get_random_acronym(self):
14
- if not self.acronym_mgr.acronyms:
15
- return None
16
-
17
  acronym = random.choice(list(self.acronym_mgr.acronyms.keys()))
18
  definition = self.acronym_mgr.acronyms[acronym]
19
-
20
  return {
21
  "type": "acronym",
22
  "term": acronym,
@@ -24,26 +21,21 @@ class QuizEngine:
24
  "question": f"What does **{acronym}** stand for?"
25
  }
26
 
27
- # --- MODE 2: DOCUMENTS (Updated for Guided Quizzing) ---
28
  def get_document_context(self, username, topic_filter=None):
29
  """
30
- Fetches a context chunk with Tiered Fallback.
31
- Returns None if absolutely no files exist.
32
- Returns {'error': 'topic_not_found'} if the filter is too strict.
33
  """
34
  user_dir = os.path.join(self.source_dir, username)
35
  if not os.path.exists(user_dir): return None
36
 
37
  files = [f for f in os.listdir(user_dir) if f.lower().endswith(('.txt', '.md'))]
38
  if not files: return None
39
-
40
- # Shuffle files to ensure randomness
41
  random.shuffle(files)
42
 
43
- # Track if we found ANY matching files for the topic (for debugging)
44
  topic_match_found = False
45
 
46
- # Attempt loop
47
  for attempt in range(20):
48
  selected_file = random.choice(files)
49
  try:
@@ -51,50 +43,39 @@ class QuizEngine:
51
  with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
52
  text = f.read()
53
 
54
- # CRITICAL CHECK: Is file empty?
55
- if len(text.strip()) < 50: continue
56
 
57
- # --- TIER 1: FILTERING ---
58
  if topic_filter:
59
- if topic_filter.lower() not in text.lower():
60
- continue # Skip this file, it doesn't have the topic
61
- topic_match_found = True # We found a file that has the topic!
62
-
63
- # --- TIER 2: CHUNKING ---
64
- # Sliding Window Strategy
65
- step_size = 1000
66
- window_size = 1500
67
  candidates = []
68
-
69
- # If text is small, take it all
70
  if len(text) < window_size:
71
  candidates.append(text)
72
  else:
73
- # Scan the file
74
  for i in range(0, len(text) - window_size, step_size):
75
  chunk = text[i : i + window_size]
76
 
77
- # Soft Filter: Skip if mostly empty
78
- if len(chunk.strip()) < 50: continue
79
 
80
- # Topic Check (Fine-grained)
81
- if topic_filter and topic_filter.lower() not in chunk.lower():
82
- continue
83
-
84
  candidates.append(chunk)
85
 
86
- # FALLBACK: If candidates is empty (maybe topic is in file but split across chunks?)
87
- # We just grab a random chunk from the file that contains the topic
88
  if not candidates and topic_filter and topic_match_found:
89
- # Crude fallback: Find the index of the word and grab text around it
90
  idx = text.lower().find(topic_filter.lower())
91
- start = max(0, idx - 500)
92
- end = min(len(text), idx + 1000)
93
  candidates.append(text[start:end])
94
 
95
  if not candidates: continue
96
 
97
- # Success!
98
  selected_context = random.choice(candidates)
99
 
100
  return {
@@ -104,78 +85,53 @@ class QuizEngine:
104
  }
105
 
106
  except Exception as e:
107
- self.logger.error(f"Error fetching context from {selected_file}: {e}")
108
  continue
109
 
110
- # If we failed after 20 tries:
111
- if topic_filter and not topic_match_found:
112
- return {"error": "topic_not_found"}
113
-
114
  return None
115
 
116
- def construct_question_generation_prompt(self, context_text):
 
 
117
  """
118
- REVISED PROMPT: 'Board-Style' Questions (No Spoilers).
119
- Includes Examples for quality, but output is strictly the question text.
120
  """
121
  return (
122
- f"Act as a Navy Board Examiner.\n"
123
- f"Here is a raw text excerpt from Navy documentation:\n"
124
- f"'''{context_text}'''\n\n"
125
- f"TASK: Generate a single question based on this text.\n\n"
 
 
126
 
127
- f"EXAMPLES OF DESIRED QUALITY (DO THIS):\n"
128
- f"1. [Simple Fact]\n"
129
- f" Text: 'The NSWC is in Maryland.'\n"
130
- f" Good Question: 'Where are the Naval Surface Warfare Centers located?'\n"
131
- f"2. [Process/Order]\n"
132
- f" Text: 'There are five acquisition phases.'\n"
133
- f" Good Question: 'What are the five acquisition phases?'\n"
134
- f"3. [Scenario/Application]\n"
135
- f" Text: 'A significant Nunn-McCurdy Unit Cost Breach is defined as cost growth of 15% over current baseline.'\n"
136
- f" Good Question: 'What conditions trigger a significant Nunn-McCurdy Unit Cost Breach?'\n\n"
137
-
138
- f"EXAMPLES OF POOR QUALITY (AVOID THIS):\n"
139
- f"1. [Lazy/Meta-Reference]\n"
140
- f" Bad Question: 'What does the text say about the budget?'\n"
141
- f" Critique: Do not reference 'the text'. Ask about the subject matter directly.\n"
142
- f"2. [Yes/No]\n"
143
- f" Bad Question: 'Is the budget cycle annual?'\n"
144
- f" Critique: Too easy. Ask 'What is the duration of the budget cycle?' instead.\n\n"
145
-
146
- f"STRICT RULES:\n"
147
- f"1. You must base your question on a SPECIFIC SENTENCE in the text.\n"
148
- f"2. If the text is meaningless or 'Intentionally Left Blank', output 'UNABLE'.\n\n"
149
 
150
  f"OUTPUT FORMAT:\n"
151
- f"Output ONLY the question text. Do NOT output the answer or the quote."
 
152
  )
153
-
154
- def construct_grading_prompt(self, question, answer, context_text):
155
- """
156
- Grades the answer using a composite context (Seed + RAG Results).
157
- """
158
- return (
159
- f"You are a Board Examiner.\n"
160
- f"Reference Material (Combined Sources):\n"
161
- f"'''{context_text}'''\n\n"
162
- f"Question: {question}\n"
163
- f"Candidate Answer: {answer}\n\n"
164
- f"TASK: Grade the answer based strictly on the Reference Material above.\n"
165
- f"1. Search the Reference Material for the correct answer. The answer might be split across the Primary Source and Related Documentation.\n"
166
- f"2. If the candidate's answer matches the facts found ANYWHERE in the text, grade PASS.\n"
167
- f"3. If the candidate misses key details that ARE present in the text, grade FAIL or PASS with Comments.\n"
168
- f"4. If the provided Reference Material does NOT contain the answer (i.e., retrieval failed), be lenient and grade based on general knowledge, but note 'Verified by General Knowledge' in the feedback.\n\n"
169
- f"OUTPUT FORMAT:\n"
170
- f"**GRADE:** [PASS/FAIL]\n"
171
- f"**FEEDBACK:** [Brief correction or confirmation]"
172
- )
173
-
174
  def construct_acronym_grading_prompt(self, term, correct_definition, user_answer):
175
- return (
176
- f"Term: {term}\n"
177
- f"Official Definition: {correct_definition}\n"
178
- f"User Answer: {user_answer}\n\n"
179
- f"Grade as PASS (correct expansion) or FAIL. If close, PASS with comment.\n"
180
- f"Output: **GRADE:** [Status]\n**FEEDBACK:** [Details]"
181
- )
 
11
 
12
  # --- MODE 1: ACRONYMS ---
13
  def get_random_acronym(self):
14
+ if not self.acronym_mgr.acronyms: return None
 
 
15
  acronym = random.choice(list(self.acronym_mgr.acronyms.keys()))
16
  definition = self.acronym_mgr.acronyms[acronym]
 
17
  return {
18
  "type": "acronym",
19
  "term": acronym,
 
21
  "question": f"What does **{acronym}** stand for?"
22
  }
23
 
24
+ # --- MODE 2: SCENARIO SIMULATOR (Updated) ---
25
  def get_document_context(self, username, topic_filter=None):
26
  """
27
+ Fetches a LARGE context chunk (4000 chars) to ensure continuity.
 
 
28
  """
29
  user_dir = os.path.join(self.source_dir, username)
30
  if not os.path.exists(user_dir): return None
31
 
32
  files = [f for f in os.listdir(user_dir) if f.lower().endswith(('.txt', '.md'))]
33
  if not files: return None
 
 
34
  random.shuffle(files)
35
 
36
+ # Track if we found topic match
37
  topic_match_found = False
38
 
 
39
  for attempt in range(20):
40
  selected_file = random.choice(files)
41
  try:
 
43
  with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
44
  text = f.read()
45
 
46
+ if len(text.strip()) < 100: continue
 
47
 
48
+ # TIER 1: Topic Filter
49
  if topic_filter:
50
+ if topic_filter.lower() not in text.lower(): continue
51
+ topic_match_found = True
52
+
53
+ # TIER 2: Large Window Extraction (The "Mega-Window")
54
+ # We grab 4000 chars instead of 1500 to get "Before & After" context
55
+ window_size = 4000
56
+ step_size = 2000
 
57
  candidates = []
58
+
 
59
  if len(text) < window_size:
60
  candidates.append(text)
61
  else:
 
62
  for i in range(0, len(text) - window_size, step_size):
63
  chunk = text[i : i + window_size]
64
 
65
+ if len(chunk.strip()) < 200: continue
66
+ if topic_filter and topic_filter.lower() not in chunk.lower(): continue
67
 
 
 
 
 
68
  candidates.append(chunk)
69
 
70
+ # Fallback: If topic matches file but logic missed it, force a grab
 
71
  if not candidates and topic_filter and topic_match_found:
 
72
  idx = text.lower().find(topic_filter.lower())
73
+ start = max(0, idx - 1000)
74
+ end = min(len(text), idx + 3000)
75
  candidates.append(text[start:end])
76
 
77
  if not candidates: continue
78
 
 
79
  selected_context = random.choice(candidates)
80
 
81
  return {
 
85
  }
86
 
87
  except Exception as e:
88
+ self.logger.error(f"Error fetching context: {e}")
89
  continue
90
 
91
+ if topic_filter and not topic_match_found: return {"error": "topic_not_found"}
 
 
 
92
  return None
93
 
94
+ # --- PROMPTS ---
95
+
96
+ def construct_scenario_prompt(self, context_text):
97
  """
98
+ Generates a 'Board-Style' Scenario.
99
+ Forces the model to output a Scenario AND a Hidden Solution.
100
  """
101
  return (
102
+ f"Act as a Senior Navy Board Examiner.\n"
103
+ f"Reference Material:\n'''{context_text}'''\n\n"
104
+ f"TASK: \n"
105
+ f"1. Identify a key technical concept in the text (e.g., Stability, Finance, Contracting).\n"
106
+ f"2. Create a REALISTIC SCENARIO based on this concept. Do not ask 'What is X?'. Instead, describe a situation (e.g., 'You are the DCA...', 'A contractor submits a bid...') and ask for the candidate's assessment.\n"
107
+ f"3. Create the OFFICIAL SOLUTION explaining the 'Why' behind the answer.\n\n"
108
 
109
+ f"STRICT OUTPUT FORMAT:\n"
110
+ f"SCENARIO: [Your scenario text here]\n"
111
+ f"SOLUTION: [The detailed answer key]"
112
+ )
113
+
114
+ def construct_scenario_grading_prompt(self, scenario, user_answer, solution, context_text):
115
+ """
116
+ Grades with the specific 'Board Assessment' persona requested.
117
+ """
118
+ return (
119
+ f"Act as a Senior Navy Board Examiner grading a candidate's oral response.\n\n"
120
+ f"--- THE SCENARIO ---\n{scenario}\n\n"
121
+ f"--- OFFICIAL SOLUTION (For You) ---\n{solution}\n\n"
122
+ f"--- REFERENCE TEXT ---\n{context_text}\n\n"
123
+ f"--- CANDIDATE ANSWER ---\n{user_answer}\n\n"
124
+
125
+ f"TASK: Grade the candidate.\n"
126
+ f"1. Compare their answer to the Official Solution and Reference Text.\n"
127
+ f"2. Look for technical precision (e.g., 'G rises' vs 'Weight moves').\n"
128
+ f"3. Provide a numeric grade and a structured critique.\n\n"
 
 
129
 
130
  f"OUTPUT FORMAT:\n"
131
+ f"**Grade:** [0-10]/10\n"
132
+ f"**Critique:** [Your detailed feedback. Be firm but constructive. Highlight specifically what they missed (e.g., 'You identified the List, but failed to identify the Loll.').]"
133
  )
134
+
135
+ # Legacy prompts (keep for safety if you switch modes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  def construct_acronym_grading_prompt(self, term, correct_definition, user_answer):
137
+ return f"Term: {term}\nDefinition: {correct_definition}\nAnswer: {user_answer}\nGrade PASS/FAIL."