from fastapi import APIRouter, Depends from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from database import get_db import models.db_models as db_models import json import io import csv from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import mm from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable router = APIRouter(prefix="/export", tags=["Export"]) def _build_pdf_report(record) -> io.BytesIO: """Generate a professionally styled PDF report for a single scan.""" buffer = io.BytesIO() doc = SimpleDocTemplate(buffer, pagesize=A4, topMargin=20*mm, bottomMargin=20*mm, leftMargin=20*mm, rightMargin=20*mm) styles = getSampleStyleSheet() # Custom styles title_style = ParagraphStyle('Title2', parent=styles['Title'], fontSize=22, textColor=colors.HexColor('#0db9f2'), spaceAfter=6) heading_style = ParagraphStyle('Heading2Custom', parent=styles['Heading2'], fontSize=14, textColor=colors.HexColor('#1e293b'), spaceBefore=14, spaceAfter=8) body_style = ParagraphStyle('BodyCustom', parent=styles['BodyText'], fontSize=10, textColor=colors.HexColor('#475569'), leading=14) elements = [] # Header elements.append(Paragraph("ScamDetect AI — Scan Report", title_style)) elements.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#0db9f2'))) elements.append(Spacer(1, 10)) # Scan Metadata elements.append(Paragraph("Scan Details", heading_style)) risk_color = '#ef4444' if record.risk_level in ('Critical', 'High') else ( '#f59e0b' if record.risk_level == 'Medium' else '#10b981' ) meta_data = [ ['Scan ID', record.id], ['Timestamp', str(record.timestamp)], ['Input Type', (record.type or 'unknown').capitalize()], ['Risk Score', f'{record.risk_score}%'], ['Risk Level', record.risk_level], ['Threat Categories', ', '.join(json.loads(record.threat_categories)) if record.threat_categories else 'None'], ] meta_table = Table(meta_data, colWidths=[120, 350]) meta_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#f1f5f9')), ('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#334155')), ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, -1), 10), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0')), ('TOPPADDING', (0, 0), (-1, -1), 6), ('BOTTOMPADDING', (0, 0), (-1, -1), 6), ('LEFTPADDING', (0, 0), (-1, -1), 8), ])) elements.append(meta_table) elements.append(Spacer(1, 12)) # Extracted Text if record.raw_text_extracted: elements.append(Paragraph("Extracted Content", heading_style)) # Truncate very long text for the PDF text_preview = record.raw_text_extracted[:500] if len(record.raw_text_extracted) > 500: text_preview += "..." elements.append(Paragraph(text_preview, body_style)) elements.append(Spacer(1, 12)) # Explanations (AI Feedback) if record.explanations: elements.append(Paragraph("AI Explainability Analysis", heading_style)) exp_header = ['Feature', 'Description', 'Risk Contribution'] exp_data = [exp_header] for exp in record.explanations: exp_data.append([exp.feature, exp.description, f'{exp.risk_contribution}%']) exp_table = Table(exp_data, colWidths=[120, 280, 80]) exp_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#0db9f2')), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, -1), 9), ('ALIGN', (2, 0), (2, -1), 'CENTER'), ('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0')), ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f8fafc')]), ('TOPPADDING', (0, 0), (-1, -1), 6), ('BOTTOMPADDING', (0, 0), (-1, -1), 6), ('LEFTPADDING', (0, 0), (-1, -1), 6), ])) elements.append(exp_table) elements.append(Spacer(1, 16)) # Footer elements.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#e2e8f0'))) elements.append(Spacer(1, 6)) elements.append(Paragraph("Generated by ScamDetect AI — Multi-Modal Fraud Detection Platform", ParagraphStyle('Footer', parent=styles['Normal'], fontSize=8, textColor=colors.HexColor('#94a3b8'), alignment=1))) doc.build(elements) buffer.seek(0) return buffer @router.get("/pdf/{scan_id}") async def export_pdf(scan_id: str, db: Session = Depends(get_db)): """Generate and download a PDF report for a specific scan.""" record = db.query(db_models.ScanRecord).filter( db_models.ScanRecord.id == scan_id ).first() if not record: return {"error": "Scan not found"} pdf_buffer = _build_pdf_report(record) return StreamingResponse( pdf_buffer, media_type="application/pdf", headers={"Content-Disposition": f"attachment; filename=ScamDetect_Report_{scan_id[:8]}.pdf"} ) @router.get("/csv") async def export_csv(db: Session = Depends(get_db)): """Export all scan history as a CSV file.""" records = db.query(db_models.ScanRecord).order_by( db_models.ScanRecord.timestamp.desc() ).all() output = io.StringIO() writer = csv.writer(output) # Header writer.writerow(['Scan ID', 'Timestamp', 'Type', 'Risk Score', 'Risk Level', 'Threat Categories', 'Extracted Text', 'Explanations']) for record in records: categories = ', '.join(json.loads(record.threat_categories)) if record.threat_categories else '' explanations = ' | '.join( [f"{e.feature}: {e.description} ({e.risk_contribution}%)" for e in record.explanations] ) writer.writerow([ record.id, record.timestamp, record.type, record.risk_score, record.risk_level, categories, (record.raw_text_extracted or '')[:200], explanations ]) output.seek(0) return StreamingResponse( io.BytesIO(output.getvalue().encode('utf-8')), media_type="text/csv", headers={"Content-Disposition": "attachment; filename=ScamDetect_History.csv"} )