phytoevidence-api / tests /test_numeric_core_v425.py
debpc
Fix numeric scalar validation fail-open
05c7321
Raw
History Blame Contribute Delete
3.55 kB
import asyncio
import sys
import types
# L'environnement local de test peut ne pas contenir FastAPI.
# Le module endpoint utilise uniquement HTTPException ; ce substitut
# permet donc de tester son contrat sans modifier le code de production.
try:
import fastapi # noqa: F401
except ModuleNotFoundError:
fastapi_stub = types.ModuleType("fastapi")
class HTTPException(Exception):
def __init__(
self,
status_code: int,
detail: str,
) -> None:
super().__init__(detail)
self.status_code = status_code
self.detail = detail
fastapi_stub.HTTPException = HTTPException
sys.modules["fastapi"] = fastapi_stub
from endpoints.validate_numeric import post_validate_numeric
from numeric_core import extract, validate_numeric
EVIDENCE = (
"Effect sizes were 1.19 and 0.57. "
"There were 22 studies. "
"The response rate was 20%. "
"The dose was 1.5 mg. "
"The 95% CI was 0.74 to 1.64."
)
def test_decimal_point_with_final_period():
values = extract("Effect size 1.19.")
assert len(values) == 1
assert values[0].kind == "scalar"
assert values[0].lo == 1.19
def test_decimal_comma_with_final_period():
values = extract("Taille d'effet 1,19.")
assert len(values) == 1
assert values[0].kind == "scalar"
assert values[0].lo == 1.19
def test_two_plain_decimals_are_extracted():
values = extract("Values were 1.19 and 0.57.")
assert [item.lo for item in values] == [1.19, 0.57]
def test_french_decimals_match_exactly():
result = validate_numeric(
"Les tailles d'effet étaient 1,19 et 0,57.",
EVIDENCE,
rel_tol=0.0,
)
assert result["has_numbers"] is True
assert result["numeric_ok"] is True
assert result["unparsed_numeric"] is False
def test_modified_decimal_is_rejected_exactly():
result = validate_numeric(
"Effect sizes were 1.20 and 0.57.",
EVIDENCE,
rel_tol=0.0,
)
assert result["numeric_ok"] is False
def test_close_decimal_is_allowed_at_two_percent():
result = validate_numeric(
"Effect sizes were 1.20 and 0.57.",
EVIDENCE,
rel_tol=0.02,
)
assert result["numeric_ok"] is True
def test_large_decimal_difference_is_rejected():
result = validate_numeric(
"Effect sizes were 1.30 and 0.57.",
EVIDENCE,
rel_tol=0.02,
)
assert result["numeric_ok"] is False
def test_specialized_numeric_types_are_preserved():
assert extract("Dose 1,5 mg.")[0].kind == "dose"
assert extract("Response 20,5%.")[0].kind == "percent"
assert extract("95% IC 0,74 à 1,64.")[0].kind == "ci"
def test_contextual_integer_mismatch_is_rejected():
result = validate_numeric(
"There were 23 studies.",
EVIDENCE,
rel_tol=0.0,
)
assert result["numeric_ok"] is False
def test_endpoint_contract_uses_exact_mode():
payload = {
"items": [
{
"id": 1,
"claim": "Effect sizes were 1.19 and 0.57.",
"evidence": EVIDENCE,
},
{
"id": 2,
"claim": "Effect sizes were 1.20 and 0.57.",
"evidence": EVIDENCE,
},
],
"relative_tolerance": 0.0,
}
response = asyncio.run(
post_validate_numeric(payload)
)
assert response["results"][0]["numeric_ok"] is True
assert response["results"][1]["numeric_ok"] is False