File size: 8,814 Bytes
8f6863a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cd802c
8f6863a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# MMSE.py
# Contains all data, scoring logic, and setup specific to the
# Mini-Mental State Examination (MMSE).

import os
import time
from gtts import gTTS

# Import shared utilities
import utils

# --- MMSE Specific Data ---
# Use the 'now' object and 'get_season' function from the utils module
now = utils.now
GROUPED_QUESTIONS = {
    "Question 1: Temporal Orientation": {
        "What year is this?": {"answer": str(now.year), "instruction": "Score 1 point for the correct year."},
        "What season is this in Northern Hemisphere?": {"answer": utils.get_season(now.month), "instruction": "Examples: Summer, Fall, Winter, Spring"},
        "What month is this?": {"answer": now.strftime("%B").lower(), "instruction": "Examples:  january, february, ..."},
        "What is the day of today's date?": {"answer": str(now.day), "instruction": "Examples: Range from 1 to 31"},
        "What day of the week is this?": {"answer": now.strftime("%A").lower(), "instruction": "Examples: monday, tuesday, ..."}
    },
    "Question 2: Spatial Orientation": {
        "What country are we in?": {"answer": "united states"}, "What state are we in?": {"answer": "connecticut"},
        "What city or town are we in?": {"answer": "greenwich"}, "What is the street address / name of building?": {"answer": "123 main street"},
        "What room or floor are we in?": {"answer": "living room"},
    },
    "Question 3: Memory Registration": {
        ": I am going to name three words. Repeat these three words: Ball Car Man": {
            "answer": "ball car man",
            "instruction": "Say the words clearly at one per second. After response, say 'Keep those words in mind. I will ask for them again.'",
            "max_points": 3
        }
    },
    "Question 4: Attention": {
        "Count backward from 100 substracting by sevens": {
            "answer": "93 86 79 72 65",
            "instruction": "Stop after five subtractions. Score one point for each correct number.",
            "max_points": 5
        }
    },
    "Question 5: Delayed Recall": {"What were the three words I asked you to remember?": {"answer": "ball car man", "max_points": 3}},
    "Question 6: Naming Communication": {
        "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},
        "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}
    },
    "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}},
    "Question 8: Praxis 3-Stage Movement": {
        "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.": {
            "answer": "A numeric value from 0 to 3 representing tasks completed.",
            "instruction": "Input how many tasks were completed (0 to 3).", "max_points": 3
        }
    },
    "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}},
    "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}},
    "Question 11: Visuoconstruction": {
       "Please draw a copy of this picture": {
            "answer": "4", "instruction": "Show them a drawing of two overlapping pentagons. Ask them to draw a copy.", "max_points": 1
        }
    }
}

# --- Derived Data Structures (for the UI to use) ---
STRUCTURED_QUESTIONS = []
main_num = 1
for section, questions in GROUPED_QUESTIONS.items():
    main_cat_name = section.split(":", 1)[1].strip() if ":" in section else section
    sub_q_idx = 0
    for question, data in questions.items():
        STRUCTURED_QUESTIONS.append({
            "main_cat": main_cat_name, "main_num": main_num, "sub_letter": chr(ord('a') + sub_q_idx),
            "question": question, "answer": data["answer"], "instruction": data.get("instruction", ""),
            "max_points": data.get("max_points", 1)
        })
        sub_q_idx += 1
    main_num += 1

TOTAL_QUESTIONS = len(STRUCTURED_QUESTIONS)
QUESTION_CHOICES = [f"Q{q['main_num']}{q['sub_letter']}: {q['question']}" for q in STRUCTURED_QUESTIONS]
DRAWING_Q_INDEX = next((i for i, q in enumerate(STRUCTURED_QUESTIONS) if "draw a copy" in q["question"]), -1)


# --- MMSE Specific Audio Handling ---
AUDIO_FILE_MAP = {}
def pregenerate_audio():
    """Pre-generates all TTS audio at startup to avoid rate-limiting."""
    print("Pre-generating MMSE TTS audio...")
    for i, q_data in enumerate(STRUCTURED_QUESTIONS):
        try:
            tts = gTTS(q_data['question'].strip())
            filepath = f"/tmp/question_{i}.mp3"
            tts.save(filepath)
            AUDIO_FILE_MAP[i] = filepath
            time.sleep(0.5) # Pause to avoid rate-limiting
        except Exception as e:
            print(f"Warning: Could not pre-generate audio for question {i}: {e}")
            AUDIO_FILE_MAP[i] = None
    print("MMSE TTS audio pre-generation complete.")

def speak_question(current_index):
    """Returns the file path for the pre-generated audio of the current question."""
    return AUDIO_FILE_MAP.get(current_index)


# --- MMSE Specific Scoring Functions ---
def score_sevens_response(cleaned_user_input):
    """Scores the sevens question from cleaned, space-separated numbers."""
    correct_numbers = {"93", "86", "79", "72", "65"}
    user_numbers = set((cleaned_user_input or "").split())
    return len(correct_numbers.intersection(user_numbers))

def score_three_words_response(cleaned_user_input):
    """Scores the three words question from cleaned text."""
    correct_words = {"ball", "car", "man"}
    user_words = set((cleaned_user_input or "").split())
    return len(correct_words.intersection(user_words))


# --- Main Evaluation Logic ---
def evaluate_MMSE(answers_list, user_drawing_path):
    """
    Evaluates all MMSE answers and returns the results.
    This function is now UI-agnostic. It returns data, not UI components.
    """
    total_score, total_possible_score, results = 0, 0, []

    for i, q_data in enumerate(STRUCTURED_QUESTIONS):
        user_answer = answers_list[i]
        point = 0
        normalized_answer = utils.normalize_numeric_words(user_answer)

        # Routing logic for different scoring types
        if i == DRAWING_Q_INDEX:
            try:
                expected_sides = int(q_data["answer"])
            except (ValueError, TypeError):
                expected_sides = 0
            point, sides_detected = utils.score_drawing(user_drawing_path, expected_sides)
            if sides_detected > 0:
                user_answer = f"[{sides_detected}-sided shape detected]"
            elif user_drawing_path and os.path.exists(user_drawing_path):
                user_answer = "[Image uploaded, but no clear shape found]"
            else:
                user_answer = "[No image uploaded]"

        elif "Write any complete sentence" in q_data["question"]:
            point = utils.score_sentence_structure(user_answer)
        elif "substracting by sevens" in q_data["question"]:
            point = score_sevens_response(utils.clean_text_answer(normalized_answer))
        elif "three words" in q_data["question"]:
            point = score_three_words_response(utils.clean_text_answer(user_answer))
        elif "day of today's date" in q_data["question"]:
            normalized_day = utils.normalize_date_answer(user_answer)
            point = 1 if normalized_day == str(now.day) else 0
        elif "Take this paper" in q_data["question"]:
            point = 0
            try:
                numeric_score = int(utils.clean_numeric_answer(normalized_answer))
                point = min(numeric_score, q_data["max_points"])
            except (ValueError, TypeError):
                point = 0
        else:
            point = utils.score_keyword_match(q_data["answer"], utils.clean_text_answer(normalized_answer))
            if point == 1:
                point = q_data["max_points"]

        result_string = (f"Q{q_data['main_num']}{q_data['sub_letter']}: {q_data['question']}\n"
                         f"  - Score: {point} / {q_data['max_points']} | Your Answer: '{user_answer}' | Expected: '{q_data['answer']}'")
        results.append(result_string)
        total_score += point
        total_possible_score += q_data["max_points"]

    # Return pure data
    return "\n\n".join(results), f"{total_score} / {total_possible_score}"