""" Export Utilities for Construction Plans Handles PDF and JSON export functionality """ import json import logging from typing import Dict, Any, Optional from datetime import datetime from pathlib import Path logger = logging.getLogger(__name__) def export_to_json(construction_plan: Any, output_path: Optional[str] = None) -> str: """ Export construction plan to JSON format Args: construction_plan: Construction plan object output_path: Optional output file path Returns: Path to exported JSON file """ try: # Convert construction plan to dictionary plan_dict = _construction_plan_to_dict(construction_plan) # Generate filename if not provided if not output_path: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_path = f"construction_plan_{timestamp}.json" # Ensure output directory exists output_file = Path(output_path) output_file.parent.mkdir(parents=True, exist_ok=True) # Write JSON file with open(output_file, 'w', encoding='utf-8') as f: json.dump(plan_dict, f, indent=2, ensure_ascii=False) logger.info(f"Construction plan exported to JSON: {output_file}") return str(output_file) except Exception as e: logger.error(f"Failed to export to JSON: {str(e)}") raise def export_to_pdf(construction_plan: Any, output_path: Optional[str] = None) -> str: """ Export construction plan to PDF format Args: construction_plan: Construction plan object output_path: Optional output file path Returns: Path to exported PDF file """ try: # Generate filename if not provided if not output_path: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_path = f"construction_plan_{timestamp}.pdf" # Ensure output directory exists output_file = Path(output_path) output_file.parent.mkdir(parents=True, exist_ok=True) # Generate PDF content pdf_content = _generate_pdf_content(construction_plan) # For now, create a simple HTML-to-PDF conversion # In production, use a proper PDF library like reportlab or weasyprint try: from weasyprint import HTML HTML(string=pdf_content).write_pdf(output_file) except (ImportError, OSError, Exception) as e: # Fallback: Save as HTML if weasyprint not available or fails logger.warning(f"weasyprint not available or failed ({type(e).__name__}), saving as HTML instead") output_file = output_file.with_suffix('.html') with open(output_file, 'w', encoding='utf-8') as f: f.write(pdf_content) logger.info(f"Construction plan exported to PDF: {output_file}") return str(output_file) except Exception as e: logger.error(f"Failed to export to PDF: {str(e)}") raise def _construction_plan_to_dict(construction_plan: Any) -> Dict[str, Any]: """Convert construction plan object to dictionary""" from dataclasses import asdict, is_dataclass # If already a dict, return it if isinstance(construction_plan, dict): logger.info("Construction plan is already a dictionary") if 'visualization' in construction_plan and construction_plan['visualization']: logger.info("Including visualization data in JSON export") if 'house_specifications' in construction_plan and construction_plan['house_specifications']: logger.info("Including house specifications in JSON export") return construction_plan elif is_dataclass(construction_plan): plan_dict = asdict(construction_plan) # Ensure visualization data is included if present if hasattr(construction_plan, 'visualization') and construction_plan.visualization: logger.info("Including visualization data in JSON export") # Ensure house specifications are included if present if hasattr(construction_plan, 'house_specifications') and construction_plan.house_specifications: logger.info("Including house specifications in JSON export") return plan_dict elif hasattr(construction_plan, '__dict__'): return construction_plan.__dict__ else: return {'data': str(construction_plan)} def _generate_pdf_content(construction_plan: Any) -> str: """Generate HTML content for PDF export""" # Handle both dict and object formats if isinstance(construction_plan, dict): metadata = construction_plan.get('metadata', {}) summary = construction_plan.get('executive_summary', {}) risk = construction_plan.get('risk_assessment', {}) house_specs = construction_plan.get('house_specifications') recommendations = construction_plan.get('construction_recommendations', {}) costs = construction_plan.get('material_costs', {}) facilities = construction_plan.get('critical_facilities', {}) visualization = construction_plan.get('visualization') else: metadata = construction_plan.metadata summary = construction_plan.executive_summary risk = construction_plan.risk_assessment house_specs = construction_plan.house_specifications if hasattr(construction_plan, 'house_specifications') else None recommendations = construction_plan.construction_recommendations costs = construction_plan.material_costs facilities = construction_plan.critical_facilities visualization = construction_plan.visualization if hasattr(construction_plan, 'visualization') else None # Helper function to safely get nested values def get_value(obj, key, default=''): if isinstance(obj, dict): return obj.get(key, default) return getattr(obj, key, default) # Extract metadata values building_type = get_value(metadata, 'building_type', 'Unknown') location = get_value(metadata, 'location', {}) location_name = get_value(location, 'name', 'Unknown') coordinates = get_value(metadata, 'coordinates', {}) lat = get_value(coordinates, 'latitude', 0) lon = get_value(coordinates, 'longitude', 0) building_area = get_value(metadata, 'building_area') generated_at = get_value(metadata, 'generated_at', 'Unknown') overall_risk = get_value(summary, 'overall_risk', 'Unknown') html = f""" Construction Plan Report

