From 5665f484ed56cb27ef7bab054b3da73b2969064f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 25 Mar 2026 10:30:37 -0400 Subject: [PATCH] feat(briefing): add Message.msg_metadata and RssItem.topics/classified_at columns msg_metadata maps to the 'metadata' DB column ('metadata' is reserved by SQLAlchemy Declarative API). to_dict() exposes the key as 'metadata' for frontend compatibility. Co-Authored-By: Claude Sonnet 4.6 --- src/fabledassistant/models/conversation.py | 4 ++++ src/fabledassistant/models/rss_feed.py | 10 +++++++++- tests/test_briefing_models.py | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/fabledassistant/models/conversation.py b/src/fabledassistant/models/conversation.py index 2613e67..0065c6a 100644 --- a/src/fabledassistant/models/conversation.py +++ b/src/fabledassistant/models/conversation.py @@ -67,6 +67,9 @@ class Message(Base, CreatedAtMixin): Integer, ForeignKey("notes.id", ondelete="SET NULL"), nullable=True ) tool_calls: Mapped[list | None] = mapped_column(JSONB, nullable=True) + # 'metadata' is reserved by SQLAlchemy Declarative — use msg_metadata as the + # Python attribute name, mapped to the 'metadata' DB column. + msg_metadata: Mapped[dict | None] = mapped_column("metadata", JSONB, nullable=True) conversation: Mapped["Conversation"] = relationship(back_populates="messages") @@ -83,5 +86,6 @@ class Message(Base, CreatedAtMixin): "status": self.status, "context_note_id": self.context_note_id, "tool_calls": self.tool_calls, + "metadata": self.msg_metadata, "created_at": self.created_at.isoformat(), } diff --git a/src/fabledassistant/models/rss_feed.py b/src/fabledassistant/models/rss_feed.py index a9ff97c..e5b3c87 100644 --- a/src/fabledassistant/models/rss_feed.py +++ b/src/fabledassistant/models/rss_feed.py @@ -1,6 +1,6 @@ from datetime import datetime, timezone -from sqlalchemy import DateTime, ForeignKey, Index, Integer, Text, UniqueConstraint +from sqlalchemy import ARRAY, DateTime, ForeignKey, Index, Integer, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from fabledassistant.models import Base @@ -49,6 +49,12 @@ class RssItem(Base): fetched_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) + topics: Mapped[list[str]] = mapped_column( + ARRAY(Text), nullable=False, default=list, server_default="{}" + ) + classified_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) feed: Mapped["RssFeed"] = relationship(back_populates="items") @@ -67,4 +73,6 @@ class RssItem(Base): "url": self.url, "published_at": self.published_at.isoformat() if self.published_at else None, "content": self.content, + "topics": self.topics or [], + "classified_at": self.classified_at.isoformat() if self.classified_at else None, } diff --git a/tests/test_briefing_models.py b/tests/test_briefing_models.py index 7f60e1d..086991b 100644 --- a/tests/test_briefing_models.py +++ b/tests/test_briefing_models.py @@ -18,3 +18,23 @@ def test_weather_cache_model_exists(): from fabledassistant.models.weather_cache import WeatherCache cols = {c.key for c in WeatherCache.__table__.columns} assert {"id", "user_id", "location_key", "location_label", "forecast_json", "previous_json", "fetched_at"} <= cols + + +def test_message_metadata_field_exists(): + from fabledassistant.models.conversation import Message + + # DB column is named 'metadata'; Python attribute is 'msg_metadata' + # (SQLAlchemy reserves 'metadata' as an attribute name on mapped classes) + col_names = {c.name for c in Message.__table__.columns} + assert "metadata" in col_names + + m = Message(conversation_id=1, role="assistant", content="hi", msg_metadata={"foo": 1}) + assert m.msg_metadata == {"foo": 1} + + +def test_rss_item_topics_field_exists(): + from fabledassistant.models.rss_feed import RssItem + + cols = {c.key for c in RssItem.__table__.columns} + assert "topics" in cols + assert "classified_at" in cols