5d41ccac1c
Closes the action loop — instead of polling the dashboard, the platform pushes actionable signals to Telegram. New hourly 'alerts' job dispatches four toggleable triggers, deduped via a new alert_log table (cooldown-based for qualified/S-R/digest, watermark-based for score deterioration). Admin → Settings gains a Telegram panel (write-only bot token, chat ID, per-trigger toggles, Send Test). Credentials follow DB > env precedence (TELEGRAM_BOT_TOKEN / _CHAT_ID). Backend: alert_service + AlertLog model + migration 005, scheduler job, admin endpoints/schema. Frontend: AlertSettings panel, hooks, api, types. Deploy: run alembic upgrade (new alert_log table). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
"""Admin request/response schemas."""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class UserManagement(BaseModel):
|
|
"""Schema for user access management."""
|
|
has_access: bool
|
|
|
|
|
|
class PasswordReset(BaseModel):
|
|
"""Schema for resetting a user's password."""
|
|
new_password: str = Field(..., min_length=6)
|
|
|
|
|
|
class CreateUserRequest(BaseModel):
|
|
"""Schema for admin-created user accounts."""
|
|
username: str = Field(..., min_length=1)
|
|
password: str = Field(..., min_length=6)
|
|
role: str = Field(default="user", pattern=r"^(user|admin)$")
|
|
has_access: bool = False
|
|
|
|
|
|
class RegistrationToggle(BaseModel):
|
|
"""Schema for toggling registration on/off."""
|
|
enabled: bool
|
|
|
|
|
|
class SystemSettingUpdate(BaseModel):
|
|
"""Schema for updating a system setting."""
|
|
value: str = Field(..., min_length=1)
|
|
|
|
|
|
class DataCleanupRequest(BaseModel):
|
|
"""Schema for data cleanup — delete records older than N days."""
|
|
older_than_days: int = Field(..., gt=0)
|
|
|
|
|
|
class JobToggle(BaseModel):
|
|
"""Schema for enabling/disabling a scheduled job."""
|
|
enabled: bool
|
|
|
|
|
|
class RecommendationConfigUpdate(BaseModel):
|
|
high_confidence_threshold: float | None = Field(default=None, ge=0, le=100)
|
|
moderate_confidence_threshold: float | None = Field(default=None, ge=0, le=100)
|
|
confidence_diff_threshold: float | None = Field(default=None, ge=0, le=100)
|
|
signal_alignment_weight: float | None = Field(default=None, ge=0, le=1)
|
|
sr_strength_weight: float | None = Field(default=None, ge=0, le=1)
|
|
momentum_technical_divergence_threshold: float | None = Field(default=None, ge=0, le=100)
|
|
fundamental_technical_divergence_threshold: float | None = Field(default=None, ge=0, le=100)
|
|
|
|
|
|
class TickerUniverseUpdate(BaseModel):
|
|
universe: Literal["sp500", "nasdaq100", "nasdaq_all"]
|
|
|
|
|
|
class ActivationConfigUpdate(BaseModel):
|
|
"""Activation gate: what counts as an actionable signal."""
|
|
min_rr: float | None = Field(default=None, ge=0)
|
|
min_confidence: float | None = Field(default=None, ge=0, le=100)
|
|
min_target_probability: float | None = Field(default=None, ge=0, le=100)
|
|
require_high_conviction: bool | None = None
|
|
exclude_conflicts: bool | None = None
|
|
|
|
|
|
class SentimentConfigUpdate(BaseModel):
|
|
"""Runtime sentiment LLM config. api_key is write-only; omit/empty to keep
|
|
the stored key."""
|
|
provider: Literal["openai", "gemini", "deepseek", "xai", "openai_compatible"] | None = None
|
|
model: str | None = Field(default=None, max_length=100)
|
|
api_key: str | None = Field(default=None, max_length=400)
|
|
base_url: str | None = Field(default=None, max_length=300)
|
|
|
|
|
|
class SentimentTestRequest(BaseModel):
|
|
ticker: str = Field(default="AAPL", max_length=10)
|
|
|
|
|
|
class AlertConfigUpdate(BaseModel):
|
|
"""Telegram alert config. bot_token is write-only; omit/empty to keep the
|
|
stored token."""
|
|
enabled: bool | None = None
|
|
bot_token: str | None = Field(default=None, max_length=200)
|
|
telegram_chat_id: str | None = Field(default=None, max_length=64)
|
|
qualified_enabled: bool | None = None
|
|
sr_proximity_enabled: bool | None = None
|
|
score_drop_enabled: bool | None = None
|
|
digest_enabled: bool | None = None
|