File size: 6,343 Bytes
7c6ffa6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

from app.services.answer_correction import (
    SOURCE_ANSWER_NOT_FOUND,
    UNKNOWN_MARKS_PROMPT,
    build_answer_correction_output,
)


def test_physics_derivation_correction_marks_missing_change_in_velocity() -> None:
    output = build_answer_correction_output(
        question="Derive v = u + at.",
        student_answer="Acceleration is velocity by time, so v = u + at.",
        subject="Physics",
        board="Kerala HSE",
        class_level="+2",
        marks=5,
    )

    joined = " ".join(output["marks_lost"]).lower()
    assert "acceleration definition is incomplete" in joined
    assert "change in velocity" in joined
    assert "a = (v - u) / t" in output["corrected_board_answer"]
    assert output["score"].startswith("Estimated score:")


def test_physics_formula_with_spaces_detected() -> None:
    output = build_answer_correction_output(
        question="Derive v = u + at.",
        student_answer="We know a = change in velocity / time. So a = (v-u)/t. Therefore v = u + at.",
        subject="Physics",
        marks=5,
    )

    corrected = output["corrected_board_answer"].lower()
    assert "v = u + at" in corrected
    assert "estimated score" in output["score"].lower()
    lost_joined = " ".join(output["marks_lost"]).lower()
    assert "symbol" not in lost_joined or "define" not in lost_joined


def test_chemistry_numerical_co2_11g_correction() -> None:
    output = build_answer_correction_output(
        question="Calculate number of moles in 11 g of CO2. Molar mass of CO2 = 44 g/mol.",
        student_answer="Moles = mass x molar mass = 11 x 44 = 484 mol.",
        subject="Chemistry",
        marks=3,
    )

    corrected = output["corrected_board_answer"].lower()
    assert "11" in corrected
    assert "44" in corrected
    assert "0.25" in corrected
    assert "co2" in corrected
    assert "mol" in corrected

    lost_joined = " ".join(output["marks_lost"]).lower()
    assert "wrong formula" in lost_joined or "multiplication" in lost_joined or "division" in lost_joined

    score = output["score"].lower()
    assert "estimated" in score
    assert "/3" in score or "/ 3" in score


def test_chemistry_numerical_water_18g_still_works() -> None:
    output = build_answer_correction_output(
        question="Calculate the number of moles in 18 g of water.",
        student_answer="Water has 1 mole.",
        subject="Chemistry",
        marks=3,
    )

    missing = " ".join(output["missing_keywords"]).lower()
    corrected = output["corrected_board_answer"].lower()
    assert "given" in missing
    assert "formula" in corrected
    assert "substitution" in corrected
    assert "mol" in corrected
    assert "18" in corrected
    assert "h2o" in corrected or "water" in corrected


def test_maths_proof_correction_without_given_to_prove() -> None:
    output = build_answer_correction_output(
        question="Prove sin^2 x + cos^2 x = 1.",
        student_answer="It is a formula, so sin^2 x + cos^2 x = 1.",
        subject="Mathematics",
        marks=3,
    )

    corrected = output["corrected_board_answer"].lower()
    assert "given" in corrected
    assert "to prove" in corrected
    assert "pythagoras theorem" in corrected

    lost_joined = " ".join(output["marks_lost"]).lower()
    score = output["score"].lower()
    assert "estimated" in score
    assert "/3" in score or "/ 3" in score
    assert len(output["marks_lost"]) >= 1


def test_maths_proof_with_full_steps_gets_fewer_marks_lost() -> None:
    output = build_answer_correction_output(
        question="Prove sin^2 x + cos^2 x = 1.",
        student_answer=(
            "Let opposite side = a, adjacent side = b, hypotenuse = c. "
            "sin x = a/c, cos x = b/c. sin^2 x + cos^2 x = a^2/c^2 + b^2/c^2 "
            "= (a^2 + b^2)/c^2 = c^2/c^2 = 1. Hence proved."
        ),
        subject="Mathematics",
        marks=3,
    )

    lost_joined = " ".join(output["marks_lost"]).lower()
    assert "too short" not in lost_joined


def test_source_mismatch_does_not_pretend_boolean_logic_supports_physics(client) -> None:
    source = client.post(
        "/sources/text",
        json={
            "title": "Number Systems and Boolean Logic",
            "text": (
                "Binary number system. Decimal to binary conversion. Boolean logic. "
                "AND, OR, NOT gates. Truth table examples. Three simple exam questions."
            ),
            "source_type": "notes",
            "subject": "Computer Science",
            "chapter": "Boolean Logic",
        },
    )
    assert source.status_code == 201
    source_id = source.json()["id"]

    response = client.post(
        "/generate/answer-correction",
        json={
            "topic": "Derive v = u + at.",
            "subject": "Physics",
            "source_id": source_id,
            "source_ids": [source_id],
            "language_preference": "English",
            "options": {
                "question": "Derive v = u + at.",
                "student_answer": "Acceleration is velocity by time, so v = u + at.",
                "marks": "5",
            },
        },
    )

    assert response.status_code == 200
    output = response.json()["output"]
    assert output["source_truth"] == SOURCE_ANSWER_NOT_FOUND
    assert output["corrected_board_answer"] == SOURCE_ANSWER_NOT_FOUND
    assert output["source_evidence"] == []


def test_unknown_marks_asks_or_states_assumed_value() -> None:
    output = build_answer_correction_output(
        question="Derive v = u + at.",
        student_answer="Acceleration is velocity by time, so v = u + at.",
        subject="Physics",
        marks="not sure",
    )

    assert "Assuming this is a 5-mark answer" in output["mark_scheme_assumption"]
    assert UNKNOWN_MARKS_PROMPT in output["mark_scheme_assumption"]
    assert output["score"].startswith("Estimated score:")


def test_estimated_rubric_label_appears_when_no_official_scheme() -> None:
    output = build_answer_correction_output(
        question="Derive v = u + at.",
        student_answer="Acceleration is velocity by time.",
        subject="Physics",
        marks=5,
    )
    assert output["official_scheme_available"] is False
    assert "estimated" in output["score"].lower()
    assert "Assuming" in output["mark_scheme_assumption"]