chore: remove SQLite entirely, PostgreSQL only

SQLite was never used in production (Docker Compose always sets
DATABASE_URL to postgresql+asyncpg://...) but the fallback kept
creating confusing stock_oracle.db files locally.

- config.py: default DATABASE_URL fallback → PostgreSQL (localhost:15433)
- database.py: remove _is_sqlite conditional branch and NullPool import
- error_logs.py: replace strftime/dialect-check with pg to_char()
- request_logs.py: replace strftime with pg to_char()
- feature_builder.py: remove _ensure_utc() helper and all call sites
- overlay_pipeline.py: remove naive-datetime workaround for is_stale()
- requirements-api.txt / requirements-test.txt: drop aiosqlite
- stock_oracle.db: deleted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
I Luk Kim 5 months ago
parent 9085151af8
commit d82ba624e2

@ -266,12 +266,7 @@ async def get_error_stats(
# Get hourly trend for last 24 hours if within range
hourly_trend = {}
if (end_date - start_date).days <= 1:
# Dialect-aware date formatting
dialect_name = db.bind.dialect.name if db.bind else "sqlite"
if dialect_name == "postgresql":
hour_expr = func.to_char(ErrorLog.created_at, 'YYYY-MM-DD HH24:00').label('hour')
else:
hour_expr = func.strftime('%Y-%m-%d %H:00', ErrorLog.created_at).label('hour')
hourly_result = await db.execute(
select(
hour_expr,

@ -262,10 +262,9 @@ async def get_request_stats(
# Get hourly trend for last 24 hours if within range
hourly_trend = {}
if (end_date - start_date).days <= 1:
# SQLite specific date formatting
hourly_result = await db.execute(
select(
func.strftime('%Y-%m-%d %H:00', RequestLog.created_at).label('hour'),
func.to_char(RequestLog.created_at, 'YYYY-MM-DD HH24:00').label('hour'),
func.count().label('count')
).where(date_filter)
.group_by('hour')

@ -38,7 +38,7 @@ class Settings(BaseSettings):
# Database
DATABASE_URL: str = os.getenv(
"DATABASE_URL",
"sqlite+aiosqlite:///./stock_oracle.db"
"postgresql+asyncpg://stockoracle:stockoracle2024@localhost:15433/stock_oracle"
)
DATABASE_ECHO: bool = False

@ -4,21 +4,10 @@ Database configuration and session management
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy.pool import NullPool
from app.core.config import settings
# Conditional engine settings based on database type
_is_sqlite = settings.DATABASE_URL.startswith("sqlite")
from app.core.config import settings
if _is_sqlite:
engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DATABASE_ECHO,
future=True,
poolclass=NullPool,
)
else:
engine = create_async_engine(
engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DATABASE_ECHO,
future=True,
@ -32,7 +21,7 @@ else:
"jit": "off"
}
}
)
)
# Create async session factory
AsyncSessionLocal = sessionmaker(

@ -29,14 +29,6 @@ def winsorize(value: float, lower: float = WINSOR_LOWER, upper: float = WINSOR_U
return max(lower, min(upper, value))
def _ensure_utc(dt) -> datetime:
"""Ensure a datetime is UTC-aware (SQLite returns naive datetimes)."""
if dt is None:
return dt
if isinstance(dt, datetime) and dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc)
return dt
def compute_zscore(value: float, values: List[float]) -> Optional[float]:
"""Compute z-score of *value* within *values* (requires ≥2 data points)."""
@ -95,8 +87,8 @@ class FeatureBuilder:
# Filter by symbol
sym_rows_hist = [r for r in all_rows if symbol in (r.matched_symbols or [])]
sym_rows_24h = [r for r in sym_rows_hist if _ensure_utc(r.published_at) >= cutoff_24h]
sym_rows_6h = [r for r in sym_rows_24h if _ensure_utc(r.published_at) >= cutoff_6h]
sym_rows_24h = [r for r in sym_rows_hist if r.published_at >= cutoff_24h]
sym_rows_6h = [r for r in sym_rows_24h if r.published_at >= cutoff_6h]
headline_count_24h = len(sym_rows_24h)
headline_count_6h = len(sym_rows_6h)
@ -144,7 +136,7 @@ class FeatureBuilder:
)
all_rows = result.fetchall()
sym_rows = [r for r in all_rows if symbol in (r.matched_symbols or [])]
sym_rows_24h = [r for r in sym_rows if _ensure_utc(r.published_at) >= cutoff_24h]
sym_rows_24h = [r for r in sym_rows if r.published_at >= cutoff_24h]
mentions_24h = len(sym_rows_24h)
weighted_views_24h = sum(r.view_count * r.channel_weight for r in sym_rows_24h)
@ -195,7 +187,7 @@ class FeatureBuilder:
views_1d = rows[0].views if rows else None
# 7-day average
rows_7d = [r for r in rows if _ensure_utc(r.date) >= cutoff_7d]
rows_7d = [r for r in rows if r.date >= cutoff_7d]
views_7d_avg = sum(r.views for r in rows_7d) / len(rows_7d) if rows_7d else None
# Historical z-score

@ -181,11 +181,7 @@ class OverlayPipeline:
"""Return True if the record is missing or older than FEATURE_STALE_HOURS."""
if record is None:
return True
# SQLite returns naive datetimes; ensure UTC-aware for comparison
as_of = record.as_of_ts
if as_of.tzinfo is None:
as_of = as_of.replace(tzinfo=timezone.utc)
age = datetime.now(timezone.utc) - as_of
age = datetime.now(timezone.utc) - record.as_of_ts
return age.total_seconds() > FEATURE_STALE_HOURS * 3600
# ------------------------------------------------------------------

@ -3,7 +3,6 @@ uvicorn[standard]>=0.24.0
sqlalchemy>=2.0.0,<3.0.0
alembic>=1.12.0
asyncpg>=0.29.0
aiosqlite>=0.19.0
greenlet>=2.0.0
pydantic>=2.0.0,<3.0.0
pydantic-settings>=2.0.0

@ -5,4 +5,3 @@ pytest-cov>=4.1.0
httpx>=0.25.0
faker>=20.0.0
pydantic-settings>=2.0.0
aiosqlite>=0.19.0
Loading…
Cancel
Save