KeenWoo commited on
Commit
8f6863a
·
verified ·
1 Parent(s): f5a8bba

Create MMSE.py

Browse files
Files changed (1) hide show
  1. MMSE.py +174 -0
MMSE.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MMSE.py
2
+ # Contains all data, scoring logic, and setup specific to the
3
+ # Mini-Mental State Examination (MMSE).
4
+
5
+ import os
6
+ import time
7
+ from gtts import gTTS
8
+
9
+ # Import shared utilities
10
+ import utils
11
+
12
+ # --- MMSE Specific Data ---
13
+ # Use the 'now' object and 'get_season' function from the utils module
14
+ now = utils.now
15
+ GROUPED_QUESTIONS = {
16
+ "Question 1: Temporal Orientation": {
17
+ "What year is this?": {"answer": str(now.year), "instruction": "Score 1 point for the correct year."},
18
+ "What season is this in Northern Hemisphere?": {"answer": utils.get_season(now.month), "instruction": "Examples: Summer, Fall, Winter, Spring"},
19
+ "What month is this?": {"answer": now.strftime("%B").lower(), "instruction": "Examples: january, february, ..."},
20
+ "What is the day of today's date?": {"answer": str(now.day), "instruction": "Examples: Range from 1 to 31"},
21
+ "What day of the week is this?": {"answer": now.strftime("%A").lower(), "instruction": "Examples: monday, tuesday, ..."}
22
+ },
23
+ "Question 2: Spatial Orientation": {
24
+ "What country are we in?": {"answer": "united states"}, "What state are we in?": {"answer": "connecticut"},
25
+ "What city or town are we in?": {"answer": "greenwich"}, "What is the street address / name of building?": {"answer": "123 main street"},
26
+ "What room or floor are we in?": {"answer": "living room"},
27
+ },
28
+ "Question 3: Memory Registration": {
29
+ ": I am going to name three words. Repeat these three words: Ball Car Man": {
30
+ "answer": "ball car man",
31
+ "instruction": "Say the words clearly at one per second. After response, say 'Keep those words in mind. I will ask for them again.'",
32
+ "max_points": 3
33
+ }
34
+ },
35
+ "Question 4: Attention": {
36
+ "Count backward from 100 substracting by sevens": {
37
+ "answer": "93 86 79 72 65",
38
+ "instruction": "Stop after five subtractions. Score one point for each correct number.",
39
+ "max_points": 5
40
+ }
41
+ },
42
+ "Question 5: Delayed Recall": {"What were the three words I asked you to remember?": {"answer": "ball car man", "max_points": 3}},
43
+ "Question 6: Naming Communication": {
44
+ "I am going to show you the first object and I would like you to name it": {"answer": "watch|wristwatch", "instruction": "Show the patient a watch.", "max_points": 1},
45
+ "I am going to show you the second object and I would like you to name it": {"answer": "pencil", "instruction": "Show the patient a pencil.", "max_points": 1}
46
+ },
47
+ "Question 7: Sentence Repetition": {"I would like you to repeat a phrase after me: No ifs, ands, or buts.": {"answer": "no ifs, ands, or buts", "max_points": 1}},
48
+ "Question 8: Praxis 3-Stage Movement": {
49
+ "Take this paper in your non-dominant hand, fold the paper in half once with both hands and put the paper down on the floor.": {
50
+ "answer": "A numeric value from 0 to 3 representing tasks completed.",
51
+ "instruction": "Input how many tasks were completed (0 to 3).", "max_points": 3
52
+ }
53
+ },
54
+ "Question 9: Reading on CLOSE YOUR EYES": {"Read the CAPITALIZED words on this question and then do what it says": {"answer": "yes", "instruction": "Input 'yes' if eyes are closed; else, 'no'.", "max_points": 1}},
55
+ "Question 10: Writing Communication": {"Write any complete sentence here or on a piece of paper": {"answer": "A sentence containing at least one noun and one verb.", "max_points": 1}},
56
+ "Question 11: Visuoconstruction": {
57
+ "Please draw a copy of this picture": {
58
+ "answer": "4", "instruction": "Show them a drawing of two overlapping pentagons. Ask them to draw a copy.", "max_points": 1
59
+ }
60
+ }
61
+ }
62
+
63
+ # --- Derived Data Structures (for the UI to use) ---
64
+ STRUCTURED_QUESTIONS = []
65
+ main_num = 1
66
+ for section, questions in GROUPED_QUESTIONS.items():
67
+ main_cat_name = section.split(":", 1)[1].strip() if ":" in section else section
68
+ sub_q_idx = 0
69
+ for question, data in questions.items():
70
+ STRUCTURED_QUESTIONS.append({
71
+ "main_cat": main_cat_name, "main_num": main_num, "sub_letter": chr(ord('a') + sub_q_idx),
72
+ "question": question, "answer": data["answer"], "instruction": data.get("instruction", ""),
73
+ "max_points": data.get("max_points", 1)
74
+ })
75
+ sub_q_idx += 1
76
+ main_num += 1
77
+
78
+ TOTAL_QUESTIONS = len(STRUCTURED_QUESTIONS)
79
+ QUESTION_CHOICES = [f"Q{q['main_num']}{q['sub_letter']}: {q['question']}" for q in STRUCTURED_QUESTIONS]
80
+ DRAWING_Q_INDEX = next((i for i, q in enumerate(STRUCTURED_QUESTIONS) if "draw a copy" in q["question"]), -1)
81
+
82
+
83
+ # --- MMSE Specific Audio Handling ---
84
+ AUDIO_FILE_MAP = {}
85
+ def pregenerate_audio():
86
+ """Pre-generates all TTS audio at startup to avoid rate-limiting."""
87
+ print("Pre-generating MMSE TTS audio...")
88
+ for i, q_data in enumerate(STRUCTURED_QUESTIONS):
89
+ try:
90
+ tts = gTTS(q_data['question'].strip())
91
+ filepath = f"/tmp/question_{i}.mp3"
92
+ tts.save(filepath)
93
+ AUDIO_FILE_MAP[i] = filepath
94
+ time.sleep(0.5) # Pause to avoid rate-limiting
95
+ except Exception as e:
96
+ print(f"Warning: Could not pre-generate audio for question {i}: {e}")
97
+ AUDIO_FILE_MAP[i] = None
98
+ print("MMSE TTS audio pre-generation complete.")
99
+
100
+ def speak_question(current_index):
101
+ """Returns the file path for the pre-generated audio of the current question."""
102
+ return AUDIO_FILE_MAP.get(current_index)
103
+
104
+
105
+ # --- MMSE Specific Scoring Functions ---
106
+ def score_sevens_response(cleaned_user_input):
107
+ """Scores the sevens question from cleaned, space-separated numbers."""
108
+ correct_numbers = {"93", "86", "79", "72", "65"}
109
+ user_numbers = set((cleaned_user_input or "").split())
110
+ return len(correct_numbers.intersection(user_numbers))
111
+
112
+ def score_three_words_response(cleaned_user_input):
113
+ """Scores the three words question from cleaned text."""
114
+ correct_words = {"ball", "car", "man"}
115
+ user_words = set((cleaned_user_input or "").split())
116
+ return len(correct_words.intersection(user_words))
117
+
118
+
119
+ # --- Main Evaluation Logic ---
120
+ def evaluate(answers_list, user_drawing_path):
121
+ """
122
+ Evaluates all MMSE answers and returns the results.
123
+ This function is now UI-agnostic. It returns data, not UI components.
124
+ """
125
+ total_score, total_possible_score, results = 0, 0, []
126
+
127
+ for i, q_data in enumerate(STRUCTURED_QUESTIONS):
128
+ user_answer = answers_list[i]
129
+ point = 0
130
+ normalized_answer = utils.normalize_numeric_words(user_answer)
131
+
132
+ # Routing logic for different scoring types
133
+ if i == DRAWING_Q_INDEX:
134
+ try:
135
+ expected_sides = int(q_data["answer"])
136
+ except (ValueError, TypeError):
137
+ expected_sides = 0
138
+ point, sides_detected = utils.score_drawing(user_drawing_path, expected_sides)
139
+ if sides_detected > 0:
140
+ user_answer = f"[{sides_detected}-sided shape detected]"
141
+ elif user_drawing_path and os.path.exists(user_drawing_path):
142
+ user_answer = "[Image uploaded, but no clear shape found]"
143
+ else:
144
+ user_answer = "[No image uploaded]"
145
+
146
+ elif "Write any complete sentence" in q_data["question"]:
147
+ point = utils.score_sentence_structure(user_answer)
148
+ elif "substracting by sevens" in q_data["question"]:
149
+ point = score_sevens_response(utils.clean_text_answer(normalized_answer))
150
+ elif "three words" in q_data["question"]:
151
+ point = score_three_words_response(utils.clean_text_answer(user_answer))
152
+ elif "day of today's date" in q_data["question"]:
153
+ normalized_day = utils.normalize_date_answer(user_answer)
154
+ point = 1 if normalized_day == str(now.day) else 0
155
+ elif "Take this paper" in q_data["question"]:
156
+ point = 0
157
+ try:
158
+ numeric_score = int(utils.clean_numeric_answer(normalized_answer))
159
+ point = min(numeric_score, q_data["max_points"])
160
+ except (ValueError, TypeError):
161
+ point = 0
162
+ else:
163
+ point = utils.score_keyword_match(q_data["answer"], utils.clean_text_answer(normalized_answer))
164
+ if point == 1:
165
+ point = q_data["max_points"]
166
+
167
+ result_string = (f"Q{q_data['main_num']}{q_data['sub_letter']}: {q_data['question']}\n"
168
+ f" - Score: {point} / {q_data['max_points']} | Your Answer: '{user_answer}' | Expected: '{q_data['answer']}'")
169
+ results.append(result_string)
170
+ total_score += point
171
+ total_possible_score += q_data["max_points"]
172
+
173
+ # Return pure data
174
+ return "\n\n".join(results), f"{total_score} / {total_possible_score}"