ScamDetect Bot
Auto-sync backend from GitHub
69b17de
Raw
History Blame Contribute Delete
6.96 kB
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"}
)