diff --git a/src/quant_strategy_plugins/ai_audit.py b/src/quant_strategy_plugins/ai_audit.py index 588d73c..4e6d412 100644 --- a/src/quant_strategy_plugins/ai_audit.py +++ b/src/quant_strategy_plugins/ai_audit.py @@ -2,6 +2,7 @@ import json import logging +import math import os import re from collections.abc import Callable, Mapping, Sequence @@ -444,6 +445,8 @@ def _as_confidence(value: Any) -> float | None: confidence = float(value) except (TypeError, ValueError): return None + if not math.isfinite(confidence): + return None if confidence < 0.0: return 0.0 if confidence > 1.0: @@ -699,7 +702,7 @@ def _run_ai_audit( attempts.append({**endpoint.report(), "status": status}) # Advisory content is display-only; it must not mutate feedback state. - if not advisory: + if not advisory and audit_response.get("confidence") is not None: _report_shadow_disagreement( audit_kind=audit_kind, ai_verdict=audit_response.get("verdict", ""), diff --git a/tests/test_ai_audit.py b/tests/test_ai_audit.py index 72e9576..7ce448e 100644 --- a/tests/test_ai_audit.py +++ b/tests/test_ai_audit.py @@ -1,5 +1,6 @@ import sys import types +import json import pytest @@ -15,6 +16,31 @@ ) +@pytest.mark.parametrize("confidence", ["nan", "NaN", "inf", "-inf", float("nan"), float("inf"), -float("inf")]) +@pytest.mark.parametrize("entry", [ai_audit.run_crisis_ai_audit, ai_audit.run_taco_ai_audit]) +def test_nonfinite_confidence_remains_unknown_and_does_not_write_feedback(monkeypatch, confidence, entry): + monkeypatch.setenv("CODEX_AUDIT_SERVICE_URL", "https://gateway.invalid") + monkeypatch.setattr(ai_audit, "build_ai_audit_endpoints", lambda **_: ( + ai_audit.AiAuditEndpoint("primary", "", model="synthetic-model"), + )) + monkeypatch.setattr(ai_audit, "_complete_with_endpoint", lambda *_: ( + json.dumps({"verdict": "agree", "confidence": confidence}), False, + )) + feedback = [] + monkeypatch.setattr(ai_audit, "_report_shadow_disagreement", lambda **fields: feedback.append(fields)) + payload = entry({"canonical_route": "no_action"}, enabled=True) + assert payload["confidence"] is None + assert feedback == [] + assert payload["final_route_unchanged"] is True + assert payload["execution_controls"]["broker_order_allowed"] is False + json.dumps(payload, allow_nan=False) + + +@pytest.mark.parametrize("value,expected", [(None, None), ("invalid", None), (0.0, 0.0), ("0.8", 0.8), (1.0, 1.0)]) +def test_confidence_finite_and_missing_values_keep_existing_semantics(value, expected): + assert ai_audit._as_confidence(value) == expected + + def _clear_ai_audit_env(monkeypatch) -> None: for key in ( "QSP_STRATEGY_PLUGIN_AI_AUDIT_API_KEY",