feat: implement date range support with period metrics in results endpoint

- Replace deprecated `date` parameter with `start_date`/`end_date`
- Return single-date format (detailed) when dates are equal
- Return range format (lightweight with period metrics) when dates differ
- Add period metrics: period_return_pct, annualized_return_pct, calendar_days, trading_days
- Default to last 30 days when no dates provided
- Group results by model for date range queries
- Add comprehensive test coverage for both response formats
- Implement automatic edge trimming for date ranges
- Add 404 error handling for empty result sets
- Include 422 error for deprecated `date` parameter usage
This commit is contained in:
2025-11-07 19:26:06 -05:00
parent 5c95180941
commit 2612b85431
2 changed files with 299 additions and 57 deletions

View File

@@ -1,8 +1,12 @@
"""Tests for results_v2 endpoint date validation."""
import pytest
import json
from datetime import datetime, timedelta
from fastapi.testclient import TestClient
from api.routes.results_v2 import validate_and_resolve_dates
from api.main import create_app
from api.database import Database
def test_validate_no_dates_provided():
@@ -59,3 +63,160 @@ def test_validate_future_date():
with pytest.raises(ValueError, match="Cannot query future dates"):
validate_and_resolve_dates(future, future)
@pytest.fixture
def test_db(tmp_path):
"""Create test database with sample data."""
db_path = str(tmp_path / "test.db")
db = Database(db_path)
# Create a job first (required by foreign key constraint)
db.connection.execute(
"""
INSERT INTO jobs (job_id, config_path, date_range, models, status, created_at)
VALUES (?, ?, ?, ?, ?, datetime('now'))
""",
("test-job-1", "config.json", '["2024-01-16", "2024-01-17"]', '["gpt-4"]', "completed")
)
db.connection.commit()
# Create sample trading days (use dates in the past)
trading_day_id_1 = db.create_trading_day(
job_id="test-job-1",
model="gpt-4",
date="2024-01-16",
starting_cash=10000.0,
starting_portfolio_value=10000.0,
daily_profit=0.0,
daily_return_pct=0.0,
ending_cash=9500.0,
ending_portfolio_value=10100.0,
reasoning_summary="Bought AAPL",
total_actions=1,
session_duration_seconds=45.2,
days_since_last_trading=0
)
db.create_holding(trading_day_id_1, "AAPL", 10)
db.create_action(trading_day_id_1, "buy", "AAPL", 10, 150.0)
trading_day_id_2 = db.create_trading_day(
job_id="test-job-1",
model="gpt-4",
date="2024-01-17",
starting_cash=9500.0,
starting_portfolio_value=10100.0,
daily_profit=100.0,
daily_return_pct=1.0,
ending_cash=9500.0,
ending_portfolio_value=10250.0,
reasoning_summary="Held AAPL",
total_actions=0,
session_duration_seconds=30.0,
days_since_last_trading=1
)
db.create_holding(trading_day_id_2, "AAPL", 10)
return db
def test_get_results_single_date(test_db):
"""Test single date query returns detailed format."""
app = create_app(db_path=test_db.db_path)
app.state.test_mode = True
# Override the database dependency to use our test database
from api.routes.results_v2 import get_database
def override_get_database():
return test_db
app.dependency_overrides[get_database] = override_get_database
client = TestClient(app)
response = client.get("/results?start_date=2024-01-16&end_date=2024-01-16")
assert response.status_code == 200
data = response.json()
assert data["count"] == 1
assert len(data["results"]) == 1
result = data["results"][0]
assert result["date"] == "2024-01-16"
assert result["model"] == "gpt-4"
assert "starting_position" in result
assert "daily_metrics" in result
assert "trades" in result
assert "final_position" in result
def test_get_results_date_range(test_db):
"""Test date range query returns metrics format."""
app = create_app(db_path=test_db.db_path)
app.state.test_mode = True
# Override the database dependency to use our test database
from api.routes.results_v2 import get_database
def override_get_database():
return test_db
app.dependency_overrides[get_database] = override_get_database
client = TestClient(app)
response = client.get("/results?start_date=2024-01-16&end_date=2024-01-17")
assert response.status_code == 200
data = response.json()
assert data["count"] == 1
assert len(data["results"]) == 1
result = data["results"][0]
assert result["model"] == "gpt-4"
assert result["start_date"] == "2024-01-16"
assert result["end_date"] == "2024-01-17"
assert "daily_portfolio_values" in result
assert "period_metrics" in result
# Check daily values
daily_values = result["daily_portfolio_values"]
assert len(daily_values) == 2
assert daily_values[0]["date"] == "2024-01-16"
assert daily_values[0]["portfolio_value"] == 10100.0
assert daily_values[1]["date"] == "2024-01-17"
assert daily_values[1]["portfolio_value"] == 10250.0
# Check period metrics
metrics = result["period_metrics"]
assert metrics["starting_portfolio_value"] == 10000.0
assert metrics["ending_portfolio_value"] == 10250.0
assert metrics["period_return_pct"] == 2.5
assert metrics["calendar_days"] == 2
assert metrics["trading_days"] == 2
def test_get_results_empty_404(test_db):
"""Test 404 when no data matches filters."""
app = create_app(db_path=test_db.db_path)
app.state.test_mode = True
# Override the database dependency to use our test database
from api.routes.results_v2 import get_database
def override_get_database():
return test_db
app.dependency_overrides[get_database] = override_get_database
client = TestClient(app)
response = client.get("/results?start_date=2024-02-01&end_date=2024-02-05")
assert response.status_code == 404
assert "No trading data found" in response.json()["detail"]