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, )