bacpilot-backend / app /services /numeric_validator.py
debpc
Wire Wolfram into numeric validation pipeline
512509e
Raw
History Blame Contribute Delete
5.51 kB
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,
)