feat(agents): pydantic output schemas (Pick/AgentOutput/RunManifest) + tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DvdRZkyCy9NJpHYDdPqmS
This commit is contained in:
Aryan SinghandClaude Opus 4.8 committed 2026-07-01 16:29:29 -05:00
1 parent 031a051687
commit f41cf75d8a
3 files changed
+207

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
"""Phase-0 gate: the output contract validates and rejects garbage."""
from __future__ import annotations
from datetime import datetime, timezone
import pytest
from pydantic import ValidationError
from agents.schemas import AgentOutput, AgentStatus, Direction, Pick, RunManifest
def _pick(**kw) -> Pick:
base = dict(ticker="nvda", conviction=0.8, thesis="momentum", requested_weight_pct=0.05)
base.update(kw)
return Pick(**base)
def test_pick_valid_and_ticker_uppercased():
p = _pick()
assert p.ticker == "NVDA"
assert p.direction == Direction.LONG
def test_conviction_out_of_range_rejected():
with pytest.raises(ValidationError):
_pick(conviction=1.5)
def test_weight_must_be_positive_fraction():
with pytest.raises(ValidationError):
_pick(requested_weight_pct=0.0)
with pytest.raises(ValidationError):
_pick(requested_weight_pct=1.5)
def test_stop_take_pct_must_be_fraction():
with pytest.raises(ValidationError):
_pick(stop_loss_pct=7.0) # 700% — clearly a mistake
assert _pick(stop_loss_pct=0.07).stop_loss_pct == 0.07
def test_long_absolute_levels_ordering_enforced():
with pytest.raises(ValidationError):
_pick(entry_type="limit", limit_price=100.0, stop_price=105.0) # stop above entry
with pytest.raises(ValidationError):
_pick(entry_type="limit", limit_price=100.0, take_price=95.0) # take below entry
ok = _pick(entry_type="limit", limit_price=100.0, stop_price=93.0, take_price=120.0)
assert ok.limit_price == 100.0
def test_agent_output_roundtrip():
out = AgentOutput(
agent_name="Nova",
persona_id="nova",
started_at=datetime.now(timezone.utc),
status=AgentStatus.COMPLETE,
picks=[_pick(), _pick(ticker="amd", conviction=0.6)],
)
restored = AgentOutput.model_validate_json(out.model_dump_json())
assert restored.agent_name == "Nova"
assert [p.ticker for p in restored.picks] == ["NVDA", "AMD"]
def test_run_manifest_minimal():
m = RunManifest(
run_id="r1",
trade_date="2026-07-01",
model="gpt-oss-20b",
started_at=datetime.now(timezone.utc),
)
assert m.status == "running"
assert m.agents == []