feat(schemas): T30 add Pydantic statistics schemas

Add comprehensive Pydantic schemas for statistics management:
- UsageStatsCreate: input validation for creating usage stats
- UsageStatsResponse: orm_mode response schema
- StatsSummary: aggregated statistics with totals and averages
- StatsByModel: per-model breakdown with percentages
- StatsByDate: daily usage aggregation
- DashboardResponse: complete dashboard data structure

All schemas use Decimal for cost precision and proper validation.

Test: 16 unit tests, 100% coverage on stats.py
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-07 15:04:49 +02:00
parent 761ef793a8
commit 0df1638da8
5 changed files with 1230 additions and 3 deletions

View File

@@ -12,6 +12,14 @@ from openrouter_monitor.schemas.auth import (
UserRegister,
UserResponse,
)
from openrouter_monitor.schemas.stats import (
DashboardResponse,
StatsByDate,
StatsByModel,
StatsSummary,
UsageStatsCreate,
UsageStatsResponse,
)
__all__ = [
"UserRegister",
@@ -23,4 +31,10 @@ __all__ = [
"ApiKeyUpdate",
"ApiKeyResponse",
"ApiKeyListResponse",
"UsageStatsCreate",
"UsageStatsResponse",
"StatsSummary",
"StatsByModel",
"StatsByDate",
"DashboardResponse",
]

View File

@@ -0,0 +1,279 @@
"""Statistics Pydantic schemas for OpenRouter API Key Monitor.
T30: Pydantic schemas for statistics management.
"""
import datetime
from decimal import Decimal
from typing import List
from pydantic import BaseModel, ConfigDict, Field
class UsageStatsCreate(BaseModel):
"""Schema for creating usage statistics.
Attributes:
api_key_id: Foreign key to api_keys table
date: Date of the statistics
model: AI model name
requests_count: Number of requests (default 0)
tokens_input: Number of input tokens (default 0)
tokens_output: Number of output tokens (default 0)
cost: Cost in USD (default 0)
"""
api_key_id: int = Field(
...,
description="Foreign key to api_keys table",
examples=[1]
)
date: datetime.date = Field(
...,
description="Date of the statistics",
examples=["2024-01-15"]
)
model: str = Field(
...,
min_length=1,
max_length=100,
description="AI model name",
examples=["gpt-4"]
)
requests_count: int = Field(
default=0,
ge=0,
description="Number of requests",
examples=[100]
)
tokens_input: int = Field(
default=0,
ge=0,
description="Number of input tokens",
examples=[5000]
)
tokens_output: int = Field(
default=0,
ge=0,
description="Number of output tokens",
examples=[3000]
)
cost: Decimal = Field(
default=Decimal("0"),
ge=0,
description="Cost in USD",
examples=["0.123456"]
)
class UsageStatsResponse(BaseModel):
"""Schema for usage statistics response (returned to client).
Attributes:
id: Primary key
api_key_id: Foreign key to api_keys table
date: Date of the statistics
model: AI model name
requests_count: Number of requests
tokens_input: Number of input tokens
tokens_output: Number of output tokens
cost: Cost in USD
created_at: Timestamp when record was created
"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(
...,
description="Primary key",
examples=[1]
)
api_key_id: int = Field(
...,
description="Foreign key to api_keys table",
examples=[2]
)
date: datetime.date = Field(
...,
description="Date of the statistics",
examples=["2024-01-15"]
)
model: str = Field(
...,
description="AI model name",
examples=["gpt-4"]
)
requests_count: int = Field(
...,
description="Number of requests",
examples=[100]
)
tokens_input: int = Field(
...,
description="Number of input tokens",
examples=[5000]
)
tokens_output: int = Field(
...,
description="Number of output tokens",
examples=[3000]
)
cost: Decimal = Field(
...,
description="Cost in USD",
examples=["0.123456"]
)
created_at: datetime.datetime = Field(
...,
description="Timestamp when record was created",
examples=["2024-01-15T12:00:00"]
)
class StatsSummary(BaseModel):
"""Schema for aggregated statistics summary.
Attributes:
total_requests: Total number of requests
total_cost: Total cost in USD
total_tokens_input: Total input tokens
total_tokens_output: Total output tokens
avg_cost_per_request: Average cost per request
period_days: Number of days in the period
"""
total_requests: int = Field(
...,
ge=0,
description="Total number of requests",
examples=[1000]
)
total_cost: Decimal = Field(
...,
ge=0,
description="Total cost in USD",
examples=["5.678901"]
)
total_tokens_input: int = Field(
default=0,
ge=0,
description="Total input tokens",
examples=[50000]
)
total_tokens_output: int = Field(
default=0,
ge=0,
description="Total output tokens",
examples=[30000]
)
avg_cost_per_request: Decimal = Field(
default=Decimal("0"),
ge=0,
description="Average cost per request",
examples=["0.005679"]
)
period_days: int = Field(
default=0,
ge=0,
description="Number of days in the period",
examples=[30]
)
class StatsByModel(BaseModel):
"""Schema for statistics grouped by model.
Attributes:
model: AI model name
requests_count: Number of requests for this model
cost: Total cost for this model
percentage_requests: Percentage of total requests
percentage_cost: Percentage of total cost
"""
model: str = Field(
...,
description="AI model name",
examples=["gpt-4"]
)
requests_count: int = Field(
...,
ge=0,
description="Number of requests for this model",
examples=[500]
)
cost: Decimal = Field(
...,
ge=0,
description="Total cost for this model",
examples=["3.456789"]
)
percentage_requests: float = Field(
default=0.0,
ge=0,
le=100,
description="Percentage of total requests",
examples=[50.0]
)
percentage_cost: float = Field(
default=0.0,
ge=0,
le=100,
description="Percentage of total cost",
examples=[60.5]
)
class StatsByDate(BaseModel):
"""Schema for statistics grouped by date.
Attributes:
date: Date of the statistics
requests_count: Number of requests on this date
cost: Total cost on this date
"""
date: datetime.date = Field(
...,
description="Date of the statistics",
examples=["2024-01-15"]
)
requests_count: int = Field(
...,
ge=0,
description="Number of requests on this date",
examples=[100]
)
cost: Decimal = Field(
...,
ge=0,
description="Total cost on this date",
examples=["0.567890"]
)
class DashboardResponse(BaseModel):
"""Schema for complete dashboard response.
Attributes:
summary: Aggregated statistics summary
by_model: Statistics grouped by model
by_date: Statistics grouped by date
top_models: List of top used models
"""
summary: StatsSummary = Field(
...,
description="Aggregated statistics summary"
)
by_model: List[StatsByModel] = Field(
...,
description="Statistics grouped by model"
)
by_date: List[StatsByDate] = Field(
...,
description="Statistics grouped by date"
)
top_models: List[str] = Field(
default_factory=list,
description="List of top used models"
)