#!/usr/bin/env python3 """ Step 7: Generate Complete Summary Report Generates a full analysis report in Markdown and DOCX format. Usage: python step7_summary_report.py \ --workspace /path/to/output \ --protein_name "Bacterial_Protein" \ --ligand_name "Drug_Candidate" \ --alignment_json alignment_results.json \ --model_quality_json model_quality_results.json \ --docking_summary_json docking_summary.json \ --output_name "Complete_Summary" """ import argparse import json import os from docx import Document from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT from datetime import datetime def generate_markdown(args, data): date = data.get('analysis_date', datetime.now().strftime('%Y-%m-%d')) lines = [ f"# In Silico Interaction Analysis: {args.ligand_name} vs {args.protein_name}", "## Complete Workflow & Results Summary", f"**Analysis Date**: {date}", "", "---", "## 1. Research Question", f"Can **{args.ligand_name}** directly bind to **{args.protein_name}**?", "", "---", "## 2. Analysis Pipeline", ] steps = [ "Step 1: Sequence Retrieval (UniProt API)", "Step 2: PDB Structure Search (RCSB PDB API)", "Step 3: Sequence Alignment (Biopython PairwiseAligner)", "Step 4: AlphaFold-Multimer Complex Modeling (Colab)", "Step 5: Model Quality Assessment (pLDDT + PAE)", "Step 6: AutoDock Vina Molecular Docking (WSL)", "Step 7: Quantitative Analysis & Conclusions", ] for s in steps: lines.append(f"- {s}") if 'alignment' in data: al = data['alignment'] lines += [ "", "---", "## 3. Sequence Alignment Results", f"| Comparison | Similarity |", f"|-------------|-----------|", f"| {al.get('name1','Query1')} vs {al.get('name2','Query2')} | {al.get('similarity', 'N/A')} |", ] if 'model_quality' in data: mq = data['model_quality'] plddt = mq.get('plddt', {}) pae = mq.get('pae', {}) grid = mq.get('grid_box', {}) lines += [ "", "---", "## 4. AlphaFold Model Quality", f"- pLDDT (Chain A): {plddt.get('A',{}).get('mean','N/A'):.1f}", f"- pLDDT (Chain B): {plddt.get('B',{}).get('mean','N/A'):.1f}", f"- PAE (Interface): {pae.get('interface_pae','N/A'):.2f} A", f"- Grid Box Center: ({grid.get('grid_center',{}).get('x','?')}, " f"{grid.get('grid_center',{}).get('y','?')}, " f"{grid.get('grid_center',{}).get('z','?')})", ] if 'docking' in data: dk = data['docking'] modes = dk.get('modes', []) pose = dk.get('pose_analysis', {}) lines += [ "", "---", "## 5. Molecular Docking Results", f"**Best Binding Affinity**: {dk.get('best_mode',{}).get('affinity','N/A')} kcal/mol", f"**Ligand shift from interface center**: {pose.get('shift_from_grid_center','N/A')} A", f"**Nearest distance to Chain A**: {pose.get('min_dist_chain_a','N/A')} A", f"**Nearest distance to Chain B**: {pose.get('min_dist_chain_b','N/A')} A", f"**Contacts within 4A**: A={pose.get('contacts_4A_chain_a','?')}, B={pose.get('contacts_4A_chain_b','?')}", ] lines += [ "", "---", "## 6. Overall Conclusion", ] if modes and pose: best = modes[0]['affinity'] if modes else 0 shift = pose.get('shift_from_grid_center', 999) contacts_a = pose.get('contacts_4A_chain_a', 999) if best < -7 and shift < 8 and contacts_a > 0: verdict = f"**{args.ligand_name} may bind to {args.protein_name}**" else: verdict = f"**{args.ligand_name} does NOT bind to the functional interface of {args.protein_name}**" lines.append(verdict) lines += [ "", "---", f"*Generated by OpenClaw AI Agent | {date}*", ] return '\n'.join(lines) def generate_docx(args, data, output_path): doc = Document() for s in doc.sections: s.top_margin = s.bottom_margin = Cm(2.5) s.left_margin = s.right_margin = Cm(2.5) t = doc.add_heading(f'In Silico Analysis: {args.ligand_name} vs {args.protein_name}', 0) t.alignment = WD_ALIGN_PARAGRAPH.CENTER doc.add_heading('Overall Conclusion', 1) if 'docking' in data: dk = data['docking'] modes = dk.get('modes', []) pose = dk.get('pose_analysis', {}) if modes and pose: best = modes[0]['affinity'] shift = pose.get('shift_from_grid_center', 999) contacts = pose.get('contacts_4A_chain_a', 0) + pose.get('contacts_4A_chain_b', 0) p = doc.add_paragraph() p.add_run(f"Best binding affinity: {best:.3f} kcal/mol\n").bold = True p.add_run(f"Ligand shift from interface: {shift} A\n") p.add_run(f"Contacts within 4A: {contacts} residues\n") if best < -7 and shift < 8 and contacts > 0: p.add_run("\nConclusion: Likely binds").bold = True else: p.add_run("\nConclusion: Does not bind functional interface").bold = True doc.save(output_path) print(f"DOCX saved: {output_path}") def main(): parser = argparse.ArgumentParser(description='Generate complete analysis report') parser.add_argument('--workspace', required=True, help='Working directory') parser.add_argument('--protein_name', required=True, help='Protein name') parser.add_argument('--ligand_name', required=True, help='Ligand name') parser.add_argument('--alignment_json', default=None) parser.add_argument('--model_quality_json', default=None) parser.add_argument('--docking_summary_json', default=None) parser.add_argument('--output_name', default='Complete_Summary') args = parser.parse_args() data = {'analysis_date': datetime.now().strftime('%Y-%m-%d')} if args.alignment_json: path = os.path.join(args.workspace, args.alignment_json) if os.path.exists(path): with open(path) as f: data['alignment'] = json.load(f) if args.model_quality_json: path = os.path.join(args.workspace, args.model_quality_json) if os.path.exists(path): with open(path) as f: data['model_quality'] = json.load(f) if args.docking_summary_json: path = os.path.join(args.workspace, args.docking_summary_json) if os.path.exists(path): with open(path) as f: data['docking'] = json.load(f) md = generate_markdown(args, data) md_path = os.path.join(args.workspace, f"{args.output_name}.md") with open(md_path, 'w', encoding='utf-8') as f: f.write(md) print(f"Markdown saved: {md_path}") docx_path = os.path.join(args.workspace, f"{args.output_name}.docx") generate_docx(args, data, docx_path) if __name__ == '__main__': main()