Source code for malva.serve.reportgen

import io
import zipfile
from datetime import datetime
import base64
from pathlib import Path
import numpy as np

[docs] class HTMLReportGenerator:
[docs] def __init__(self, session_data): """Initialize with session data""" self.session_data = session_data
def _create_html(self) -> str: """Create HTML report""" # Get timestamp timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Start HTML content html_content = f''' <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Malva Search Report - {timestamp}</title> <style> body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.6; max-width: 1200px; margin: 0 auto; padding: 20px; color: #333; }} h1, h2, h3 {{ color: #2c3e50; margin-top: 1.5em; }} .header {{ border-bottom: 2px solid #eee; padding-bottom: 10px; margin-bottom: 30px; }} .timestamp {{ color: #666; font-size: 0.9em; }} .info-table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }} .info-table th, .info-table td {{ padding: 12px; border: 1px solid #ddd; text-align: left; }} .info-table th {{ background-color: #f5f6fa; }} .visualization {{ margin: 30px 0; text-align: center; }} .sequence-info {{ background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; overflow-x: auto; }} .sequence {{ font-family: monospace; white-space: pre-wrap; word-wrap: break-word; }} .note {{ background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; padding: 15px; border-radius: 5px; margin: 20px 0; }} .footer {{ margin-top: 50px; padding-top: 20px; border-top: 2px solid #eee; font-size: 0.9em; color: #666; }} </style> </head> <body> <div class="header"> <h1>Malva Search Report</h1> <div class="timestamp">Generated on: {timestamp}</div> </div> <h2>Query Information</h2> <table class="info-table"> <tr> <th>Query Term</th> <td>{self.session_data.get("query_term", "N/A")}</td> </tr> <tr> <th>Window Size</th> <td>{self.session_data.get("sliding_size", "N/A")}</td> </tr> <tr> <th>Analysis Parameters</th> <td> Low Complexity Filter: {self.session_data.get("low_complexity_filter", "N/A")}<br> K-mer Presence Threshold: {self.session_data.get("pct_threshold", "N/A")} </td> </tr> </table> <h2>Sequences</h2> <div class="sequence-info"> <pre class="sequence">{self._format_sequences()}</pre> </div> ''' # Add visualization section if available if "where_abundant" in self.session_data: html_content += self._create_visualization_section() # Add coverage plot placeholder html_content += ''' <h2>Sequence Coverage</h2> <div class="note"> Coverage plot showing query sequence matches will be available in future updates. </div> ''' # Add file information html_content += ''' <h2>Additional Files</h2> <table class="info-table"> <tr> <th>File</th> <th>Description</th> </tr> <tr> <td>queries.fa</td> <td>Contains all query sequences in FASTA format</td> </tr> <tr> <td>malva.log</td> <td>Contains detailed logging information from the analysis</td> </tr> <tr> <td>summary.txt</td> <td>Contains additional summary statistics and analysis details</td> </tr> </table> <div class="footer"> <p>Generated by Malva Search Tool</p> <p>For more information, visit the documentation.</p> </div> </body> </html> ''' return html_content def _format_sequences(self) -> str: """Format sequences for display""" sequences = self._get_sequences() formatted = [] for idx, seq in enumerate(sequences, 1): if isinstance(seq, dict) and 'header' in seq: header = seq['header'] sequence = seq['sequence'] else: if self.session_data.get("query_term", "").startswith(("gene:", "ensembl:")): header = f">query_{self.session_data['query_term']}" else: header = f">query_sequence_{idx}" sequence = seq # Format sequence with line breaks every 60 characters formatted_seq = '\n'.join(sequence[i:i+60] for i in range(0, len(sequence), 60)) formatted.append(f"{header}\n{formatted_seq}") return '\n\n'.join(formatted) def _create_visualization_section(self) -> str: """Create visualization section of the report""" # Add placeholder for static visualization image return ''' <h2>Search Results Visualization</h2> <div class="visualization"> <p>The interactive visualization from the web interface is available in the results view.</p> <!-- Static visualization image would be added here --> </div> ''' def _get_sequences(self) -> list: """Extract sequences from session data""" sequences = [] query_seq = self.session_data.get("query_seq") if isinstance(query_seq, list): sequences.extend(query_seq) else: sequences.append(query_seq) return sequences def _create_fasta(self) -> str: """Create FASTA format file""" sequences = self._get_sequences() fasta_content = [] for idx, seq in enumerate(sequences, 1): if isinstance(seq, dict) and 'header' in seq: header = seq['header'] sequence = seq['sequence'] else: if self.session_data.get("query_term", "").startswith(("gene:", "ensembl:")): header = f">query_{self.session_data['query_term']}" else: header = f">query_sequence_{idx}" sequence = seq fasta_content.append(f"{header}\n{sequence}") return "\n".join(fasta_content) def _create_summary(self) -> str: """Create summary text file""" summary_lines = [ "MALVA SEARCH SUMMARY", "=" * 50, f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", f"Query Term: {self.session_data.get('query_term', 'N/A')}", f"Number of Sequences: {len(self._get_sequences())}", f"Window Size: {self.session_data.get('sliding_size', 'N/A')}", "\nSearch Parameters:", "-" * 20, f"Low Complexity Filter: {self.session_data.get('low_complexity_filter', 'N/A')}", f"K-mer Presence Threshold: {self.session_data.get('pct_threshold', 'N/A')}", ] return "\n".join(summary_lines)
[docs] def create_report_zip(self) -> bytes: """Create ZIP file containing all report components""" zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: # Add HTML report zip_file.writestr('report.html', self._create_html()) # Add FASTA file zip_file.writestr('queries.fa', self._create_fasta()) # Add log file zip_file.writestr('malva.log', "Malva log content will be added here\n") # Add summary file zip_file.writestr('summary.txt', self._create_summary()) zip_buffer.seek(0) return zip_buffer.getvalue()