@@ -245,6 +245,7 @@ def __init__(
245245 self .cache_manager = None # Will be set if caching is enabled
246246 self .cache_year_filter = None # Track what filters the cache uses
247247 self .cache_since_filter = None
248+ self .display_start_date = None # Display filter (--mtd/--since) separate from cache
248249 self .config_dir = config_dir # Custom config directory (None = default ~/.moneyflow)
249250 self .encryption_key : Optional [bytes ] = None # Encryption key for cache (set after login)
250251 # Controller will be initialized after data_manager is ready
@@ -367,27 +368,27 @@ def _initialize_managers(
367368 def _determine_date_range (self ):
368369 """Determine date range based on CLI arguments.
369370
371+ Separates display filtering (--mtd, --since) from cache behavior:
372+ - display_start_date: What the user wants to VIEW (filters the UI)
373+ - cache filters: What the cache actually STORES (preserved on refresh)
374+
370375 Returns:
371- tuple: (start_date, end_date , cache_year_filter, cache_since_filter)
376+ tuple: (display_start_date , cache_year_filter, cache_since_filter)
372377 """
378+ # Display filter - what user wants to see
373379 if self .custom_start_date :
374- start_date = self .custom_start_date
375- end_date = datetime .now ().strftime ("%Y-%m-%d" )
376- cache_year_filter = None
377- cache_since_filter = self .custom_start_date
380+ display_start_date = self .custom_start_date
378381 elif self .start_year :
379- start_date = f"{ self .start_year } -01-01"
380- end_date = datetime .now ().strftime ("%Y-%m-%d" )
381- cache_year_filter = self .start_year
382- cache_since_filter = None
382+ display_start_date = f"{ self .start_year } -01-01"
383383 else :
384- # Fetch ALL transactions (no date filter for offline-first approach)
385- start_date = None
386- end_date = None
387- cache_year_filter = None
388- cache_since_filter = None
384+ display_start_date = None
385+
386+ # Cache filters - determined by existing cache or first fetch
387+ # These are set later based on what's actually cached
388+ cache_year_filter = None
389+ cache_since_filter = None
389390
390- return start_date , end_date , cache_year_filter , cache_since_filter
391+ return display_start_date , cache_year_filter , cache_since_filter
391392
392393 @staticmethod
393394 def _filter_df_by_start_date (df : pl .DataFrame , start_date : str ) -> pl .DataFrame :
@@ -1027,14 +1028,15 @@ async def fetch_operation():
10271028 )
10281029
10291030 # Save to cache for next time (only if --cache was passed)
1031+ # Always save as full cache (no filters) - display filters applied separately
10301032 if self .cache_manager :
10311033 loading_status .update ("💾 Saving to cache..." )
10321034 self .cache_manager .save_cache (
10331035 transactions_df = df ,
10341036 categories = categories ,
10351037 category_groups = category_groups ,
1036- year = self . cache_year_filter ,
1037- since = self . cache_since_filter ,
1038+ year = None , # Full cache - no year filter
1039+ since = None , # Full cache - no since filter
10381040 )
10391041 loading_status .update (f"✅ Loaded { len (df ):,} transactions and cached!" )
10401042 else :
@@ -1102,14 +1104,12 @@ def update_progress(msg: str) -> None:
11021104
11031105 try :
11041106 # Fetch the expired tier from API
1107+ # Use helper methods to ensure both dates are always provided (API requirement)
11051108 if is_hot_refresh :
1106- fetch_start , fetch_end = boundary_str , None
1107- loading_status .update (f"📊 Fetching transactions since { boundary_str } ..." )
1109+ fetch_start , fetch_end = self . cache_manager . get_hot_refresh_date_range ()
1110+ loading_status .update (f"📊 Fetching transactions since { fetch_start } ..." )
11081111 else :
1109- fetch_start , fetch_end = (
1110- None ,
1111- (boundary_date - timedelta (days = 1 )).strftime ("%Y-%m-%d" ),
1112- )
1112+ fetch_start , fetch_end = self .cache_manager .get_cold_refresh_date_range ()
11131113 loading_status .update (
11141114 f"📊 Fetching historical transactions before { boundary_str } ..."
11151115 )
@@ -1322,8 +1322,8 @@ async def initialize_data(self) -> None:
13221322 profile_dir = determined_profile_dir , backend_type = determined_backend_type
13231323 )
13241324
1325- # Step 4: Determine date range
1326- start_date , end_date , self .cache_year_filter , self .cache_since_filter = (
1325+ # Step 4: Determine display filter (separate from cache)
1326+ self . display_start_date , self .cache_year_filter , self .cache_since_filter = (
13271327 self ._determine_date_range ()
13281328 )
13291329
@@ -1335,9 +1335,9 @@ async def initialize_data(self) -> None:
13351335 df , categories , category_groups = cached_data
13361336 # Filter cached data to match requested date range (e.g., --mtd)
13371337 # Cache may contain more data than requested (e.g., full year cache for MTD request)
1338- if start_date :
1338+ if self . display_start_date :
13391339 original_count = len (df )
1340- df = self ._filter_df_by_start_date (df , start_date )
1340+ df = self ._filter_df_by_start_date (df , self . display_start_date )
13411341 if len (df ) < original_count :
13421342 loading_status .update (
13431343 f"📦 Filtered cache: { len (df ):,} of { original_count :,} transactions"
@@ -1348,32 +1348,43 @@ async def initialize_data(self) -> None:
13481348 if partial_result :
13491349 df , categories , category_groups = partial_result
13501350 # Filter if needed
1351- if start_date :
1351+ if self . display_start_date :
13521352 original_count = len (df )
1353- df = self ._filter_df_by_start_date (df , start_date )
1353+ df = self ._filter_df_by_start_date (df , self . display_start_date )
13541354 if len (df ) < original_count :
13551355 loading_status .update (
13561356 f"📦 Filtered: { len (df ):,} of { original_count :,} transactions"
13571357 )
13581358 else :
13591359 # Partial refresh failed, fall back to full fetch
1360+ # Always fetch full data - display filter applied after
13601361 fetch_result = await self ._fetch_data_with_retry (
1361- creds , start_date , end_date , loading_status
1362+ creds , None , None , loading_status
13621363 )
13631364 if fetch_result is None :
13641365 has_error = True
13651366 return
13661367 df , categories , category_groups = fetch_result
13671368 else :
13681369 # Step 6: Full fetch from API (BOTH, ALL, or no cache)
1370+ # Always fetch full data - display filter applied after
13691371 fetch_result = await self ._fetch_data_with_retry (
1370- creds , start_date , end_date , loading_status
1372+ creds , None , None , loading_status
13711373 )
13721374 if fetch_result is None :
13731375 has_error = True
13741376 return
13751377 df , categories , category_groups = fetch_result
13761378
1379+ # Apply display filter after fetch (cache stores full data)
1380+ if self .display_start_date and strategy != RefreshStrategy .NONE :
1381+ original_count = len (df )
1382+ df = self ._filter_df_by_start_date (df , self .display_start_date )
1383+ if len (df ) < original_count :
1384+ loading_status .update (
1385+ f"📦 Filtered: { len (df ):,} of { original_count :,} transactions"
1386+ )
1387+
13771388 # Step 7: Store data
13781389 self ._store_data (df , categories , category_groups )
13791390
0 commit comments