Validation_Suite/validation-suite/backend/mercury/app/modules/orion/service.py

113 lines
4.6 KiB
Python

from __future__ import annotations
from pathlib import Path
import logging
from sqlalchemy.orm import Session
from app.modules.orion.assets import ORION_ASSET_DIR
from app.modules.orion.components import (
AttachmentComponent,
ChecklistComponent,
CoverComponent,
CustomerComponent,
DeviceComponent,
DryingComponent,
EnvironmentComponent,
EquipmentComponent,
LoadingComponent,
MeasurementComponent,
ProgramComponent,
RecommendationComponent,
ReportComponent,
StaticTextComponent,
SummaryComponent,
TocComponent,
)
from app.modules.orion.context import OrionContextBuilder, ReportContext
from app.modules.orion.templates.report import render_document
from app.modules.orion.template_service import REPORT_SECTIONS
logger = logging.getLogger(__name__)
class OrionReportService:
def __init__(self, session: Session, generated_dir: Path | None = None) -> None:
self.session = session
self.generated_dir = generated_dir or Path("/app/reports")
def render_html(self, validation_id: str) -> str:
context = OrionContextBuilder(self.session, self.generated_dir).build(validation_id)
components = self._components()
chapters = [component.render(context) for component in components]
return render_document(context, chapters)
def render_pdf(self, validation_id: str) -> Path:
from weasyprint import HTML
context = OrionContextBuilder(self.session, self.generated_dir).build(validation_id)
output_path = context.generated_dir / f"{context.validation.report_number}.pdf"
html = self.render_html(validation_id)
HTML(string=html, base_url=str(ORION_ASSET_DIR)).write_pdf(output_path)
self._append_pdf_attachments(context, output_path)
return output_path
def _components(self) -> list[ReportComponent]:
chapters = [self._component_for_section(item) for item in REPORT_SECTIONS]
return [CoverComponent(), SummaryComponent(), TocComponent(REPORT_SECTIONS), CustomerComponent(), *chapters]
def _component_for_section(self, item: dict) -> ReportComponent:
component = item.get("component")
key = str(item["key"])
title = str(item["title"])
number = item.get("number")
if component == "device":
return DeviceComponent(key, title, number)
if component == "checklist":
return ChecklistComponent(key, title, number)
if component == "environment":
return EnvironmentComponent(key, title, number)
if component == "programs":
return ProgramComponent(key, title, number)
if component == "loading":
return LoadingComponent(key, title, number)
if component == "equipment":
return EquipmentComponent(key, title, number)
if component == "measurements":
return MeasurementComponent(key, title, number)
if component == "drying":
return DryingComponent(key, title, number)
if component == "recommendations":
return RecommendationComponent(key, title, number)
if component == "attachments":
return AttachmentComponent(key, title, number)
return StaticTextComponent(key, title, block_key=item.get("block_key"), number=number)
def _append_pdf_attachments(self, context: ReportContext, output_path: Path) -> None:
pdf_paths = []
for item in context.validation.attachments or []:
filename = str(item.get("filename") or "")
content_type = str(item.get("content_type") or "")
if not filename.lower().endswith(".pdf") and content_type != "application/pdf":
continue
storage_path = item.get("storage_path")
if storage_path and Path(str(storage_path)).exists():
pdf_paths.append(Path(str(storage_path)))
if not pdf_paths:
return
try:
from pypdf import PdfReader, PdfWriter
writer = PdfWriter()
for page in PdfReader(str(output_path)).pages:
writer.add_page(page)
for pdf_path in pdf_paths:
try:
for page in PdfReader(str(pdf_path)).pages:
writer.add_page(page)
except Exception:
logger.exception("Could not append attachment PDF %s", pdf_path)
with output_path.open("wb") as handle:
writer.write(handle)
except Exception:
logger.exception("Could not merge Orion PDF attachments for validation %s", context.validation.id)