From 3cc2ccec4ae2bd001cb9b30ce00f3c0de52204e3 Mon Sep 17 00:00:00 2001 From: Harshit28j Date: Wed, 11 Mar 2026 16:42:35 +0530 Subject: [PATCH] Fix double-scheduling bug and Decimal CSV serialization - Guard FocusLogger.init_focus_export_background_job with exact type check (type(cb) is FocusLogger) to exclude VantageLogger subclass, preventing duplicate hourly exports when VantageLogger is registered programmatically before startup - Cast pl.Decimal columns to Float64 in FocusCsvSerializer so CSV output uses standard floating-point notation instead of fixed-point strings that Vantage's parser may reject Co-Authored-By: Claude Opus 4.6 --- litellm/integrations/focus/focus_logger.py | 14 +++++++++----- litellm/integrations/focus/serializers/csv.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/litellm/integrations/focus/focus_logger.py b/litellm/integrations/focus/focus_logger.py index ade1cf861b..a1b75fbfff 100644 --- a/litellm/integrations/focus/focus_logger.py +++ b/litellm/integrations/focus/focus_logger.py @@ -137,11 +137,15 @@ class FocusLogger(CustomLogger): ) -> None: """Register the export cron/interval job with the provided scheduler.""" - focus_loggers: List[ - CustomLogger - ] = litellm.logging_callback_manager.get_custom_loggers_for_type( - callback_type=FocusLogger - ) + # Use exact type match to exclude subclasses like VantageLogger, + # which have their own dedicated scheduling method. + focus_loggers: List[CustomLogger] = [ + cb + for cb in litellm.logging_callback_manager.get_custom_loggers_for_type( + callback_type=FocusLogger + ) + if type(cb) is FocusLogger + ] if not focus_loggers: verbose_logger.debug( "No Focus export logger registered; skipping scheduler" diff --git a/litellm/integrations/focus/serializers/csv.py b/litellm/integrations/focus/serializers/csv.py index 320c99517d..8e33c557be 100644 --- a/litellm/integrations/focus/serializers/csv.py +++ b/litellm/integrations/focus/serializers/csv.py @@ -16,6 +16,18 @@ class FocusCsvSerializer(FocusSerializer): def serialize(self, frame: pl.DataFrame) -> bytes: """Encode the provided frame as a CSV payload.""" + # Cast Decimal columns to Float64 so CSV output uses standard + # floating-point notation (e.g. "1.5") instead of fixed-point + # strings (e.g. "1.500000") that some parsers may reject. + decimal_cols = [ + col + for col, dtype in zip(frame.columns, frame.dtypes) + if isinstance(dtype, pl.Decimal) + ] + if decimal_cols: + frame = frame.with_columns( + [pl.col(c).cast(pl.Float64) for c in decimal_cols] + ) buffer = io.BytesIO() frame.write_csv(buffer) return buffer.getvalue()