File size: 13,313 Bytes
ceaf3bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
import tempfile
import os
import shutil
from io import BytesIO
from zipfile import ZipFile
from typing import Dict, Callable, Any, List
from datetime import datetime
from system_chart_extraction import system_chart_extraction

class SystemChartService:
    """
    Service class for System Chart Extraction.
    Handles the orchestration of system chart extraction, visualization creation,
    and result packaging.
    """
    
    def __init__(self):
        pass
    
    async def extract_system_charts(
        self,
        category: str,
        llm,
        pdf_dict: Dict[str, str],
        enable_visualization: bool = False,
        run_system_chart_normalization: bool = False,
        progress_callback: Callable[[Dict[str, Any]], None] = None,
        extraction_id: str = None
    ) -> Dict[str, Any]:
        """
        Main system chart extraction pipeline.
        
        Args:
            category: Database category for extraction
            llm: Language model for extraction
            pdf_dict: Dictionary mapping document titles to full text content
            enable_visualization: Whether to create visualization diagrams
            progress_callback: Function to report progress updates
            
        Returns:
            Dict containing extraction results and metadata
        """
        try:
            # Validate input
            if not pdf_dict:
                return {
                    "success": False,
                    "message": "No files found in the specified category.",
                    "zip_data": None,
                    "filename": None
                }
            
            print(f"πŸ“„ Found {len(pdf_dict)} papers to process")
            
            # Initial progress update
            if progress_callback:
                progress_callback({
                    "status": "starting",
                    "progress": 0,
                    "message": "Getting PDF files..."
                })
            
            # Create progress callback function for the extraction process
            def extraction_progress_callback(progress_data):
                if progress_callback:
                    print(f"πŸ“Š Progress update: {progress_data}")
                    progress_callback(progress_data)
                    print(f"πŸ”„ Progress updated: {progress_data}")
            
            # Update progress before starting extraction
            if progress_callback:
                progress_callback({
                    "status": "starting",
                    "progress": 5,
                    "message": f"Found {len(pdf_dict)} papers. Starting extraction..."
                })
            
            print(f"πŸ”¬ Starting system chart extraction...")
            
            # Create temporary directory for visualization files if needed
            temp_dir = None
            if enable_visualization:
                temp_dir = tempfile.mkdtemp(prefix="system_chart_viz_")
                print(f"πŸ“ Created temporary directory: {temp_dir}")
            
            # Run system chart extraction with progress tracking
            output_system_charts, output_all_details, created_visualizations = await system_chart_extraction(
                pdf_dict, 
                llm, 
                extraction_progress_callback, 
                enable_visualization,
                temp_dir,
                extraction_id,  # Pass extraction_id to save conversations
                run_normalization=run_system_chart_normalization  # ← only normalize when asked/needed
            )
            
            # Create ZIP file in memory
            zip_data = self._create_zip_file(
                output_system_charts, 
                output_all_details, 
                created_visualizations
            )
            
            # Clean up temporary directory
            if temp_dir:
                self._cleanup_temp_directory(temp_dir)
            
            # Prepare result
            viz_message = f" with {len(created_visualizations)} visualizations" if created_visualizations else ""
            
            return {
                "success": True,
                "message": f"System chart extraction completed successfully{viz_message}!",
                "zip_data": zip_data,
                "filename": "system_chart_extraction.zip",
                "charts_count": len(pdf_dict),
                "visualizations_count": len(created_visualizations) if created_visualizations else 0
            }
            
        except Exception as e:
            # Clean up on error
            if 'temp_dir' in locals() and temp_dir:
                self._cleanup_temp_directory(temp_dir)
            
            return {
                "success": False,
                "message": f"Error during system chart extraction: {str(e)}",
                "zip_data": None,
                "filename": None
            }
    
    def _create_zip_file(
        self, 
        output_system_charts: str, 
        output_all_details: str, 
        created_visualizations: List[str]
    ) -> bytes:
        """Create ZIP file containing extraction results and visualizations."""
        zip_buffer = BytesIO()
        
        with ZipFile(zip_buffer, "w") as zipf:
            # Add text results
            zipf.writestr("system_charts.txt", output_system_charts)
            zipf.writestr("all_details.txt", output_all_details)
            
            # Add visualization files if any were created
            if created_visualizations:
                for viz_path in created_visualizations:
                    if os.path.exists(viz_path):
                        # Extract just the filename for the ZIP entry
                        viz_filename = os.path.basename(viz_path)
                        zipf.write(viz_path, f"visualizations/{viz_filename}")
                        print(f"πŸ“¦ Added visualization to ZIP: {viz_filename}")
        
        # Get ZIP data and close buffer
        zip_data = zip_buffer.getvalue()
        zip_buffer.close()
        
        return zip_data
    
    def _cleanup_temp_directory(self, temp_dir: str) -> None:
        """Clean up temporary directory and its contents."""
        if temp_dir and os.path.exists(temp_dir):
            try:
                shutil.rmtree(temp_dir)
                print(f"πŸ—‘οΈ Cleaned up temporary directory: {temp_dir}")
            except OSError as e:
                print(f"⚠️ Could not clean up temporary directory {temp_dir}: {e}")
                # Don't fail the whole process if cleanup fails
                pass
    
    @staticmethod
    def parse_system_charts_file(file_content: str) -> List[Dict[str, str]]:
        """
        Parse a delimited system charts file into individual chart objects.
        
        Args:
            file_content: Content of the system_charts.txt file
            
        Returns:
            List of dictionaries, each containing:
            - 'chart_number': The chart number (auto-generated)
            - 'title': Paper title
            - 'citation': Citation format (same as title for compatibility)
            - 'content': System chart table content
        """
        charts = []
        lines = file_content.split('\n')
        
        current_chart = None
        chart_number = 1
        
        for i, line in enumerate(lines):
            line = line.strip()
            
            # Check for delimiter line (100 '=' characters)
            if line == '=' * 100:
                # Look for PAPER: line after delimiter
                if i + 1 < len(lines):
                    next_line = lines[i + 1].strip()
                    if next_line.startswith('PAPER:'):
                        # Save previous chart if exists
                        if current_chart:
                            charts.append(current_chart)
                        
                        # Start new chart
                        paper_title = next_line.replace('PAPER:', '').strip()
                        current_chart = {
                            'chart_number': str(chart_number),
                            'title': paper_title,
                            'citation': f'[{paper_title}]',
                            'content': ''
                        }
                        chart_number += 1
                        
            elif line.startswith('SYSTEM_CHART:') and current_chart:
                # Extract the system chart content
                chart_content = line.replace('SYSTEM_CHART:', '').strip()
                current_chart['content'] = chart_content
        
        # Handle last chart if file doesn't end with proper delimiter
        if current_chart:
            charts.append(current_chart)
        
        print(f"πŸ“Š Parsed {len(charts)} system charts from file")
        return charts
    
    @staticmethod
    def combine_system_charts_files(file_contents: List[str]) -> str:
        """
        Combine multiple system chart txt files into a single formatted string.
        Preserves the one-to-one relationship between system charts and titles.
        
        Args:
            file_contents: List of system chart file contents to combine
            
        Returns:
            Combined system chart content in the new format
        """
        combined_charts = []
        
        # Parse each file and collect all charts
        for file_content in file_contents:
            charts = SystemChartService.parse_system_charts_file(file_content)
            combined_charts.extend(charts)
        
        # Generate combined output in new format
        combined_output = "COMBINED SYSTEM CHARTS EXTRACTION RESULTS\n"
        combined_output += "=" * 100 + "\n"
        
        for chart in combined_charts:
            delimiter = "=" * 100
            combined_output += f'\n{delimiter}\n'
            combined_output += f'PAPER: {chart["title"]}\n'
            combined_output += f'SYSTEM_CHART: {chart["content"]}\n'
            combined_output += f'{delimiter}\n\n'
        
        # Add summary
        summary_delimiter = "=" * 100
        combined_output += f'\n{summary_delimiter}\n'
        combined_output += f'COMBINATION SUMMARY\n'
        combined_output += f'{summary_delimiter}\n'
        combined_output += f'Total Files Combined: {len(file_contents)}\n'
        combined_output += f'Total System Charts: {len(combined_charts)}\n'
        combined_output += f'Combination Date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n'
        
        print(f"πŸ”— Combined {len(file_contents)} files into {len(combined_charts)} system charts")
        return combined_output
    
    async def combine_multiple_system_chart_files(
        self,
        file_paths: List[str],
        output_filename: str = "combined_system_charts.txt"
    ) -> Dict[str, Any]:
        """
        Combine multiple system chart txt files into a single ZIP file.
        
        Args:
            file_paths: List of file paths to system chart txt files
            output_filename: Name for the output file
            
        Returns:
            Dict containing success status, message, and ZIP data
        """
        try:
            file_contents = []
            
            # Read all files
            for file_path in file_paths:
                if not os.path.exists(file_path):
                    return {
                        "success": False,
                        "message": f"File not found: {file_path}",
                        "zip_data": None,
                        "filename": None
                    }
                
                with open(file_path, 'r', encoding='utf-8') as f:
                    file_contents.append(f.read())
            
            # Combine the files
            combined_content = self.combine_system_charts_files(file_contents)
            
            # Create ZIP file
            zip_buffer = BytesIO()
            with ZipFile(zip_buffer, "w") as zipf:
                zipf.writestr(output_filename, combined_content)
            
            zip_data = zip_buffer.getvalue()
            zip_buffer.close()
            
            return {
                "success": True,
                "message": f"Successfully combined {len(file_paths)} system chart files!",
                "zip_data": zip_data,
                "filename": "combined_system_charts.zip",
                "files_combined": len(file_paths)
            }
            
        except Exception as e:
            return {
                "success": False,
                "message": f"Error combining system chart files: {str(e)}",
                "zip_data": None,
                "filename": None
            }
    
    @staticmethod
    def get_chart_citations(file_content: str) -> List[str]:
        """
        Extract just the citations from a system charts file.
        
        Args:
            file_content: Content of the system_charts.txt file
            
        Returns:
            List of citation strings
        """
        charts = SystemChartService.parse_system_charts_file(file_content)
        return [chart.get('citation', chart.get('title', f"Chart {chart['chart_number']}")) 
                for chart in charts if chart.get('chart_number')]