fix: deduplicate missing-key warnings and normalize match_details
Some checks failed
CI / test (pull_request) Has been cancelled

Addresses second round of PR #102 review:
- _warn_missing_key(): logs each missing key only once per engine instance
  to prevent log spam in high-frequency trading loops
- _build_match_details(): uses _safe_float() normalized values instead of
  raw market_data to ensure consistent float types in logging/analysis
- Test: verify warning fires exactly once across repeated calls
- Test: verify match_details contains normalized float values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
agentson
2026-02-08 20:41:20 +09:00
parent d2fc829380
commit e711d6702a
2 changed files with 35 additions and 13 deletions

View File

@@ -174,14 +174,18 @@ class TestEvaluateCondition:
# Should return False (invalid types → None → False), never raise
assert not engine.evaluate_condition(cond, data)
def test_missing_key_logs_warning(self, engine: ScenarioEngine, caplog) -> None:
"""Missing market_data key should log a warning."""
def test_missing_key_logs_warning_once(self, caplog) -> None:
"""Missing key warning should fire only once per key per engine instance."""
import logging
eng = ScenarioEngine()
cond = StockCondition(rsi_below=30.0)
with caplog.at_level(logging.WARNING):
engine.evaluate_condition(cond, {})
assert "key missing from market_data" in caplog.text
eng.evaluate_condition(cond, {})
eng.evaluate_condition(cond, {})
eng.evaluate_condition(cond, {})
# Warning should appear exactly once despite 3 calls
assert caplog.text.count("'rsi' but key missing") == 1
# ---------------------------------------------------------------------------
@@ -427,3 +431,12 @@ class TestEvaluate:
pb = _playbook()
result = engine.evaluate(pb, "005930", {"rsi": 25.0}, {})
assert result.stock_code == "005930"
def test_match_details_normalized(self, engine: ScenarioEngine) -> None:
"""match_details should contain _safe_float normalized values, not raw."""
pb = _playbook(scenarios=[_scenario(rsi_below=30.0)])
# Pass string value — should be normalized to float in match_details
result = engine.evaluate(pb, "005930", {"rsi": "25.0"}, {})
assert result.action == ScenarioAction.BUY
assert result.match_details["rsi"] == 25.0
assert isinstance(result.match_details["rsi"], float)