🏗️ Disaster Risk Construction Plan

Project Information

Building Type: {building_type.replace('_', ' ').title()}

Location: {location_name}

Coordinates: {lat:.4f}°N, {lon:.4f}°E

{f"

Building Area: {building_area:.2f} sq meters

" if building_area else ""}

Generated: {generated_at}

Executive Summary

Overall Risk: {overall_risk}

""" # Critical concerns critical_concerns = get_value(summary, 'critical_concerns', []) if critical_concerns: html += """

⚠️ Critical Concerns

""" # Key recommendations key_recommendations = get_value(summary, 'key_recommendations', []) if key_recommendations: html += """

🎯 Key Recommendations

    """ for rec in key_recommendations: html += f"
  1. {rec}
  2. " html += """
""" # Visualization if visualization: viz_timestamp = get_value(visualization, 'generation_timestamp', 'Unknown') viz_model = get_value(visualization, 'model_version', 'Unknown') viz_format = get_value(visualization, 'image_format', 'png') viz_base64 = get_value(visualization, 'image_base64', '') viz_features = get_value(visualization, 'features_included', []) viz_prompt = get_value(visualization, 'prompt_used', '') html += f"""

🎨 Architectural Visualization

Generated: {viz_timestamp}

Model: {viz_model}

""" # Add the image if available if viz_base64: html += f"""
Architectural Visualization
""" # Add features list if viz_features: html += """

Disaster-Resistant Features Shown

""" # Add prompt used if viz_prompt: html += f"""

Design Prompt: {viz_prompt}

""" html += """
""" # Risk assessment html += f"""

Risk Assessment

Overall Risk Level: {risk.summary.overall_risk_level}

Total Hazards Assessed: {risk.summary.total_hazards_assessed}

High Risk Hazards: {risk.summary.high_risk_count}

""" # House Specifications if house_specs: html += """

🏗️ House Specifications

Basic Specifications

Number of Floors """ + str(house_specs.number_of_floors) + """
Primary Material """ + str(house_specs.primary_material).replace('_', ' ').title() + """
Foundation Type """ + str(house_specs.foundation_type).replace('_', ' ').title() + """
Foundation Depth """ + f"{house_specs.foundation_depth_meters:.2f} meters" + """
Roof Type """ + str(house_specs.roof_type).replace('_', ' ').title() + """
Wall Thickness """ + f"{house_specs.wall_thickness_mm} mm" + """
""" # Reinforcement details if house_specs.reinforcement_details: html += f"""

Reinforcement Details

{house_specs.reinforcement_details}

""" # Structural features if house_specs.structural_features: html += """

Structural Features

""" for feature in house_specs.structural_features: html += f""" """ html += """
Feature Specification Justification
{feature.feature_name} {feature.specification} {feature.justification}
""" # Compliance codes if house_specs.compliance_codes: html += """

Compliance Codes

""" # Decision rationale if house_specs.decision_rationale: html += f"""

Decision Rationale

{house_specs.decision_rationale}

""" html += """
""" # Recommendations if recommendations.general_guidelines: html += """

Construction Recommendations

General Guidelines

""" # Material costs if costs.materials: html += """

Material Cost Estimates

""" if costs.total_estimate: html += f"""

Total Estimate Range:

""" html += """

Itemized Materials

""" for material in costs.materials[:20]: # Limit to 20 items for PDF quantity_str = f"{material.quantity_needed:.2f} {material.unit}" if material.quantity_needed else "TBD" total_str = f"{material.currency} {material.total_cost:,.2f}" if material.total_cost else "TBD" html += f""" """ html += """
Material Category Unit Price Quantity Total
{material.material_name} {material.category} {material.currency} {material.price_per_unit:,.2f}/{material.unit} {quantity_str} {total_str}
""" # Critical facilities if facilities.schools or facilities.hospitals: html += """

Critical Facilities

""" if facilities.schools: html += "

Schools

" if facilities.hospitals: html += "

Hospitals

" html += """
""" # Footer html += """ """ return html def create_export_buttons(construction_plan: Any) -> tuple: """ Create export buttons and handlers for Gradio interface Args: construction_plan: Construction plan object Returns: Tuple of (json_button, pdf_button, json_file, pdf_file) """ import gradio as gr def export_json_handler(): try: file_path = export_to_json(construction_plan) return file_path except Exception as e: logger.error(f"JSON export failed: {str(e)}") return None def export_pdf_handler(): try: file_path = export_to_pdf(construction_plan) return file_path except Exception as e: logger.error(f"PDF export failed: {str(e)}") return None json_button = gr.Button("📥 Export as JSON", variant="secondary") pdf_button = gr.Button("📄 Export as PDF", variant="secondary") json_file = gr.File(label="JSON Export", visible=False) pdf_file = gr.File(label="PDF Export", visible=False) json_button.click(fn=export_json_handler, outputs=json_file) pdf_button.click(fn=export_pdf_handler, outputs=pdf_file) return json_button, pdf_button, json_file, pdf_file