DocDoeAI / tests /test_answer_correction.py
asnannp's picture
Deploy backend cd4237ff: support routes + rate limit + exam_date nullable + upload 413 fix
7c6ffa6
Raw
History Blame Contribute Delete
6.34 kB
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"]