{
    "sourceFile": "gui/prediction_handler.py",
    "activeCommit": 0,
    "commits": [
        {
            "activePatchIndex": 5,
            "patches": [
                {
                    "date": 1745236106567,
                    "content": "Index: \n===================================================================\n--- \n+++ \n"
                },
                {
                    "date": 1745317773906,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -40,9 +40,9 @@\n     Handles running predictions in a separate thread to avoid GUI freezes.\r\n     \"\"\"\r\n     # --- Signals ---\r\n     # Emits a list of dictionaries, each representing a file row for the table\r\n-    # Dict format: {'original_path': str, 'predicted_name': str | None, 'status': str, 'details': str | None, 'source_asset': str}\r\n+    # Dict format: {'original_path': str, 'predicted_asset_name': str | None, 'predicted_output_name': str | None, 'status': str, 'details': str | None, 'source_asset': str}\r\n     prediction_results_ready = Signal(list)\r\n     # Emitted when all predictions for a batch are done\r\n     prediction_finished = Signal()\r\n     # Emitted for status updates\r\n@@ -78,34 +78,45 @@\n                 log.error(f\"Detailed prediction failed critically for {input_path_str}. Adding asset-level error.\")\r\n                 # Add a single error entry for the whole asset if the method returns None\r\n                 asset_results.append({\r\n                     'original_path': source_asset_name, # Use asset name as placeholder\r\n-                    'predicted_name': None,\r\n+                    'predicted_asset_name': None, # New key\r\n+                    'predicted_output_name': None, # New key\r\n                     'status': 'Error',\r\n                     'details': 'Critical prediction failure (check logs)',\r\n                     'source_asset': source_asset_name\r\n                 })\r\n             else:\r\n                 log.debug(f\"Received {len(detailed_predictions)} detailed predictions for {input_path_str}.\")\r\n-                # Add source_asset key to each dictionary and append to the main list\r\n+                # Add source_asset key and ensure correct keys exist\r\n                 for prediction_dict in detailed_predictions:\r\n-                    prediction_dict['source_asset'] = source_asset_name\r\n-                    asset_results.append(prediction_dict)\r\n+                    # Ensure all expected keys are present, even if None\r\n+                    result_entry = {\r\n+                        'original_path': prediction_dict.get('original_path', '[Missing Path]'),\r\n+                        'predicted_asset_name': prediction_dict.get('predicted_asset_name'), # New key\r\n+                        'predicted_output_name': prediction_dict.get('predicted_output_name'), # New key\r\n+                        'status': prediction_dict.get('status', 'Error'),\r\n+                        'details': prediction_dict.get('details', '[Missing Details]'),\r\n+                        'source_asset': source_asset_name # Add the source asset identifier\r\n+                    }\r\n+                    asset_results.append(result_entry)\r\n \r\n         except AssetProcessingError as e: # Catch errors during processor instantiation or prediction setup\r\n              log.error(f\"Asset processing error during prediction setup for {input_path_str}: {e}\")\r\n              asset_results.append({\r\n                  'original_path': source_asset_name,\r\n-                 'predicted_name': None,\r\n+                 'predicted_asset_name': None,\r\n+                 'predicted_output_name': None,\r\n                  'status': 'Error',\r\n                  'details': f'Asset Error: {e}',\r\n                  'source_asset': source_asset_name\r\n              })\r\n         except Exception as e: # Catch unexpected errors\r\n             log.exception(f\"Unexpected error during prediction for {input_path_str}: {e}\")\r\n             asset_results.append({\r\n                 'original_path': source_asset_name,\r\n-                'predicted_name': None,\r\n+                'predicted_asset_name': None,\r\n+                'predicted_output_name': None,\r\n                 'status': 'Error',\r\n                 'details': f'Unexpected Error: {e}',\r\n                 'source_asset': source_asset_name\r\n             })\r\n@@ -180,9 +191,10 @@\n                         # We might not know which input path failed here easily without more mapping\r\n                         # Add a generic error?\r\n                         all_file_results.append({\r\n                             'original_path': '[Unknown Asset - Executor Error]',\r\n-                            'predicted_name': None,\r\n+                            'predicted_asset_name': None,\r\n+                            'predicted_output_name': None,\r\n                             'status': 'Error',\r\n                             'details': f'Executor Error: {exc}',\r\n                             'source_asset': '[Unknown]'\r\n                         })\r\n@@ -192,9 +204,10 @@\n              self.status_message.emit(f\"Error during prediction setup: {pool_exc}\", 5000)\r\n              # Add a generic error if the pool fails\r\n              all_file_results.append({\r\n                  'original_path': '[Prediction Pool Error]',\r\n-                 'predicted_name': None,\r\n+                 'predicted_asset_name': None,\r\n+                 'predicted_output_name': None,\r\n                  'status': 'Error',\r\n                  'details': f'Pool Error: {pool_exc}',\r\n                  'source_asset': '[System]'\r\n              })\r\n"
                },
                {
                    "date": 1745332997337,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -0,0 +1,228 @@\n+# gui/prediction_handler.py\r\n+import logging\r\n+from pathlib import Path\r\n+import time # For potential delays if needed\r\n+import os # For cpu_count\r\n+from concurrent.futures import ThreadPoolExecutor, as_completed # For parallel prediction\r\n+\r\n+# --- PySide6 Imports ---\r\n+from PySide6.QtCore import QObject, Signal\r\n+\r\n+# --- Backend Imports ---\r\n+# Adjust path to ensure modules can be found relative to this file's location\r\n+import sys\r\n+script_dir = Path(__file__).parent\r\n+project_root = script_dir.parent\r\n+if str(project_root) not in sys.path:\r\n+    sys.path.insert(0, str(project_root))\r\n+\r\n+try:\r\n+    from configuration import Configuration, ConfigurationError\r\n+    from asset_processor import AssetProcessor, AssetProcessingError\r\n+    BACKEND_AVAILABLE = True\r\n+except ImportError as e:\r\n+    print(f\"ERROR (PredictionHandler): Failed to import backend modules: {e}\")\r\n+    # Define placeholders if imports fail\r\n+    Configuration = None\r\n+    AssetProcessor = None\r\n+    ConfigurationError = Exception\r\n+    AssetProcessingError = Exception\r\n+    BACKEND_AVAILABLE = False\r\n+\r\n+log = logging.getLogger(__name__)\r\n+# Basic config if logger hasn't been set up elsewhere\r\n+if not log.hasHandlers():\r\n+     logging.basicConfig(level=logging.INFO, format='%(levelname)s (PredictHandler): %(message)s')\r\n+\r\n+\r\n+class PredictionHandler(QObject):\r\n+    \"\"\"\r\n+    Handles running predictions in a separate thread to avoid GUI freezes.\r\n+    \"\"\"\r\n+    # --- Signals ---\r\n+    # Emits a list of dictionaries, each representing a file row for the table\r\n+    # Dict format: {'original_path': str, 'predicted_asset_name': str | None, 'predicted_output_name': str | None, 'status': str, 'details': str | None, 'source_asset': str}\r\n+    prediction_results_ready = Signal(list)\r\n+    # Emitted when all predictions for a batch are done\r\n+    prediction_finished = Signal()\r\n+    # Emitted for status updates\r\n+    status_message = Signal(str, int)\r\n+\r\n+    def __init__(self, parent=None):\r\n+        super().__init__(parent)\r\n+        self._is_running = False\r\n+        # No explicit cancel needed for prediction for now, it should be fast per-item\r\n+\r\n+    @property\r\n+    def is_running(self):\r\n+        return self._is_running\r\n+\r\n+    def _predict_single_asset(self, input_path_str: str, config: Configuration) -> list[dict]:\r\n+        \"\"\"\r\n+        Helper method to predict a single asset. Runs within the ThreadPoolExecutor.\r\n+        Returns a list of prediction dictionaries for the asset, or a single error dict.\r\n+        \"\"\"\r\n+        input_path = Path(input_path_str)\r\n+        source_asset_name = input_path.name # For reference in the results\r\n+        asset_results = []\r\n+        try:\r\n+            # Create AssetProcessor instance (needs dummy output path)\r\n+            # Ensure AssetProcessor is thread-safe or create a new instance per thread.\r\n+            # Based on its structure (using temp dirs), creating new instances should be safe.\r\n+            processor = AssetProcessor(input_path, config, Path(\".\")) # Dummy output path\r\n+\r\n+            # Get detailed file predictions\r\n+            detailed_predictions = processor.get_detailed_file_predictions()\r\n+\r\n+            if detailed_predictions is None:\r\n+                log.error(f\"Detailed prediction failed critically for {input_path_str}. Adding asset-level error.\")\r\n+                # Add a single error entry for the whole asset if the method returns None\r\n+                asset_results.append({\r\n+                    'original_path': source_asset_name, # Use asset name as placeholder\r\n+                    'predicted_asset_name': None, # New key\r\n+                    'predicted_output_name': None, # New key\r\n+                    'status': 'Error',\r\n+                    'details': 'Critical prediction failure (check logs)',\r\n+                    'source_asset': source_asset_name\r\n+                })\r\n+            else:\r\n+                log.debug(f\"Received {len(detailed_predictions)} detailed predictions for {input_path_str}.\")\r\n+                # Add source_asset key and ensure correct keys exist\r\n+                for prediction_dict in detailed_predictions:\r\n+                    # Ensure all expected keys are present, even if None\r\n+                    result_entry = {\r\n+                        'original_path': prediction_dict.get('original_path', '[Missing Path]'),\r\n+                        'predicted_asset_name': prediction_dict.get('predicted_asset_name'), # New key\r\n+                        'predicted_output_name': prediction_dict.get('predicted_output_name'), # New key\r\n+                        'status': prediction_dict.get('status', 'Error'),\r\n+                        'details': prediction_dict.get('details', '[Missing Details]'),\r\n+                        'source_asset': source_asset_name # Add the source asset identifier\r\n+                    }\r\n+                    asset_results.append(result_entry)\r\n+\r\n+        except AssetProcessingError as e: # Catch errors during processor instantiation or prediction setup\r\n+             log.error(f\"Asset processing error during prediction setup for {input_path_str}: {e}\")\r\n+             asset_results.append({\r\n+                 'original_path': source_asset_name,\r\n+                 'predicted_asset_name': None,\r\n+                 'predicted_output_name': None,\r\n+                 'status': 'Error',\r\n+                 'details': f'Asset Error: {e}',\r\n+                 'source_asset': source_asset_name\r\n+             })\r\n+        except Exception as e: # Catch unexpected errors\r\n+            log.exception(f\"Unexpected error during prediction for {input_path_str}: {e}\")\r\n+            asset_results.append({\r\n+                'original_path': source_asset_name,\r\n+                'predicted_asset_name': None,\r\n+                'predicted_output_name': None,\r\n+                'status': 'Error',\r\n+                'details': f'Unexpected Error: {e}',\r\n+                'source_asset': source_asset_name\r\n+            })\r\n+        finally:\r\n+            # Cleanup for the single asset prediction if needed (AssetProcessor handles its own temp dir)\r\n+            pass\r\n+        return asset_results\r\n+\r\n+\r\n+    def run_prediction(self, input_paths: list[str], preset_name: str):\r\n+        \"\"\"\r\n+        Runs the prediction logic for the given paths and preset using a ThreadPoolExecutor.\r\n+        This method is intended to be run in a separate QThread.\r\n+        \"\"\"\r\n+        if self._is_running:\r\n+            log.warning(\"Prediction is already running.\")\r\n+            return\r\n+        if not BACKEND_AVAILABLE:\r\n+            log.error(\"Backend modules not available. Cannot run prediction.\")\r\n+            self.status_message.emit(\"Error: Backend components missing.\", 5000)\r\n+            self.prediction_finished.emit()\r\n+            return\r\n+        if not preset_name:\r\n+            log.warning(\"No preset selected for prediction.\")\r\n+            self.status_message.emit(\"No preset selected.\", 3000)\r\n+            self.prediction_finished.emit()\r\n+            return\r\n+\r\n+        self._is_running = True\r\n+        thread_id = QThread.currentThreadId() # Get current thread ID\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PredictionHandler.run_prediction. Starting run for {len(input_paths)} items, Preset='{preset_name}'\")\r\n+        self.status_message.emit(f\"Updating preview for {len(input_paths)} items...\", 0)\r\n+\r\n+        config = None # Load config once if possible\r\n+        try:\r\n+            config = Configuration(preset_name)\r\n+        except ConfigurationError as e:\r\n+            log.error(f\"Failed to load configuration for preset '{preset_name}': {e}\")\r\n+            self.status_message.emit(f\"Error loading preset '{preset_name}': {e}\", 5000)\r\n+            # Emit error for all items? Or just finish? Finish for now.\r\n+            self.prediction_finished.emit()\r\n+            self._is_running = False\r\n+            return\r\n+        except Exception as e:\r\n+            log.exception(f\"Unexpected error loading configuration for preset '{preset_name}': {e}\")\r\n+            self.status_message.emit(f\"Unexpected error loading preset '{preset_name}'.\", 5000)\r\n+            self.prediction_finished.emit()\r\n+            return\r\n+\r\n+        all_file_results = [] # Accumulate results here\r\n+        futures = []\r\n+        # Determine number of workers - use half the cores, minimum 1, max 8?\r\n+        max_workers = min(max(1, (os.cpu_count() or 1) // 2), 8)\r\n+        log.info(f\"Using ThreadPoolExecutor with max_workers={max_workers} for prediction.\")\r\n+\r\n+        try:\r\n+            with ThreadPoolExecutor(max_workers=max_workers) as executor:\r\n+                # Submit tasks for each input path\r\n+                for input_path_str in input_paths:\r\n+                    future = executor.submit(self._predict_single_asset, input_path_str, config)\r\n+                    futures.append(future)\r\n+\r\n+                # Process results as they complete\r\n+                for future in as_completed(futures):\r\n+                    try:\r\n+                        # Result is a list of dicts for one asset\r\n+                        asset_result_list = future.result()\r\n+                        if asset_result_list: # Check if list is not empty\r\n+                            all_file_results.extend(asset_result_list)\r\n+                    except Exception as exc:\r\n+                        # This catches errors within the future execution itself if not handled by _predict_single_asset\r\n+                        log.error(f'Prediction task generated an exception: {exc}', exc_info=True)\r\n+                        # We might not know which input path failed here easily without more mapping\r\n+                        # Add a generic error?\r\n+                        all_file_results.append({\r\n+                            'original_path': '[Unknown Asset - Executor Error]',\r\n+                            'predicted_asset_name': None,\r\n+                            'predicted_output_name': None,\r\n+                            'status': 'Error',\r\n+                            'details': f'Executor Error: {exc}',\r\n+                            'source_asset': '[Unknown]'\r\n+                        })\r\n+\r\n+        except Exception as pool_exc:\r\n+             log.exception(f\"An error occurred with the prediction ThreadPoolExecutor: {pool_exc}\")\r\n+             self.status_message.emit(f\"Error during prediction setup: {pool_exc}\", 5000)\r\n+             # Add a generic error if the pool fails\r\n+             all_file_results.append({\r\n+                 'original_path': '[Prediction Pool Error]',\r\n+                 'predicted_asset_name': None,\r\n+                 'predicted_output_name': None,\r\n+                 'status': 'Error',\r\n+                 'details': f'Pool Error: {pool_exc}',\r\n+                 'source_asset': '[System]'\r\n+             })\r\n+\r\n+        # Emit the combined list of detailed file results at the end\r\n+        log.info(f\"Parallel prediction run finished. Preparing to emit {len(all_file_results)} file results.\")\r\n+        # <<< Add logging before emit >>>\r\n+        log.debug(f\"Type of all_file_results before emit: {type(all_file_results)}\")\r\n+        try:\r\n+            log.debug(f\"Content of all_file_results (first 5) before emit: {all_file_results[:5]}\")\r\n+        except Exception as e:\r\n+             log.error(f\"Error logging all_file_results content: {e}\")\r\n+        # <<< End added logging >>>\r\n+        self.prediction_results_ready.emit(all_file_results)\r\n+        self.status_message.emit(\"Preview update complete.\", 3000)\r\n+        self.prediction_finished.emit()\r\n+        self._is_running = False\n\\ No newline at end of file\n"
                },
                {
                    "date": 1745333025359,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -213,243 +213,20 @@\n                  'source_asset': '[System]'\r\n              })\r\n \r\n         # Emit the combined list of detailed file results at the end\r\n-        log.info(f\"Parallel prediction run finished. Preparing to emit {len(all_file_results)} file results.\")\r\n+        # Note: thread_id was already defined earlier in this function\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] Parallel prediction run finished. Preparing to emit {len(all_file_results)} file results.\")\r\n         # <<< Add logging before emit >>>\r\n-        log.debug(f\"Type of all_file_results before emit: {type(all_file_results)}\")\r\n+        log.debug(f\"[{time.time():.4f}][T:{thread_id}] Type of all_file_results before emit: {type(all_file_results)}\")\r\n         try:\r\n-            log.debug(f\"Content of all_file_results (first 5) before emit: {all_file_results[:5]}\")\r\n+            log.debug(f\"[{time.time():.4f}][T:{thread_id}] Content of all_file_results (first 5) before emit: {all_file_results[:5]}\")\r\n         except Exception as e:\r\n-             log.error(f\"Error logging all_file_results content: {e}\")\r\n+             log.error(f\"[{time.time():.4f}][T:{thread_id}] Error logging all_file_results content: {e}\")\r\n         # <<< End added logging >>>\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] Emitting prediction_results_ready signal...\")\r\n         self.prediction_results_ready.emit(all_file_results)\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] Emitted prediction_results_ready signal.\")\r\n         self.status_message.emit(\"Preview update complete.\", 3000)\r\n         self.prediction_finished.emit()\r\n-        self._is_running = False\n-# gui/prediction_handler.py\r\n-import logging\r\n-from pathlib import Path\r\n-import time # For potential delays if needed\r\n-import os # For cpu_count\r\n-from concurrent.futures import ThreadPoolExecutor, as_completed # For parallel prediction\r\n-\r\n-# --- PySide6 Imports ---\r\n-from PySide6.QtCore import QObject, Signal\r\n-\r\n-# --- Backend Imports ---\r\n-# Adjust path to ensure modules can be found relative to this file's location\r\n-import sys\r\n-script_dir = Path(__file__).parent\r\n-project_root = script_dir.parent\r\n-if str(project_root) not in sys.path:\r\n-    sys.path.insert(0, str(project_root))\r\n-\r\n-try:\r\n-    from configuration import Configuration, ConfigurationError\r\n-    from asset_processor import AssetProcessor, AssetProcessingError\r\n-    BACKEND_AVAILABLE = True\r\n-except ImportError as e:\r\n-    print(f\"ERROR (PredictionHandler): Failed to import backend modules: {e}\")\r\n-    # Define placeholders if imports fail\r\n-    Configuration = None\r\n-    AssetProcessor = None\r\n-    ConfigurationError = Exception\r\n-    AssetProcessingError = Exception\r\n-    BACKEND_AVAILABLE = False\r\n-\r\n-log = logging.getLogger(__name__)\r\n-# Basic config if logger hasn't been set up elsewhere\r\n-if not log.hasHandlers():\r\n-     logging.basicConfig(level=logging.INFO, format='%(levelname)s (PredictHandler): %(message)s')\r\n-\r\n-\r\n-class PredictionHandler(QObject):\r\n-    \"\"\"\r\n-    Handles running predictions in a separate thread to avoid GUI freezes.\r\n-    \"\"\"\r\n-    # --- Signals ---\r\n-    # Emits a list of dictionaries, each representing a file row for the table\r\n-    # Dict format: {'original_path': str, 'predicted_asset_name': str | None, 'predicted_output_name': str | None, 'status': str, 'details': str | None, 'source_asset': str}\r\n-    prediction_results_ready = Signal(list)\r\n-    # Emitted when all predictions for a batch are done\r\n-    prediction_finished = Signal()\r\n-    # Emitted for status updates\r\n-    status_message = Signal(str, int)\r\n-\r\n-    def __init__(self, parent=None):\r\n-        super().__init__(parent)\r\n         self._is_running = False\r\n-        # No explicit cancel needed for prediction for now, it should be fast per-item\r\n-\r\n-    @property\r\n-    def is_running(self):\r\n-        return self._is_running\r\n-\r\n-    def _predict_single_asset(self, input_path_str: str, config: Configuration) -> list[dict]:\r\n-        \"\"\"\r\n-        Helper method to predict a single asset. Runs within the ThreadPoolExecutor.\r\n-        Returns a list of prediction dictionaries for the asset, or a single error dict.\r\n-        \"\"\"\r\n-        input_path = Path(input_path_str)\r\n-        source_asset_name = input_path.name # For reference in the results\r\n-        asset_results = []\r\n-        try:\r\n-            # Create AssetProcessor instance (needs dummy output path)\r\n-            # Ensure AssetProcessor is thread-safe or create a new instance per thread.\r\n-            # Based on its structure (using temp dirs), creating new instances should be safe.\r\n-            processor = AssetProcessor(input_path, config, Path(\".\")) # Dummy output path\r\n-\r\n-            # Get detailed file predictions\r\n-            detailed_predictions = processor.get_detailed_file_predictions()\r\n-\r\n-            if detailed_predictions is None:\r\n-                log.error(f\"Detailed prediction failed critically for {input_path_str}. Adding asset-level error.\")\r\n-                # Add a single error entry for the whole asset if the method returns None\r\n-                asset_results.append({\r\n-                    'original_path': source_asset_name, # Use asset name as placeholder\r\n-                    'predicted_asset_name': None, # New key\r\n-                    'predicted_output_name': None, # New key\r\n-                    'status': 'Error',\r\n-                    'details': 'Critical prediction failure (check logs)',\r\n-                    'source_asset': source_asset_name\r\n-                })\r\n-            else:\r\n-                log.debug(f\"Received {len(detailed_predictions)} detailed predictions for {input_path_str}.\")\r\n-                # Add source_asset key and ensure correct keys exist\r\n-                for prediction_dict in detailed_predictions:\r\n-                    # Ensure all expected keys are present, even if None\r\n-                    result_entry = {\r\n-                        'original_path': prediction_dict.get('original_path', '[Missing Path]'),\r\n-                        'predicted_asset_name': prediction_dict.get('predicted_asset_name'), # New key\r\n-                        'predicted_output_name': prediction_dict.get('predicted_output_name'), # New key\r\n-                        'status': prediction_dict.get('status', 'Error'),\r\n-                        'details': prediction_dict.get('details', '[Missing Details]'),\r\n-                        'source_asset': source_asset_name # Add the source asset identifier\r\n-                    }\r\n-                    asset_results.append(result_entry)\r\n-\r\n-        except AssetProcessingError as e: # Catch errors during processor instantiation or prediction setup\r\n-             log.error(f\"Asset processing error during prediction setup for {input_path_str}: {e}\")\r\n-             asset_results.append({\r\n-                 'original_path': source_asset_name,\r\n-                 'predicted_asset_name': None,\r\n-                 'predicted_output_name': None,\r\n-                 'status': 'Error',\r\n-                 'details': f'Asset Error: {e}',\r\n-                 'source_asset': source_asset_name\r\n-             })\r\n-        except Exception as e: # Catch unexpected errors\r\n-            log.exception(f\"Unexpected error during prediction for {input_path_str}: {e}\")\r\n-            asset_results.append({\r\n-                'original_path': source_asset_name,\r\n-                'predicted_asset_name': None,\r\n-                'predicted_output_name': None,\r\n-                'status': 'Error',\r\n-                'details': f'Unexpected Error: {e}',\r\n-                'source_asset': source_asset_name\r\n-            })\r\n-        finally:\r\n-            # Cleanup for the single asset prediction if needed (AssetProcessor handles its own temp dir)\r\n-            pass\r\n-        return asset_results\r\n-\r\n-\r\n-    def run_prediction(self, input_paths: list[str], preset_name: str):\r\n-        \"\"\"\r\n-        Runs the prediction logic for the given paths and preset using a ThreadPoolExecutor.\r\n-        This method is intended to be run in a separate QThread.\r\n-        \"\"\"\r\n-        if self._is_running:\r\n-            log.warning(\"Prediction is already running.\")\r\n-            return\r\n-        if not BACKEND_AVAILABLE:\r\n-            log.error(\"Backend modules not available. Cannot run prediction.\")\r\n-            self.status_message.emit(\"Error: Backend components missing.\", 5000)\r\n-            self.prediction_finished.emit()\r\n-            return\r\n-        if not preset_name:\r\n-            log.warning(\"No preset selected for prediction.\")\r\n-            self.status_message.emit(\"No preset selected.\", 3000)\r\n-            self.prediction_finished.emit()\r\n-            return\r\n-\r\n-        self._is_running = True\r\n-        log.info(f\"Starting prediction run for {len(input_paths)} items, Preset='{preset_name}'\")\r\n-        self.status_message.emit(f\"Updating preview for {len(input_paths)} items...\", 0)\r\n-\r\n-        config = None # Load config once if possible\r\n-        try:\r\n-            config = Configuration(preset_name)\r\n-        except ConfigurationError as e:\r\n-            log.error(f\"Failed to load configuration for preset '{preset_name}': {e}\")\r\n-            self.status_message.emit(f\"Error loading preset '{preset_name}': {e}\", 5000)\r\n-            # Emit error for all items? Or just finish? Finish for now.\r\n-            self.prediction_finished.emit()\r\n-            self._is_running = False\r\n-            return\r\n-        except Exception as e:\r\n-            log.exception(f\"Unexpected error loading configuration for preset '{preset_name}': {e}\")\r\n-            self.status_message.emit(f\"Unexpected error loading preset '{preset_name}'.\", 5000)\r\n-            self.prediction_finished.emit()\r\n-            return\r\n-\r\n-        all_file_results = [] # Accumulate results here\r\n-        futures = []\r\n-        # Determine number of workers - use half the cores, minimum 1, max 8?\r\n-        max_workers = min(max(1, (os.cpu_count() or 1) // 2), 8)\r\n-        log.info(f\"Using ThreadPoolExecutor with max_workers={max_workers} for prediction.\")\r\n-\r\n-        try:\r\n-            with ThreadPoolExecutor(max_workers=max_workers) as executor:\r\n-                # Submit tasks for each input path\r\n-                for input_path_str in input_paths:\r\n-                    future = executor.submit(self._predict_single_asset, input_path_str, config)\r\n-                    futures.append(future)\r\n-\r\n-                # Process results as they complete\r\n-                for future in as_completed(futures):\r\n-                    try:\r\n-                        # Result is a list of dicts for one asset\r\n-                        asset_result_list = future.result()\r\n-                        if asset_result_list: # Check if list is not empty\r\n-                            all_file_results.extend(asset_result_list)\r\n-                    except Exception as exc:\r\n-                        # This catches errors within the future execution itself if not handled by _predict_single_asset\r\n-                        log.error(f'Prediction task generated an exception: {exc}', exc_info=True)\r\n-                        # We might not know which input path failed here easily without more mapping\r\n-                        # Add a generic error?\r\n-                        all_file_results.append({\r\n-                            'original_path': '[Unknown Asset - Executor Error]',\r\n-                            'predicted_asset_name': None,\r\n-                            'predicted_output_name': None,\r\n-                            'status': 'Error',\r\n-                            'details': f'Executor Error: {exc}',\r\n-                            'source_asset': '[Unknown]'\r\n-                        })\r\n-\r\n-        except Exception as pool_exc:\r\n-             log.exception(f\"An error occurred with the prediction ThreadPoolExecutor: {pool_exc}\")\r\n-             self.status_message.emit(f\"Error during prediction setup: {pool_exc}\", 5000)\r\n-             # Add a generic error if the pool fails\r\n-             all_file_results.append({\r\n-                 'original_path': '[Prediction Pool Error]',\r\n-                 'predicted_asset_name': None,\r\n-                 'predicted_output_name': None,\r\n-                 'status': 'Error',\r\n-                 'details': f'Pool Error: {pool_exc}',\r\n-                 'source_asset': '[System]'\r\n-             })\r\n-\r\n-        # Emit the combined list of detailed file results at the end\r\n-        log.info(f\"Parallel prediction run finished. Preparing to emit {len(all_file_results)} file results.\")\r\n-        # <<< Add logging before emit >>>\r\n-        log.debug(f\"Type of all_file_results before emit: {type(all_file_results)}\")\r\n-        try:\r\n-            log.debug(f\"Content of all_file_results (first 5) before emit: {all_file_results[:5]}\")\r\n\\ No newline at end of file\n-        except Exception as e:\r\n-             log.error(f\"Error logging all_file_results content: {e}\")\r\n-        # <<< End added logging >>>\r\n-        self.prediction_results_ready.emit(all_file_results)\r\n-        self.status_message.emit(\"Preview update complete.\", 3000)\r\n-        self.prediction_finished.emit()\r\n-        self._is_running = False\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PredictionHandler.run_prediction.\")\n\\ No newline at end of file\n"
                },
                {
                    "date": 1745333390361,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -145,9 +145,9 @@\n             self.prediction_finished.emit()\r\n             return\r\n \r\n         self._is_running = True\r\n-        thread_id = QThread.currentThreadId() # Get current thread ID\r\n+        thread_id = QThread.currentThread() # Get current thread object\r\n         log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PredictionHandler.run_prediction. Starting run for {len(input_paths)} items, Preset='{preset_name}'\")\r\n         self.status_message.emit(f\"Updating preview for {len(input_paths)} items...\", 0)\r\n \r\n         config = None # Load config once if possible\r\n"
                },
                {
                    "date": 1745333453367,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -5,9 +5,9 @@\n import os # For cpu_count\r\n from concurrent.futures import ThreadPoolExecutor, as_completed # For parallel prediction\r\n \r\n # --- PySide6 Imports ---\r\n-from PySide6.QtCore import QObject, Signal\r\n+from PySide6.QtCore import QObject, Signal, QThread # Import QThread\r\n \r\n # --- Backend Imports ---\r\n # Adjust path to ensure modules can be found relative to this file's location\r\n import sys\r\n"
                }
            ],
            "date": 1745236106567,
            "name": "Commit-0",
            "content": "# gui/prediction_handler.py\r\nimport logging\r\nfrom pathlib import Path\r\nimport time # For potential delays if needed\r\nimport os # For cpu_count\r\nfrom concurrent.futures import ThreadPoolExecutor, as_completed # For parallel prediction\r\n\r\n# --- PySide6 Imports ---\r\nfrom PySide6.QtCore import QObject, Signal\r\n\r\n# --- Backend Imports ---\r\n# Adjust path to ensure modules can be found relative to this file's location\r\nimport sys\r\nscript_dir = Path(__file__).parent\r\nproject_root = script_dir.parent\r\nif str(project_root) not in sys.path:\r\n    sys.path.insert(0, str(project_root))\r\n\r\ntry:\r\n    from configuration import Configuration, ConfigurationError\r\n    from asset_processor import AssetProcessor, AssetProcessingError\r\n    BACKEND_AVAILABLE = True\r\nexcept ImportError as e:\r\n    print(f\"ERROR (PredictionHandler): Failed to import backend modules: {e}\")\r\n    # Define placeholders if imports fail\r\n    Configuration = None\r\n    AssetProcessor = None\r\n    ConfigurationError = Exception\r\n    AssetProcessingError = Exception\r\n    BACKEND_AVAILABLE = False\r\n\r\nlog = logging.getLogger(__name__)\r\n# Basic config if logger hasn't been set up elsewhere\r\nif not log.hasHandlers():\r\n     logging.basicConfig(level=logging.INFO, format='%(levelname)s (PredictHandler): %(message)s')\r\n\r\n\r\nclass PredictionHandler(QObject):\r\n    \"\"\"\r\n    Handles running predictions in a separate thread to avoid GUI freezes.\r\n    \"\"\"\r\n    # --- Signals ---\r\n    # Emits a list of dictionaries, each representing a file row for the table\r\n    # Dict format: {'original_path': str, 'predicted_name': str | None, 'status': str, 'details': str | None, 'source_asset': str}\r\n    prediction_results_ready = Signal(list)\r\n    # Emitted when all predictions for a batch are done\r\n    prediction_finished = Signal()\r\n    # Emitted for status updates\r\n    status_message = Signal(str, int)\r\n\r\n    def __init__(self, parent=None):\r\n        super().__init__(parent)\r\n        self._is_running = False\r\n        # No explicit cancel needed for prediction for now, it should be fast per-item\r\n\r\n    @property\r\n    def is_running(self):\r\n        return self._is_running\r\n\r\n    def _predict_single_asset(self, input_path_str: str, config: Configuration) -> list[dict]:\r\n        \"\"\"\r\n        Helper method to predict a single asset. Runs within the ThreadPoolExecutor.\r\n        Returns a list of prediction dictionaries for the asset, or a single error dict.\r\n        \"\"\"\r\n        input_path = Path(input_path_str)\r\n        source_asset_name = input_path.name # For reference in the results\r\n        asset_results = []\r\n        try:\r\n            # Create AssetProcessor instance (needs dummy output path)\r\n            # Ensure AssetProcessor is thread-safe or create a new instance per thread.\r\n            # Based on its structure (using temp dirs), creating new instances should be safe.\r\n            processor = AssetProcessor(input_path, config, Path(\".\")) # Dummy output path\r\n\r\n            # Get detailed file predictions\r\n            detailed_predictions = processor.get_detailed_file_predictions()\r\n\r\n            if detailed_predictions is None:\r\n                log.error(f\"Detailed prediction failed critically for {input_path_str}. Adding asset-level error.\")\r\n                # Add a single error entry for the whole asset if the method returns None\r\n                asset_results.append({\r\n                    'original_path': source_asset_name, # Use asset name as placeholder\r\n                    'predicted_name': None,\r\n                    'status': 'Error',\r\n                    'details': 'Critical prediction failure (check logs)',\r\n                    'source_asset': source_asset_name\r\n                })\r\n            else:\r\n                log.debug(f\"Received {len(detailed_predictions)} detailed predictions for {input_path_str}.\")\r\n                # Add source_asset key to each dictionary and append to the main list\r\n                for prediction_dict in detailed_predictions:\r\n                    prediction_dict['source_asset'] = source_asset_name\r\n                    asset_results.append(prediction_dict)\r\n\r\n        except AssetProcessingError as e: # Catch errors during processor instantiation or prediction setup\r\n             log.error(f\"Asset processing error during prediction setup for {input_path_str}: {e}\")\r\n             asset_results.append({\r\n                 'original_path': source_asset_name,\r\n                 'predicted_name': None,\r\n                 'status': 'Error',\r\n                 'details': f'Asset Error: {e}',\r\n                 'source_asset': source_asset_name\r\n             })\r\n        except Exception as e: # Catch unexpected errors\r\n            log.exception(f\"Unexpected error during prediction for {input_path_str}: {e}\")\r\n            asset_results.append({\r\n                'original_path': source_asset_name,\r\n                'predicted_name': None,\r\n                'status': 'Error',\r\n                'details': f'Unexpected Error: {e}',\r\n                'source_asset': source_asset_name\r\n            })\r\n        finally:\r\n            # Cleanup for the single asset prediction if needed (AssetProcessor handles its own temp dir)\r\n            pass\r\n        return asset_results\r\n\r\n\r\n    def run_prediction(self, input_paths: list[str], preset_name: str):\r\n        \"\"\"\r\n        Runs the prediction logic for the given paths and preset using a ThreadPoolExecutor.\r\n        This method is intended to be run in a separate QThread.\r\n        \"\"\"\r\n        if self._is_running:\r\n            log.warning(\"Prediction is already running.\")\r\n            return\r\n        if not BACKEND_AVAILABLE:\r\n            log.error(\"Backend modules not available. Cannot run prediction.\")\r\n            self.status_message.emit(\"Error: Backend components missing.\", 5000)\r\n            self.prediction_finished.emit()\r\n            return\r\n        if not preset_name:\r\n            log.warning(\"No preset selected for prediction.\")\r\n            self.status_message.emit(\"No preset selected.\", 3000)\r\n            self.prediction_finished.emit()\r\n            return\r\n\r\n        self._is_running = True\r\n        log.info(f\"Starting prediction run for {len(input_paths)} items, Preset='{preset_name}'\")\r\n        self.status_message.emit(f\"Updating preview for {len(input_paths)} items...\", 0)\r\n\r\n        config = None # Load config once if possible\r\n        try:\r\n            config = Configuration(preset_name)\r\n        except ConfigurationError as e:\r\n            log.error(f\"Failed to load configuration for preset '{preset_name}': {e}\")\r\n            self.status_message.emit(f\"Error loading preset '{preset_name}': {e}\", 5000)\r\n            # Emit error for all items? Or just finish? Finish for now.\r\n            self.prediction_finished.emit()\r\n            self._is_running = False\r\n            return\r\n        except Exception as e:\r\n            log.exception(f\"Unexpected error loading configuration for preset '{preset_name}': {e}\")\r\n            self.status_message.emit(f\"Unexpected error loading preset '{preset_name}'.\", 5000)\r\n            self.prediction_finished.emit()\r\n            return\r\n\r\n        all_file_results = [] # Accumulate results here\r\n        futures = []\r\n        # Determine number of workers - use half the cores, minimum 1, max 8?\r\n        max_workers = min(max(1, (os.cpu_count() or 1) // 2), 8)\r\n        log.info(f\"Using ThreadPoolExecutor with max_workers={max_workers} for prediction.\")\r\n\r\n        try:\r\n            with ThreadPoolExecutor(max_workers=max_workers) as executor:\r\n                # Submit tasks for each input path\r\n                for input_path_str in input_paths:\r\n                    future = executor.submit(self._predict_single_asset, input_path_str, config)\r\n                    futures.append(future)\r\n\r\n                # Process results as they complete\r\n                for future in as_completed(futures):\r\n                    try:\r\n                        # Result is a list of dicts for one asset\r\n                        asset_result_list = future.result()\r\n                        if asset_result_list: # Check if list is not empty\r\n                            all_file_results.extend(asset_result_list)\r\n                    except Exception as exc:\r\n                        # This catches errors within the future execution itself if not handled by _predict_single_asset\r\n                        log.error(f'Prediction task generated an exception: {exc}', exc_info=True)\r\n                        # We might not know which input path failed here easily without more mapping\r\n                        # Add a generic error?\r\n                        all_file_results.append({\r\n                            'original_path': '[Unknown Asset - Executor Error]',\r\n                            'predicted_name': None,\r\n                            'status': 'Error',\r\n                            'details': f'Executor Error: {exc}',\r\n                            'source_asset': '[Unknown]'\r\n                        })\r\n\r\n        except Exception as pool_exc:\r\n             log.exception(f\"An error occurred with the prediction ThreadPoolExecutor: {pool_exc}\")\r\n             self.status_message.emit(f\"Error during prediction setup: {pool_exc}\", 5000)\r\n             # Add a generic error if the pool fails\r\n             all_file_results.append({\r\n                 'original_path': '[Prediction Pool Error]',\r\n                 'predicted_name': None,\r\n                 'status': 'Error',\r\n                 'details': f'Pool Error: {pool_exc}',\r\n                 'source_asset': '[System]'\r\n             })\r\n\r\n        # Emit the combined list of detailed file results at the end\r\n        log.info(f\"Parallel prediction run finished. Preparing to emit {len(all_file_results)} file results.\")\r\n        # <<< Add logging before emit >>>\r\n        log.debug(f\"Type of all_file_results before emit: {type(all_file_results)}\")\r\n        try:\r\n            log.debug(f\"Content of all_file_results (first 5) before emit: {all_file_results[:5]}\")\r\n        except Exception as e:\r\n             log.error(f\"Error logging all_file_results content: {e}\")\r\n        # <<< End added logging >>>\r\n        self.prediction_results_ready.emit(all_file_results)\r\n        self.status_message.emit(\"Preview update complete.\", 3000)\r\n        self.prediction_finished.emit()\r\n        self._is_running = False"
        }
    ]
}