File size: 5,505 Bytes
5ffcd9d
 
 
512509e
5ffcd9d
512509e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ffcd9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19ee5f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ffcd9d
 
 
 
19ee5f6
5ffcd9d
19ee5f6
 
 
 
 
 
 
 
5ffcd9d
 
 
 
 
 
 
 
 
19ee5f6
 
 
 
 
 
 
 
5ffcd9d
 
 
512509e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ffcd9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512509e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from sympy import Symbol, diff, simplify, sympify
from sympy.core.sympify import SympifyError

from app.clients.wolfram import WolframAlphaClient, WolframClientError
from app.schemas.numeric import NumericCheckItem, NumericCheckReport
from app.services.cas_arbitration import arbitrate_sympy_wolfram


def _clean_wolfram_plaintext_result(plaintext: str) -> str:
    cleaned = plaintext.strip()

    if "=" in cleaned:
        cleaned = cleaned.split("=", 1)[1].strip()

    return cleaned


def _extract_first_useful_wolfram_text(pods: list[object]) -> str | None:
    for pod in pods:
        title = getattr(pod, "title", "")
        plaintext = getattr(pod, "plaintext", "")

        if not plaintext:
            continue

        normalized_title = title.lower()
        if normalized_title in {"derivative", "result", "alternate forms"}:
            return _clean_wolfram_plaintext_result(str(plaintext))

    if pods:
        plaintext = getattr(pods[0], "plaintext", "")
        if plaintext:
            return _clean_wolfram_plaintext_result(str(plaintext))

    return None


def validate_derivative(
    expression: str,
    expected_derivative: str,
    variable: str = "x",
) -> NumericCheckItem:
    x = Symbol(variable)

    try:
        parsed_expression = sympify(expression)
        parsed_expected = sympify(expected_derivative)
        computed = diff(parsed_expression, x)
        is_valid = simplify(computed - parsed_expected) == 0

        details = (
            f"SymPy calcule d/d{variable}({expression}) = {computed}. "
            f"Résultat attendu : {expected_derivative}."
        )

        if is_valid:
            return NumericCheckItem(
                check_type="derivative",
                expression=expression,
                expected=expected_derivative,
                is_valid=True,
                details=details,
                sympy_status="ok",
                sympy_result=str(computed),
                wolfram_status="not_run",
                wolfram_result=None,
                cas_status="SYMPY_ONLY",
                preferred_engine="sympy",
                confidence_impact="neutral",
                human_review_reason=None,
            )

        return NumericCheckItem(
            check_type="derivative",
            expression=expression,
            expected=expected_derivative,
            is_valid=False,
            details=details,
            sympy_status="ok",
            sympy_result=str(computed),
            wolfram_status="not_run",
            wolfram_result=None,
            cas_status="SYMPY_ONLY",
            preferred_engine="sympy",
            confidence_impact="decrease",
            human_review_reason="sympy_contradicts_expected_derivative",
        )

    except (SympifyError, TypeError, ValueError) as exc:
        return NumericCheckItem(
            check_type="derivative",
            expression=expression,
            expected=expected_derivative,
            is_valid=False,
            details=f"Expression non exploitable par SymPy : {exc}",
            sympy_status="failed",
            sympy_result=None,
            wolfram_status="not_run",
            wolfram_result=None,
            cas_status="NO_CAS_VALIDATION",
            preferred_engine="none",
            confidence_impact="decrease",
            human_review_reason="sympy_unusable_expression",
        )


async def validate_derivative_with_wolfram(
    expression: str,
    expected_derivative: str,
    variable: str = "x",
) -> NumericCheckItem:
    sympy_check = validate_derivative(expression, expected_derivative, variable)

    query = f"derivative of {expression} with respect to {variable}"

    try:
        wolfram_response = await WolframAlphaClient().query(query)
    except WolframClientError:
        return arbitrate_sympy_wolfram(
            sympy_check=sympy_check,
            wolfram_result=None,
            wolfram_success=False,
            affects_score=True,
        )

    wolfram_result = _extract_first_useful_wolfram_text(wolfram_response.pods)

    return arbitrate_sympy_wolfram(
        sympy_check=sympy_check,
        wolfram_result=wolfram_result,
        wolfram_success=wolfram_response.success and bool(wolfram_result),
        affects_score=True,
    )


def run_numeric_checks_for_demo_answer(answer_text: str) -> NumericCheckReport:
    normalized = answer_text.replace(" ", "")

    checks: list[NumericCheckItem] = []

    if "f'(x)=2x" in normalized or "f’(x)=2x" in normalized:
        checks.append(validate_derivative("x**2", "2*x"))

    contradicted = any(not check.is_valid for check in checks)

    return NumericCheckReport(
        checks=checks,
        contradicted_by_numeric_check=contradicted,
        needs_human_review=contradicted,
    )


async def run_numeric_checks_for_demo_answer_with_wolfram(answer_text: str) -> NumericCheckReport:
    normalized = answer_text.replace(" ", "")

    checks: list[NumericCheckItem] = []

    if "f'(x)=2x" in normalized or "f’(x)=2x" in normalized:
        checks.append(await validate_derivative_with_wolfram("x**2", "2*x"))

    contradicted = any(check.cas_status == "DISAGREEMENT" or not check.is_valid for check in checks)
    needs_human_review = any(
        check.cas_status == "DISAGREEMENT" or check.human_review_reason for check in checks
    )

    return NumericCheckReport(
        checks=checks,
        contradicted_by_numeric_check=contradicted,
        needs_human_review=needs_human_review,
    )