feat(validation): add editing versioning revalidation and aligned reports
This commit is contained in:
parent
f73a24df13
commit
302e542fda
28 changed files with 2691 additions and 406 deletions
|
|
@ -0,0 +1,37 @@
|
|||
from app.modules.orion.components.base import ReportComponent
|
||||
from app.modules.orion.components.chapters import (
|
||||
AttachmentComponent,
|
||||
ChecklistComponent,
|
||||
CoverComponent,
|
||||
CustomerComponent,
|
||||
DeviceComponent,
|
||||
DryingComponent,
|
||||
EnvironmentComponent,
|
||||
EquipmentComponent,
|
||||
LoadingComponent,
|
||||
MeasurementComponent,
|
||||
ProgramComponent,
|
||||
RecommendationComponent,
|
||||
StaticTextComponent,
|
||||
SummaryComponent,
|
||||
TocComponent,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AttachmentComponent",
|
||||
"ChecklistComponent",
|
||||
"CoverComponent",
|
||||
"CustomerComponent",
|
||||
"DeviceComponent",
|
||||
"DryingComponent",
|
||||
"EnvironmentComponent",
|
||||
"EquipmentComponent",
|
||||
"LoadingComponent",
|
||||
"MeasurementComponent",
|
||||
"ProgramComponent",
|
||||
"RecommendationComponent",
|
||||
"StaticTextComponent",
|
||||
"ReportComponent",
|
||||
"SummaryComponent",
|
||||
"TocComponent",
|
||||
]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from app.modules.orion.context import ReportContext
|
||||
|
||||
|
||||
class ReportComponent(ABC):
|
||||
anchor: str
|
||||
title: str
|
||||
|
||||
@abstractmethod
|
||||
def render(self, context: ReportContext) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from app.modules.orion.components.base import ReportComponent
|
||||
from app.modules.orion.context import ReportContext
|
||||
from app.modules.orion.html import definition_list, paragraph, section, table, text, yes_no
|
||||
|
||||
|
||||
class CoverComponent(ReportComponent):
|
||||
anchor = "cover"
|
||||
title = "Deckblatt"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
validation = context.validation
|
||||
rows = definition_list(
|
||||
[
|
||||
("Berichtsnummer", validation.report_number),
|
||||
("Validierungsart", validation.validation_type),
|
||||
("Projekt", validation.project),
|
||||
("Pruefdatum", validation.performed_on),
|
||||
("Pruefungsort", validation.test_location),
|
||||
("Pruefer", validation.examiner_name),
|
||||
("Gesamtergebnis", validation.result),
|
||||
("Status", validation.status),
|
||||
("Ansprechpartner", context.contact.full_name if context.contact else "nicht erfasst"),
|
||||
("Mitwirkende Personen", validation.participants or "nicht erfasst"),
|
||||
]
|
||||
)
|
||||
return (
|
||||
"<section class=\"cover-page\" id=\"cover\">"
|
||||
"<div class=\"cover-kicker\">Neutraler Logo-Platzhalter · Validation Suite</div>"
|
||||
"<h1>Pruefbericht zur Validierung</h1>"
|
||||
"<p class=\"cover-subtitle\">Funktions- und Leistungsqualifikation Klein-Sterilisator</p>"
|
||||
f"<p class=\"cover-subtitle\">{text(context.customer.name)}</p>"
|
||||
f"{rows}"
|
||||
"<div class=\"signature-grid\"><div>Unterschrift technische Validierung</div><div>Unterschrift Auftraggeber</div></div>"
|
||||
"</section>"
|
||||
)
|
||||
|
||||
|
||||
class TocComponent(ReportComponent):
|
||||
anchor = "toc"
|
||||
title = "Inhaltsverzeichnis"
|
||||
|
||||
def __init__(self, components: list[ReportComponent]) -> None:
|
||||
self.components = components
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
links = "".join(
|
||||
f"<li><a href=\"#{component.anchor}\">{text(component.title)}</a></li>"
|
||||
for component in self.components
|
||||
if component.anchor not in {"cover", "toc"}
|
||||
)
|
||||
return section(self.anchor, self.title, f"<ol class=\"toc-list\">{links}</ol>")
|
||||
|
||||
|
||||
class SummaryComponent(ReportComponent):
|
||||
anchor = "summary"
|
||||
title = "Zusammenfassendes Ergebnis der Validierung"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
validation = context.validation
|
||||
body = definition_list(
|
||||
[
|
||||
("Kunde", context.customer.name),
|
||||
("Standort", context.location.name if context.location else None),
|
||||
("Geraet", f"{context.device.manufacturer} {context.device.model}" if context.device else None),
|
||||
("Pruefmittel", len(context.equipment)),
|
||||
("Ergebnis", validation.result),
|
||||
("Mitwirkende Personen", validation.participants),
|
||||
("Hinweis auf naechste Leistungsbeurteilung", validation.next_validation_on or "nicht erfasst"),
|
||||
]
|
||||
)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
|
||||
class StaticTextComponent(ReportComponent):
|
||||
def __init__(self, anchor: str, title: str, body: str = "nicht erfasst") -> None:
|
||||
self.anchor = anchor
|
||||
self.title = title
|
||||
self.body = body
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
return section(self.anchor, self.title, f"<p>{text(self.body)}</p>")
|
||||
|
||||
|
||||
class CustomerComponent(ReportComponent):
|
||||
anchor = "customer"
|
||||
title = "Kunde, Standort und Ansprechpartner"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
customer = context.customer
|
||||
location = context.location
|
||||
contact = context.contact
|
||||
body = "<h3>Kunde</h3>" + definition_list(
|
||||
[
|
||||
("Name", customer.name),
|
||||
("Typ", customer.customer_type.value),
|
||||
("Adresse", " ".join(filter(None, [customer.street, customer.postal_code, customer.city]))),
|
||||
("Telefon", customer.phone),
|
||||
("Mail", customer.email),
|
||||
("Betreiber", context.validation.operator_name),
|
||||
("QM", customer.quality_manager),
|
||||
("Hygienebeauftragter", customer.hygiene_officer),
|
||||
]
|
||||
)
|
||||
if location:
|
||||
body += "<h3>Standort</h3>" + definition_list(
|
||||
[
|
||||
("Name", location.name),
|
||||
("Adresse", " ".join(filter(None, [location.street, location.postal_code, location.city]))),
|
||||
("Raum", location.room),
|
||||
]
|
||||
)
|
||||
if contact:
|
||||
body += "<h3>Ansprechpartner</h3>" + definition_list(
|
||||
[
|
||||
("Name", contact.full_name),
|
||||
("Funktion", contact.function),
|
||||
("Mail", contact.email),
|
||||
("Telefon", contact.phone),
|
||||
]
|
||||
)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
|
||||
class DeviceComponent(ReportComponent):
|
||||
anchor = "device"
|
||||
title = "Geraet"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
device = context.device
|
||||
if device is None:
|
||||
return section(self.anchor, self.title, "")
|
||||
body = definition_list(
|
||||
[
|
||||
("Hersteller", device.manufacturer),
|
||||
("Modell", device.model),
|
||||
("Typ", device.device_type),
|
||||
("Seriennummer", device.serial_number),
|
||||
("Baujahr", device.year_built),
|
||||
("Inbetriebnahme", device.commissioned_on),
|
||||
("Kammervolumen", f"{device.chamber_volume_liters} Liter" if device.chamber_volume_liters else None),
|
||||
("Dampferzeugung", device.steam_generation),
|
||||
("Wasseraufbereitung", device.water_treatment),
|
||||
("Dokumentation", device.documentation),
|
||||
("Lieferant", device.supplier),
|
||||
]
|
||||
)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
|
||||
class EquipmentComponent(ReportComponent):
|
||||
anchor = "equipment"
|
||||
title = "Pruefmittel"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
rows = [
|
||||
[
|
||||
item.kind.value,
|
||||
item.manufacturer,
|
||||
item.model,
|
||||
item.serial_number,
|
||||
item.calibrated_on,
|
||||
item.calibration_due_on,
|
||||
item.status.value,
|
||||
]
|
||||
for item in context.equipment
|
||||
]
|
||||
return section(
|
||||
self.anchor,
|
||||
self.title,
|
||||
table(
|
||||
["Art", "Hersteller", "Modell", "Seriennummer", "Kalibriert", "Gueltig bis", "Status"],
|
||||
rows,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class EnvironmentComponent(ReportComponent):
|
||||
anchor = "environment"
|
||||
title = "Umgebungsbedingungen"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
data = context.validation.environment_conditions or {}
|
||||
body = definition_list(
|
||||
[
|
||||
("Raumtemperatur", data.get("room_temperature")),
|
||||
("relative Luftfeuchtigkeit", data.get("humidity")),
|
||||
("Pruefzeit", data.get("test_time")),
|
||||
]
|
||||
)
|
||||
rows = [[item.get("text"), yes_no(item.get("value")), item.get("comment")] for item in data.get("checks", [])]
|
||||
if rows:
|
||||
body += table(["Pruefpunkt", "Bewertung", "Kommentar"], rows)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
|
||||
class ChecklistComponent(ReportComponent):
|
||||
anchor = "checklists"
|
||||
title = "Dokumentations- und Leistungschecklisten"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
documentation = context.validation.documentation_checklist or []
|
||||
performance = context.validation.performance_checklist or []
|
||||
body = "<h3>Dokumentation</h3>" + self._render_items(documentation)
|
||||
body += "<h3>Leistung</h3>" + self._render_items(performance)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
def _render_items(self, items: list[dict]) -> str:
|
||||
rows = [
|
||||
[item.get("number"), item.get("text"), yes_no(item.get("value")), item.get("comment")]
|
||||
for item in items
|
||||
]
|
||||
return table(["Nr.", "Pruefpunkt", "Bewertung", "Kommentar"], rows)
|
||||
|
||||
|
||||
class ProgramComponent(ReportComponent):
|
||||
anchor = "programs"
|
||||
title = "Programme"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
programs = [item for item in (context.validation.programs or []) if item.get("selected")]
|
||||
rows = [[index + 1, item.get("name"), "Eigenes Programm" if item.get("custom") else "Standard"] for index, item in enumerate(programs)]
|
||||
return section(self.anchor, self.title, table(["Nr.", "Programm", "Typ"], rows))
|
||||
|
||||
|
||||
class LoadingComponent(ReportComponent):
|
||||
anchor = "loading"
|
||||
title = "Beladungsmuster"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
rows = [
|
||||
[item.get("run"), item.get("pattern"), item.get("description"), len(item.get("images", []))]
|
||||
for item in context.validation.loading_patterns or []
|
||||
]
|
||||
return section(self.anchor, self.title, table(["Testlauf", "Beladungsmuster", "Beschreibung", "Bilder"], rows))
|
||||
|
||||
|
||||
class MeasurementComponent(ReportComponent):
|
||||
anchor = "measurements"
|
||||
title = "Messdaten"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
rows = [
|
||||
[
|
||||
item.get("name"),
|
||||
item.get("start_time"),
|
||||
item.get("end_time"),
|
||||
item.get("duration"),
|
||||
item.get("leak_rate"),
|
||||
item.get("min_temperature"),
|
||||
item.get("max_temperature"),
|
||||
item.get("temperature_band"),
|
||||
item.get("holding_time"),
|
||||
item.get("pressure"),
|
||||
item.get("result"),
|
||||
]
|
||||
for item in context.validation.measurement_data or []
|
||||
]
|
||||
body = table(
|
||||
[
|
||||
"Bereich",
|
||||
"Start",
|
||||
"Ende",
|
||||
"Dauer",
|
||||
"Leckrate",
|
||||
"Min. Temp.",
|
||||
"Max. Temp.",
|
||||
"Band",
|
||||
"Haltezeit",
|
||||
"Druck",
|
||||
"Ergebnis",
|
||||
],
|
||||
rows,
|
||||
"compact",
|
||||
)
|
||||
winlog_rows = []
|
||||
for item in context.validation.measurement_data or []:
|
||||
for imported in item.get("imports", []):
|
||||
winlog_rows.append([item.get("name"), imported.get("filename"), imported.get("content_type")])
|
||||
if winlog_rows:
|
||||
body += "<h3>Winlog-Dateien</h3>" + table(["Bereich", "Datei", "Typ"], winlog_rows)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
|
||||
class DryingComponent(ReportComponent):
|
||||
anchor = "drying"
|
||||
title = "Trocknung"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
data = context.validation.drying or {}
|
||||
body = definition_list(
|
||||
[
|
||||
("Startgewicht", data.get("start_weight")),
|
||||
("Endgewicht", data.get("end_weight")),
|
||||
("Differenz", data.get("difference")),
|
||||
("Bewertung", data.get("rating")),
|
||||
("Bemerkung", data.get("comment")),
|
||||
]
|
||||
)
|
||||
return section(self.anchor, self.title, body)
|
||||
|
||||
|
||||
class RecommendationComponent(ReportComponent):
|
||||
anchor = "recommendations"
|
||||
title = "Empfehlungen und Auflagen"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
rows = [
|
||||
[item.get("number"), item.get("text"), item.get("deadline"), item.get("status")]
|
||||
for item in context.validation.recommendations or []
|
||||
]
|
||||
return section(self.anchor, self.title, table(["Nr.", "Text", "Frist", "Status"], rows))
|
||||
|
||||
|
||||
class AttachmentComponent(ReportComponent):
|
||||
anchor = "attachments"
|
||||
title = "Bilder und Anlagen"
|
||||
|
||||
def render(self, context: ReportContext) -> str:
|
||||
rows = [
|
||||
[item.get("order"), item.get("category"), item.get("filename"), item.get("description")]
|
||||
for item in sorted(context.validation.attachments or [], key=lambda row: row.get("order") or 0)
|
||||
]
|
||||
return section(self.anchor, self.title, table(["Reihenfolge", "Kategorie", "Datei", "Beschreibung"], rows))
|
||||
Loading…
Add table
Add a link
Reference in a new issue