models: add User.lang + StrategicLogTranslation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-27 16:52:04 +02:00
parent 7683f82820
commit 9423fa81b7
2 changed files with 93 additions and 0 deletions

View file

@ -120,6 +120,37 @@ class StrategicLog(Base):
cost_usd: Mapped[float | None] = mapped_column(Float)
class StrategicLogTranslation(Base):
"""Cached translation of a single StrategicLog row.
Populated by ai_log_job after the English row is committed: one
row per (log_id, lang) combination. The /log endpoint serves the
matching row when available and falls back to the English source
when no row exists yet (e.g. translation failed or the language
was added after the log was generated).
No user attribution the cache is shared. Setting `lang` on a
user just selects which (already-translated) variant they see.
"""
__tablename__ = "strategic_log_translations"
id: Mapped[int] = mapped_column(_PK, primary_key=True, autoincrement=True)
log_id: Mapped[int] = mapped_column(
_PK, ForeignKey("strategic_logs.id", ondelete="CASCADE"), nullable=False,
)
lang: Mapped[str] = mapped_column(String(8), nullable=False)
content_md: Mapped[str] = mapped_column(Text, nullable=False)
generated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=utcnow,
)
llm_model: Mapped[str | None] = mapped_column(String(64))
llm_cost_usd: Mapped[float | None] = mapped_column(Float)
__table_args__ = (
UniqueConstraint("log_id", "lang", name="uq_slt_log_lang"),
)
class IndicatorSummary(Base):
"""Short AI-generated read for one indicator group, regenerated hourly.
The latest row per group_name is what the dashboard renders."""
@ -189,6 +220,13 @@ class User(Base):
# NULL = use INTERMEDIATE at render time. Server-side mirror of the
# dashboard tone, decoupled because the dashboard pref is localStorage.
digest_tone: Mapped[str | None] = mapped_column(String(16))
# Preferred language for AI-generated content (strategic log,
# digest emails, portfolio commentary). Default 'en'. The settings
# PATCH endpoint validates against ACTIVE_LANGUAGES in
# app/services/i18n.py before writing.
lang: Mapped[str] = mapped_column(
String(8), nullable=False, default="en", server_default="en",
)
# Polar (MoR) linkage — populated by the polar_webhook handler the
# first time we see a subscription/order event for the user. The
# customer id is the stable join key; the subscription id is what