"""Tests for the multi-layered context management system.""" from __future__ import annotations import sqlite3 from datetime import UTC, datetime, timedelta import pytest from src.context.aggregator import ContextAggregator from src.context.layer import LAYER_CONFIG, ContextLayer from src.context.store import ContextStore from src.context.summarizer import ContextSummarizer from src.db import init_db, log_trade @pytest.fixture def db_conn() -> sqlite3.Connection: """Provide an in-memory database connection.""" return init_db(":memory:") @pytest.fixture def store(db_conn: sqlite3.Connection) -> ContextStore: """Provide a ContextStore instance.""" return ContextStore(db_conn) @pytest.fixture def aggregator(db_conn: sqlite3.Connection) -> ContextAggregator: """Provide a ContextAggregator instance.""" return ContextAggregator(db_conn) class TestContextStore: """Test suite for ContextStore CRUD operations.""" def test_set_and_get_context(self, store: ContextStore) -> None: """Test setting and retrieving a context value.""" store.set_context(ContextLayer.L6_DAILY, "2026-02-04", "total_pnl", 1234.56) value = store.get_context(ContextLayer.L6_DAILY, "2026-02-04", "total_pnl") assert value == 1234.56 def test_get_nonexistent_context(self, store: ContextStore) -> None: """Test retrieving a non-existent context returns None.""" value = store.get_context(ContextLayer.L6_DAILY, "2026-02-04", "nonexistent") assert value is None def test_update_existing_context(self, store: ContextStore) -> None: """Test updating an existing context value.""" store.set_context(ContextLayer.L6_DAILY, "2026-02-04", "total_pnl", 100.0) store.set_context(ContextLayer.L6_DAILY, "2026-02-04", "total_pnl", 200.0) value = store.get_context(ContextLayer.L6_DAILY, "2026-02-04", "total_pnl") assert value == 200.0 def test_get_all_contexts_for_layer(self, store: ContextStore) -> None: """Test retrieving all contexts for a specific layer.""" store.set_context(ContextLayer.L6_DAILY, "2026-02-04", "total_pnl", 100.0) store.set_context(ContextLayer.L6_DAILY, "2026-02-04", "trade_count", 10) store.set_context(ContextLayer.L6_DAILY, "2026-02-04", "win_rate", 60.5) contexts = store.get_all_contexts(ContextLayer.L6_DAILY, "2026-02-04") assert len(contexts) == 3 assert contexts["total_pnl"] == 100.0 assert contexts["trade_count"] == 10 assert contexts["win_rate"] == 60.5 def test_get_latest_timeframe(self, store: ContextStore) -> None: """Test getting the most recent timeframe for a layer.""" store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "total_pnl", 100.0) store.set_context(ContextLayer.L6_DAILY, "2026-02-03", "total_pnl", 200.0) store.set_context(ContextLayer.L6_DAILY, "2026-02-02", "total_pnl", 150.0) latest = store.get_latest_timeframe(ContextLayer.L6_DAILY) # Latest by updated_at, which should be the last one set assert latest == "2026-02-02" def test_delete_old_contexts( self, store: ContextStore, db_conn: sqlite3.Connection ) -> None: """Test deleting contexts older than a cutoff date.""" # Insert contexts with specific old timestamps # (bypassing set_context which uses current time) old_date = "2026-01-01T00:00:00+00:00" new_date = "2026-02-01T00:00:00+00:00" db_conn.execute( """ INSERT INTO contexts (layer, timeframe, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) """, (ContextLayer.L6_DAILY.value, "2026-01-01", "total_pnl", "100.0", old_date, old_date), ) db_conn.execute( """ INSERT INTO contexts (layer, timeframe, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) """, (ContextLayer.L6_DAILY.value, "2026-02-01", "total_pnl", "200.0", new_date, new_date), ) db_conn.commit() # Delete contexts before 2026-01-15 cutoff = "2026-01-15T00:00:00+00:00" deleted = store.delete_old_contexts(ContextLayer.L6_DAILY, cutoff) # Should delete the 2026-01-01 context assert deleted == 1 assert store.get_context(ContextLayer.L6_DAILY, "2026-02-01", "total_pnl") == 200.0 assert store.get_context(ContextLayer.L6_DAILY, "2026-01-01", "total_pnl") is None def test_cleanup_expired_contexts( self, store: ContextStore, db_conn: sqlite3.Connection ) -> None: """Test automatic cleanup based on retention policies.""" # Set old contexts for L7 (7 day retention) old_date = (datetime.now(UTC) - timedelta(days=10)).isoformat() db_conn.execute( """ INSERT INTO contexts (layer, timeframe, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) """, (ContextLayer.L7_REALTIME.value, "2026-01-01", "price", "100.0", old_date, old_date), ) db_conn.commit() deleted_counts = store.cleanup_expired_contexts() # Should delete the old L7 context (10 days > 7 day retention) assert deleted_counts[ContextLayer.L7_REALTIME] == 1 # L1 has no retention limit, so nothing should be deleted assert deleted_counts[ContextLayer.L1_LEGACY] == 0 def test_context_metadata_initialized( self, store: ContextStore, db_conn: sqlite3.Connection ) -> None: """Test that context metadata is properly initialized.""" cursor = db_conn.execute("SELECT COUNT(*) FROM context_metadata") count = cursor.fetchone()[0] # Should have metadata for all 7 layers assert count == 7 # Verify L1 metadata cursor = db_conn.execute( "SELECT description, retention_days FROM context_metadata WHERE layer = ?", (ContextLayer.L1_LEGACY.value,), ) row = cursor.fetchone() assert row is not None assert "Cumulative trading history" in row[0] assert row[1] is None # No retention limit for L1 class TestContextAggregator: """Test suite for ContextAggregator.""" def test_aggregate_daily_from_trades( self, aggregator: ContextAggregator, db_conn: sqlite3.Connection ) -> None: """Test aggregating daily metrics from trades.""" date = datetime.now(UTC).date().isoformat() # Create sample trades log_trade(db_conn, "005930", "BUY", 85, "Good signal", quantity=10, price=70000, pnl=500) log_trade(db_conn, "000660", "SELL", 90, "Take profit", quantity=5, price=50000, pnl=1500) log_trade(db_conn, "035720", "HOLD", 75, "Wait", quantity=0, price=0, pnl=0) # Manually set timestamps to the target date db_conn.execute( f"UPDATE trades SET timestamp = '{date}T10:00:00+00:00'" ) db_conn.commit() # Aggregate aggregator.aggregate_daily_from_trades(date, market="KR") # Verify L6 contexts store = aggregator.store assert store.get_context(ContextLayer.L6_DAILY, date, "trade_count_KR") == 3 assert store.get_context(ContextLayer.L6_DAILY, date, "buys_KR") == 1 assert store.get_context(ContextLayer.L6_DAILY, date, "sells_KR") == 1 assert store.get_context(ContextLayer.L6_DAILY, date, "holds_KR") == 1 assert store.get_context(ContextLayer.L6_DAILY, date, "total_pnl_KR") == 2000.0 assert store.get_context(ContextLayer.L6_DAILY, date, "unique_stocks_KR") == 3 # 2 wins, 0 losses assert store.get_context(ContextLayer.L6_DAILY, date, "win_rate_KR") == 100.0 def test_aggregate_weekly_from_daily(self, aggregator: ContextAggregator) -> None: """Test aggregating weekly metrics from daily.""" week = "2026-W06" # Set daily contexts aggregator.store.set_context( ContextLayer.L6_DAILY, "2026-02-02", "total_pnl_KR", 100.0 ) aggregator.store.set_context( ContextLayer.L6_DAILY, "2026-02-03", "total_pnl_KR", 200.0 ) aggregator.store.set_context( ContextLayer.L6_DAILY, "2026-02-02", "avg_confidence_KR", 80.0 ) aggregator.store.set_context( ContextLayer.L6_DAILY, "2026-02-03", "avg_confidence_KR", 85.0 ) # Aggregate aggregator.aggregate_weekly_from_daily(week) # Verify L5 contexts store = aggregator.store weekly_pnl = store.get_context(ContextLayer.L5_WEEKLY, week, "weekly_pnl_KR") avg_conf = store.get_context(ContextLayer.L5_WEEKLY, week, "avg_confidence_KR") assert weekly_pnl == 300.0 assert avg_conf == 82.5 def test_aggregate_monthly_from_weekly(self, aggregator: ContextAggregator) -> None: """Test aggregating monthly metrics from weekly.""" month = "2026-02" # Set weekly contexts aggregator.store.set_context( ContextLayer.L5_WEEKLY, "2026-W05", "weekly_pnl_KR", 100.0 ) aggregator.store.set_context( ContextLayer.L5_WEEKLY, "2026-W06", "weekly_pnl_KR", 200.0 ) aggregator.store.set_context( ContextLayer.L5_WEEKLY, "2026-W07", "weekly_pnl_KR", 150.0 ) # Aggregate aggregator.aggregate_monthly_from_weekly(month) # Verify L4 contexts store = aggregator.store monthly_pnl = store.get_context(ContextLayer.L4_MONTHLY, month, "monthly_pnl") assert monthly_pnl == 450.0 def test_aggregate_quarterly_from_monthly(self, aggregator: ContextAggregator) -> None: """Test aggregating quarterly metrics from monthly.""" quarter = "2026-Q1" # Set monthly contexts for Q1 (Jan, Feb, Mar) aggregator.store.set_context(ContextLayer.L4_MONTHLY, "2026-01", "monthly_pnl", 1000.0) aggregator.store.set_context(ContextLayer.L4_MONTHLY, "2026-02", "monthly_pnl", 2000.0) aggregator.store.set_context(ContextLayer.L4_MONTHLY, "2026-03", "monthly_pnl", 1500.0) # Aggregate aggregator.aggregate_quarterly_from_monthly(quarter) # Verify L3 contexts store = aggregator.store quarterly_pnl = store.get_context(ContextLayer.L3_QUARTERLY, quarter, "quarterly_pnl") assert quarterly_pnl == 4500.0 def test_aggregate_annual_from_quarterly(self, aggregator: ContextAggregator) -> None: """Test aggregating annual metrics from quarterly.""" year = "2026" # Set quarterly contexts for all 4 quarters aggregator.store.set_context(ContextLayer.L3_QUARTERLY, "2026-Q1", "quarterly_pnl", 4500.0) aggregator.store.set_context(ContextLayer.L3_QUARTERLY, "2026-Q2", "quarterly_pnl", 5000.0) aggregator.store.set_context(ContextLayer.L3_QUARTERLY, "2026-Q3", "quarterly_pnl", 4800.0) aggregator.store.set_context(ContextLayer.L3_QUARTERLY, "2026-Q4", "quarterly_pnl", 5200.0) # Aggregate aggregator.aggregate_annual_from_quarterly(year) # Verify L2 contexts store = aggregator.store annual_pnl = store.get_context(ContextLayer.L2_ANNUAL, year, "annual_pnl") assert annual_pnl == 19500.0 def test_aggregate_legacy_from_annual(self, aggregator: ContextAggregator) -> None: """Test aggregating legacy metrics from all annual data.""" # Set annual contexts for multiple years aggregator.store.set_context(ContextLayer.L2_ANNUAL, "2024", "annual_pnl", 10000.0) aggregator.store.set_context(ContextLayer.L2_ANNUAL, "2025", "annual_pnl", 15000.0) aggregator.store.set_context(ContextLayer.L2_ANNUAL, "2026", "annual_pnl", 20000.0) # Aggregate aggregator.aggregate_legacy_from_annual() # Verify L1 contexts store = aggregator.store total_pnl = store.get_context(ContextLayer.L1_LEGACY, "LEGACY", "total_pnl") years_traded = store.get_context(ContextLayer.L1_LEGACY, "LEGACY", "years_traded") avg_annual_pnl = store.get_context(ContextLayer.L1_LEGACY, "LEGACY", "avg_annual_pnl") assert total_pnl == 45000.0 assert years_traded == 3 assert avg_annual_pnl == 15000.0 def test_run_all_aggregations( self, aggregator: ContextAggregator, db_conn: sqlite3.Connection ) -> None: """Test running all aggregations from L7 to L1.""" date = datetime.now(UTC).date().isoformat() # Create sample trades log_trade(db_conn, "005930", "BUY", 85, "Good signal", quantity=10, price=70000, pnl=1000) # Set timestamp db_conn.execute(f"UPDATE trades SET timestamp = '{date}T10:00:00+00:00'") db_conn.commit() # Run all aggregations aggregator.run_all_aggregations() # Verify data exists in each layer store = aggregator.store assert store.get_context(ContextLayer.L6_DAILY, date, "total_pnl_KR") == 1000.0 from datetime import date as date_cls trade_date = date_cls.fromisoformat(date) iso_year, iso_week, _ = trade_date.isocalendar() trade_week = f"{iso_year}-W{iso_week:02d}" assert store.get_context(ContextLayer.L5_WEEKLY, trade_week, "weekly_pnl_KR") is not None trade_month = f"{trade_date.year}-{trade_date.month:02d}" trade_quarter = f"{trade_date.year}-Q{(trade_date.month - 1) // 3 + 1}" trade_year = str(trade_date.year) assert store.get_context(ContextLayer.L4_MONTHLY, trade_month, "monthly_pnl") == 1000.0 assert store.get_context(ContextLayer.L3_QUARTERLY, trade_quarter, "quarterly_pnl") == 1000.0 assert store.get_context(ContextLayer.L2_ANNUAL, trade_year, "annual_pnl") == 1000.0 class TestLayerMetadata: """Test suite for layer metadata configuration.""" def test_all_layers_have_metadata(self) -> None: """Test that all 7 layers have metadata defined.""" assert len(LAYER_CONFIG) == 7 for layer in ContextLayer: assert layer in LAYER_CONFIG def test_layer_retention_policies(self) -> None: """Test layer retention policies are correctly configured.""" # L1 should have no retention limit assert LAYER_CONFIG[ContextLayer.L1_LEGACY].retention_days is None # L7 should have the shortest retention (7 days) assert LAYER_CONFIG[ContextLayer.L7_REALTIME].retention_days == 7 # L2 should have a long retention (10 years) assert LAYER_CONFIG[ContextLayer.L2_ANNUAL].retention_days == 365 * 10 def test_layer_aggregation_chain(self) -> None: """Test that the aggregation chain is properly configured.""" # L7 has no source (leaf layer) assert LAYER_CONFIG[ContextLayer.L7_REALTIME].aggregation_source is None # L6 aggregates from L7 assert LAYER_CONFIG[ContextLayer.L6_DAILY].aggregation_source == ContextLayer.L7_REALTIME # L5 aggregates from L6 assert LAYER_CONFIG[ContextLayer.L5_WEEKLY].aggregation_source == ContextLayer.L6_DAILY # L4 aggregates from L5 assert LAYER_CONFIG[ContextLayer.L4_MONTHLY].aggregation_source == ContextLayer.L5_WEEKLY # L3 aggregates from L4 assert LAYER_CONFIG[ContextLayer.L3_QUARTERLY].aggregation_source == ContextLayer.L4_MONTHLY # L2 aggregates from L3 assert LAYER_CONFIG[ContextLayer.L2_ANNUAL].aggregation_source == ContextLayer.L3_QUARTERLY # L1 aggregates from L2 assert LAYER_CONFIG[ContextLayer.L1_LEGACY].aggregation_source == ContextLayer.L2_ANNUAL # --------------------------------------------------------------------------- # ContextSummarizer tests # --------------------------------------------------------------------------- @pytest.fixture def summarizer(db_conn: sqlite3.Connection) -> ContextSummarizer: """Provide a ContextSummarizer backed by an in-memory store.""" return ContextSummarizer(ContextStore(db_conn)) class TestContextSummarizer: """Test suite for ContextSummarizer.""" # ------------------------------------------------------------------ # summarize_numeric_values # ------------------------------------------------------------------ def test_summarize_empty_values(self, summarizer: ContextSummarizer) -> None: """Empty list must return SummaryStats with count=0 and no other fields.""" stats = summarizer.summarize_numeric_values([]) assert stats.count == 0 assert stats.mean is None assert stats.min is None assert stats.max is None def test_summarize_single_value(self, summarizer: ContextSummarizer) -> None: """Single-element list must return correct stats with std=0 and trend=flat.""" stats = summarizer.summarize_numeric_values([42.0]) assert stats.count == 1 assert stats.mean == 42.0 assert stats.std == 0.0 assert stats.trend == "flat" def test_summarize_upward_trend(self, summarizer: ContextSummarizer) -> None: """Increasing values must produce trend='up'.""" values = [1.0, 2.0, 3.0, 10.0, 20.0, 30.0] stats = summarizer.summarize_numeric_values(values) assert stats.trend == "up" def test_summarize_downward_trend(self, summarizer: ContextSummarizer) -> None: """Decreasing values must produce trend='down'.""" values = [30.0, 20.0, 10.0, 3.0, 2.0, 1.0] stats = summarizer.summarize_numeric_values(values) assert stats.trend == "down" def test_summarize_flat_trend(self, summarizer: ContextSummarizer) -> None: """Stable values must produce trend='flat'.""" values = [100.0, 100.1, 99.9, 100.0, 100.2, 99.8] stats = summarizer.summarize_numeric_values(values) assert stats.trend == "flat" # ------------------------------------------------------------------ # summarize_layer # ------------------------------------------------------------------ def test_summarize_layer_no_data( self, summarizer: ContextSummarizer ) -> None: """summarize_layer with no data must return the 'No data' sentinel.""" result = summarizer.summarize_layer(ContextLayer.L6_DAILY) assert result["count"] == 0 assert "No data" in result["summary"] def test_summarize_layer_numeric( self, summarizer: ContextSummarizer, db_conn: sqlite3.Connection ) -> None: """summarize_layer must collect numeric values and produce stats.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "total_pnl", 100.0) store.set_context(ContextLayer.L6_DAILY, "2026-02-02", "total_pnl", 200.0) result = summarizer.summarize_layer(ContextLayer.L6_DAILY) assert "total_entries" in result def test_summarize_layer_with_dict_values( self, summarizer: ContextSummarizer ) -> None: """summarize_layer must handle dict values by extracting numeric subkeys.""" store = summarizer.store # set_context serialises the value as JSON, so passing a dict works store.set_context( ContextLayer.L6_DAILY, "2026-02-01", "metrics", {"win_rate": 65.0, "label": "good"} ) result = summarizer.summarize_layer(ContextLayer.L6_DAILY) assert "total_entries" in result # numeric subkey "win_rate" should appear as "metrics.win_rate" assert "metrics.win_rate" in result def test_summarize_layer_with_string_values( self, summarizer: ContextSummarizer ) -> None: """summarize_layer must count string values separately.""" store = summarizer.store # set_context stores string values as JSON-encoded strings store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "outlook", "BULLISH") result = summarizer.summarize_layer(ContextLayer.L6_DAILY) # String fields contribute a `_count` entry assert "outlook_count" in result # ------------------------------------------------------------------ # rolling_window_summary # ------------------------------------------------------------------ def test_rolling_window_summary_basic( self, summarizer: ContextSummarizer ) -> None: """rolling_window_summary must return the expected structure.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "pnl", 500.0) result = summarizer.rolling_window_summary(ContextLayer.L6_DAILY) assert "window_days" in result assert "recent_data" in result assert "historical_summary" in result def test_rolling_window_summary_no_older_data( self, summarizer: ContextSummarizer ) -> None: """rolling_window_summary with summarize_older=False skips history.""" result = summarizer.rolling_window_summary( ContextLayer.L6_DAILY, summarize_older=False ) assert result["historical_summary"] == {} # ------------------------------------------------------------------ # aggregate_to_higher_layer # ------------------------------------------------------------------ def test_aggregate_to_higher_layer_mean( self, summarizer: ContextSummarizer ) -> None: """aggregate_to_higher_layer with 'mean' via dict subkeys returns average.""" store = summarizer.store # Use different outer keys but same inner metric key so get_all_contexts # returns multiple rows with the target subkey. store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day1", {"pnl": 100.0}) store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day2", {"pnl": 200.0}) result = summarizer.aggregate_to_higher_layer( ContextLayer.L6_DAILY, ContextLayer.L5_WEEKLY, "pnl", "mean" ) assert result == pytest.approx(150.0) def test_aggregate_to_higher_layer_sum( self, summarizer: ContextSummarizer ) -> None: """aggregate_to_higher_layer with 'sum' must return the total.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day1", {"pnl": 100.0}) store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day2", {"pnl": 200.0}) result = summarizer.aggregate_to_higher_layer( ContextLayer.L6_DAILY, ContextLayer.L5_WEEKLY, "pnl", "sum" ) assert result == pytest.approx(300.0) def test_aggregate_to_higher_layer_max( self, summarizer: ContextSummarizer ) -> None: """aggregate_to_higher_layer with 'max' must return the maximum.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day1", {"pnl": 100.0}) store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day2", {"pnl": 200.0}) result = summarizer.aggregate_to_higher_layer( ContextLayer.L6_DAILY, ContextLayer.L5_WEEKLY, "pnl", "max" ) assert result == pytest.approx(200.0) def test_aggregate_to_higher_layer_min( self, summarizer: ContextSummarizer ) -> None: """aggregate_to_higher_layer with 'min' must return the minimum.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day1", {"pnl": 100.0}) store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day2", {"pnl": 200.0}) result = summarizer.aggregate_to_higher_layer( ContextLayer.L6_DAILY, ContextLayer.L5_WEEKLY, "pnl", "min" ) assert result == pytest.approx(100.0) def test_aggregate_to_higher_layer_no_data( self, summarizer: ContextSummarizer ) -> None: """aggregate_to_higher_layer with no matching key must return None.""" result = summarizer.aggregate_to_higher_layer( ContextLayer.L6_DAILY, ContextLayer.L5_WEEKLY, "nonexistent", "mean" ) assert result is None def test_aggregate_to_higher_layer_unknown_func_defaults_to_mean( self, summarizer: ContextSummarizer ) -> None: """Unknown aggregation function must fall back to mean.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day1", {"pnl": 100.0}) store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "day2", {"pnl": 200.0}) result = summarizer.aggregate_to_higher_layer( ContextLayer.L6_DAILY, ContextLayer.L5_WEEKLY, "pnl", "unknown_func" ) assert result == pytest.approx(150.0) # ------------------------------------------------------------------ # create_compact_summary + format_summary_for_prompt # ------------------------------------------------------------------ def test_create_compact_summary( self, summarizer: ContextSummarizer ) -> None: """create_compact_summary must produce a dict keyed by layer value.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "pnl", 100.0) result = summarizer.create_compact_summary([ContextLayer.L6_DAILY]) assert ContextLayer.L6_DAILY.value in result def test_format_summary_for_prompt_with_numeric_metrics( self, summarizer: ContextSummarizer ) -> None: """format_summary_for_prompt must render avg/trend fields.""" store = summarizer.store store.set_context(ContextLayer.L6_DAILY, "2026-02-01", "pnl", 100.0) store.set_context(ContextLayer.L6_DAILY, "2026-02-02", "pnl", 200.0) compact = summarizer.create_compact_summary([ContextLayer.L6_DAILY]) text = summarizer.format_summary_for_prompt(compact) assert isinstance(text, str) def test_format_summary_for_prompt_skips_empty_layers( self, summarizer: ContextSummarizer ) -> None: """format_summary_for_prompt must skip layers with no metrics.""" summary = {ContextLayer.L6_DAILY.value: {}} text = summarizer.format_summary_for_prompt(summary) assert text == "" def test_format_summary_non_dict_value( self, summarizer: ContextSummarizer ) -> None: """format_summary_for_prompt must render non-dict values as plain text.""" summary = { "daily": { "plain_count": 42, } } text = summarizer.format_summary_for_prompt(summary) assert "plain_count" in text assert "42" in text