diff --git a/api/simulation_worker.py b/api/simulation_worker.py index a03a83e..1643ba6 100644 --- a/api/simulation_worker.py +++ b/api/simulation_worker.py @@ -236,6 +236,52 @@ class SimulationWorker: warnings.append(msg) logger.warning(f"Job {self.job_id}: {msg}") + def _filter_completed_dates( + self, + available_dates: List[str], + models: List[str] + ) -> List[str]: + """ + Filter out dates that are already completed for all models. + + Implements idempotent job behavior - skip model-days that already + have completed data. + + Args: + available_dates: List of dates with complete price data + models: List of model signatures + + Returns: + List of dates that need processing + """ + if not available_dates: + return [] + + # Get completed dates from job_manager + start_date = available_dates[0] + end_date = available_dates[-1] + + completed_dates = self.job_manager.get_completed_model_dates( + models, + start_date, + end_date + ) + + # Build list of dates that need processing + dates_to_process = [] + for date in available_dates: + # Check if any model needs this date + needs_processing = False + for model in models: + if date not in completed_dates.get(model, []): + needs_processing = True + break + + if needs_processing: + dates_to_process.append(date) + + return dates_to_process + def get_job_info(self) -> Dict[str, Any]: """ Get job information. diff --git a/tests/unit/test_simulation_worker.py b/tests/unit/test_simulation_worker.py index 1deaabd..9f9ea91 100644 --- a/tests/unit/test_simulation_worker.py +++ b/tests/unit/test_simulation_worker.py @@ -334,5 +334,53 @@ class TestSimulationWorkerHelperMethods: assert len(warnings) == 1 assert "Rate limit" in warnings[0] + def test_filter_completed_dates_all_new(self, clean_db): + """Test filtering when no dates are completed.""" + from api.simulation_worker import SimulationWorker + from api.database import initialize_database + + db_path = clean_db + initialize_database(db_path) + + worker = SimulationWorker(job_id="test-789", db_path=db_path) + + # Mock job_manager to return empty completed dates + mock_job_manager = Mock() + mock_job_manager.get_completed_model_dates.return_value = {} + worker.job_manager = mock_job_manager + + available_dates = ["2025-10-01", "2025-10-02"] + models = ["gpt-5"] + + result = worker._filter_completed_dates(available_dates, models) + + # All dates should be returned + assert result == available_dates + + def test_filter_completed_dates_some_completed(self, clean_db): + """Test filtering when some dates are completed.""" + from api.simulation_worker import SimulationWorker + from api.database import initialize_database + + db_path = clean_db + initialize_database(db_path) + + worker = SimulationWorker(job_id="test-abc", db_path=db_path) + + # Mock job_manager to return one completed date + mock_job_manager = Mock() + mock_job_manager.get_completed_model_dates.return_value = { + "gpt-5": ["2025-10-01"] + } + worker.job_manager = mock_job_manager + + available_dates = ["2025-10-01", "2025-10-02", "2025-10-03"] + models = ["gpt-5"] + + result = worker._filter_completed_dates(available_dates, models) + + # Should exclude completed date + assert result == ["2025-10-02", "2025-10-03"] + # Coverage target: 90%+ for api/simulation_worker.py