didi-lot1-ai/ai_platform/modules/didi_brain/scheduler/config.py

124 lines
4.8 KiB
Python

"""Configuration for the scheduler container.
All settings flow from env vars (prefix ``SCHED_``) so deployment can tune
intervals + feed lists without rebuilds. Reasonable defaults for a typical
DIDI deployment are baked in, but ``brain_api_url`` is required to fail-fast
if misconfigured.
"""
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class SchedulerSettings(BaseSettings):
"""Top-level scheduler config — validated at startup."""
model_config = SettingsConfigDict(
env_file=Path(__file__).parent.parent / ".env",
env_file_encoding="utf-8",
extra="ignore",
env_prefix="SCHED_",
)
# ---- Brain API target -------------------------------------------------
brain_api_url: str = Field(
default="http://brain-api:8090",
description="Base URL of the brain HTTP API. Docker DNS by default.",
)
brain_api_timeout_s: float = Field(default=120.0)
# ---- Feeder (Pilon 4) -------------------------------------------------
feeder_enabled: bool = Field(default=True)
# Intervals (seconds) per volatility — controls how often we poll RSS
# feeds in each tier.
feeder_volatile_interval_s: int = Field(default=900) # 15 min
feeder_evolving_interval_s: int = Field(default=21600) # 6 h
feeder_stable_interval_s: int = Field(default=86400) # 24 h
feeder_max_items_per_run: int = Field(default=20)
feeder_min_published_age_s: int = Field(default=3600) # skip <1h items
feeder_max_published_age_s: int = Field(default=259200) # skip >3d items
# ---- Auditor (Pilon 5) ------------------------------------------------
auditor_enabled: bool = Field(default=True)
auditor_interval_s: int = Field(default=86400) # 24h sweep
auditor_batch_limit: int = Field(default=200)
auditor_min_age_hours: float = Field(
default=24.0,
description=(
"Don't audit atoms younger than this — they were just written, "
"judging them yields no new signal."
),
)
# ---- Breaking-news watcher (Pilon 9) ----------------------------------
watcher_enabled: bool = Field(default=True)
watcher_poll_interval_s: int = Field(default=300) # 5 min
watcher_max_items_per_run: int = Field(default=10)
watcher_min_published_age_s: int = Field(default=60) # >1min old
watcher_max_published_age_s: int = Field(default=3600) # <1h old
# ---- Logging ----------------------------------------------------------
log_level: str = Field(default="INFO")
log_json: bool = Field(default=False)
@lru_cache(maxsize=1)
def get_settings() -> SchedulerSettings:
return SchedulerSettings()
settings = get_settings()
# ----------------------------------------------------------------------------
# Default feed lists per volatility — overridable via env (FEED_VOLATILE_URLS,
# FEED_EVOLVING_URLS, FEED_STABLE_URLS, FEED_BREAKING_URLS as
# comma-separated strings) for ops flexibility.
# ----------------------------------------------------------------------------
# Volatile: fast-moving news (war, breaking events, daily politics).
# Note: Reuters retired their public RSS feeds. Operators with paid Reuters
# access can add their feed URLs via the FEED_VOLATILE_URLS env override.
DEFAULT_VOLATILE_FEEDS: list[str] = [
"https://feeds.bbci.co.uk/news/world/rss.xml",
"https://www.aljazeera.com/xml/rss/all.xml",
"https://feeds.npr.org/1004/rss.xml",
"https://www.theguardian.com/world/rss",
# Romanian
"https://www.digi24.ro/rss",
"https://www.hotnews.ro/rss",
"https://www.g4media.ro/feed",
]
# Evolving: weekly-stable topics (economy, climate, science debates).
DEFAULT_EVOLVING_FEEDS: list[str] = [
"https://feeds.bbci.co.uk/news/business/rss.xml",
"https://feeds.bbci.co.uk/news/health/rss.xml",
"https://rss.nytimes.com/services/xml/rss/nyt/Science.xml",
]
# Stable: long-cycle topics (basic science, history, settled facts).
DEFAULT_STABLE_FEEDS: list[str] = [
"https://feeds.bbci.co.uk/news/science_and_environment/rss.xml",
]
# Breaking: small set polled fast (5 min) — only the highest-credibility
# real-time wires. Each item runs through the LLM classifier to decide
# which topics/entities to invalidate caches for.
DEFAULT_BREAKING_FEEDS: list[str] = [
"https://www.theguardian.com/world/rss",
"https://feeds.bbci.co.uk/news/world/rss.xml",
"https://www.aljazeera.com/xml/rss/all.xml",
]
def parse_csv(value: str | None, default: list[str]) -> list[str]:
"""Parse a comma-separated env var into a list, falling back to default."""
if not value:
return list(default)
return [v.strip() for v in value.split(",") if v.strip()]