Files
web-inspector/backend/app/services/report_service.py
jungwoo choi b5fa5d96b9 feat: 웹사이트 표준화 검사 도구 구현
- 4개 검사 엔진: HTML/CSS, 접근성(WCAG), SEO, 성능/보안 (총 50개 항목)
- FastAPI 백엔드 (9개 API, SSE 실시간 진행, PDF/JSON 리포트)
- Next.js 15 프론트엔드 (6개 페이지, 29개 컴포넌트, 반원 게이지 차트)
- Docker Compose 배포 (Backend:8011, Frontend:3011, MongoDB:27022, Redis:6392)
- 전체 테스트 32/32 PASS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:57:27 +09:00

96 lines
3.1 KiB
Python

"""
Report generation service.
Generates PDF and JSON reports from inspection results.
"""
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Optional
from urllib.parse import urlparse
from jinja2 import Environment, FileSystemLoader
from slugify import slugify
logger = logging.getLogger(__name__)
TEMPLATES_DIR = Path(__file__).parent.parent / "templates"
# Grade color mapping
GRADE_COLORS = {
"A+": "#22C55E",
"A": "#22C55E",
"B": "#3B82F6",
"C": "#F59E0B",
"D": "#F97316",
"F": "#EF4444",
}
SEVERITY_COLORS = {
"critical": "#EF4444",
"major": "#F97316",
"minor": "#EAB308",
"info": "#3B82F6",
}
CATEGORY_LABELS = {
"html_css": "HTML/CSS 표준",
"accessibility": "접근성 (WCAG)",
"seo": "SEO 최적화",
"performance_security": "성능/보안",
}
class ReportService:
"""PDF and JSON report generation service."""
def __init__(self):
self.env = Environment(
loader=FileSystemLoader(str(TEMPLATES_DIR)),
autoescape=True,
)
# Register custom filters
self.env.filters["grade_color"] = lambda g: GRADE_COLORS.get(g, "#6B7280")
self.env.filters["severity_color"] = lambda s: SEVERITY_COLORS.get(s, "#6B7280")
self.env.filters["category_label"] = lambda c: CATEGORY_LABELS.get(c, c)
async def generate_pdf(self, inspection: dict) -> bytes:
"""Generate PDF report from inspection result."""
try:
from weasyprint import HTML
template = self.env.get_template("report.html")
html_string = template.render(
inspection=inspection,
generated_at=datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"),
grade_colors=GRADE_COLORS,
severity_colors=SEVERITY_COLORS,
category_labels=CATEGORY_LABELS,
)
pdf_bytes = HTML(string=html_string).write_pdf()
return pdf_bytes
except ImportError:
logger.error("WeasyPrint is not installed")
raise RuntimeError("PDF generation is not available (WeasyPrint not installed)")
except Exception as e:
logger.error("PDF generation failed: %s", str(e))
raise RuntimeError(f"PDF generation failed: {str(e)}")
async def generate_json(self, inspection: dict) -> bytes:
"""Generate JSON report from inspection result."""
# Remove MongoDB internal fields
clean_data = {k: v for k, v in inspection.items() if k != "_id"}
json_str = json.dumps(clean_data, ensure_ascii=False, indent=2, default=str)
return json_str.encode("utf-8")
@staticmethod
def generate_filename(url: str, extension: str) -> str:
"""Generate download filename: web-inspector-{url-slug}-{date}.{ext}"""
parsed = urlparse(url)
hostname = parsed.hostname or "unknown"
url_slug = slugify(hostname, max_length=50)
date_str = datetime.utcnow().strftime("%Y-%m-%d")
return f"web-inspector-{url_slug}-{date_str}.{extension}"