496 lines
24 KiB
Python
496 lines
24 KiB
Python
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
from reportlab.lib import colors
|
||
from reportlab.lib.enums import TA_CENTER, TA_LEFT
|
||
from reportlab.lib.pagesizes import A4
|
||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||
from reportlab.lib.units import mm
|
||
from reportlab.platypus import (
|
||
BaseDocTemplate, Frame, PageTemplate, Paragraph, Spacer, Table, TableStyle,
|
||
PageBreak, KeepTogether
|
||
)
|
||
|
||
PAGE_W, PAGE_H = A4
|
||
|
||
|
||
def _header_footer(canvas, doc):
|
||
canvas.saveState()
|
||
canvas.setStrokeColor(colors.HexColor("#D0D5DD"))
|
||
canvas.line(18*mm, PAGE_H-16*mm, PAGE_W-18*mm, PAGE_H-16*mm)
|
||
canvas.setFont("Helvetica-Bold", 8)
|
||
canvas.setFillColor(colors.HexColor("#344054"))
|
||
canvas.drawString(18*mm, PAGE_H-12*mm, "VALIDATION SUITE - PRÜFBERICHT")
|
||
canvas.setFont("Helvetica", 8)
|
||
canvas.setFillColor(colors.HexColor("#667085"))
|
||
canvas.drawRightString(PAGE_W-18*mm, 10*mm, f"Seite {doc.page}")
|
||
canvas.restoreState()
|
||
|
||
|
||
def _table(data, widths=None, header=True):
|
||
t = Table(data, colWidths=widths, repeatRows=1 if header else 0)
|
||
style = [
|
||
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#D0D5DD")),
|
||
("VALIGN", (0,0), (-1,-1), "TOP"),
|
||
("LEFTPADDING", (0,0), (-1,-1), 6),
|
||
("RIGHTPADDING", (0,0), (-1,-1), 6),
|
||
("TOPPADDING", (0,0), (-1,-1), 6),
|
||
("BOTTOMPADDING", (0,0), (-1,-1), 6),
|
||
]
|
||
if header:
|
||
style += [
|
||
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#EEF2FF")),
|
||
("TEXTCOLOR", (0,0), (-1,0), colors.HexColor("#3730A3")),
|
||
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
|
||
]
|
||
t.setStyle(TableStyle(style))
|
||
return t
|
||
|
||
|
||
def _placeholder(title, height=85):
|
||
data = [[Paragraph(f"<b>{title}</b><br/><font color='#667085'>Platzhalter - wird aus der jeweiligen Validierung ersetzt.</font>", getSampleStyleSheet()["BodyText"])]]
|
||
t = Table(data, colWidths=[165*mm], rowHeights=[height*mm])
|
||
t.setStyle(TableStyle([
|
||
("BOX",(0,0),(-1,-1),1,colors.HexColor("#98A2B3")),
|
||
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#F8FAFC")),
|
||
("VALIGN",(0,0),(-1,-1),"MIDDLE"),
|
||
("ALIGN",(0,0),(-1,-1),"CENTER"),
|
||
]))
|
||
return t
|
||
|
||
|
||
def build_validation_report(validation, equipment, output_dir: Path) -> str:
|
||
output_dir.mkdir(parents=True, exist_ok=True)
|
||
filename = f"{validation.report_number}_V2_Vorschau.pdf"
|
||
path = output_dir / filename
|
||
|
||
styles = getSampleStyleSheet()
|
||
styles.add(ParagraphStyle(name="CoverTitle", parent=styles["Title"], fontSize=24, leading=29, alignment=TA_CENTER, textColor=colors.HexColor("#172033")))
|
||
styles.add(ParagraphStyle(name="Chapter", parent=styles["Heading1"], fontSize=17, leading=21, spaceBefore=8, spaceAfter=10, textColor=colors.HexColor("#172033")))
|
||
styles.add(ParagraphStyle(name="Section", parent=styles["Heading2"], fontSize=13, leading=17, spaceBefore=8, spaceAfter=7, textColor=colors.HexColor("#344054")))
|
||
styles.add(ParagraphStyle(name="Small", parent=styles["BodyText"], fontSize=8, leading=11, textColor=colors.HexColor("#667085")))
|
||
styles.add(ParagraphStyle(name="BodyV2", parent=styles["BodyText"], fontSize=9.3, leading=13.5, spaceAfter=6))
|
||
|
||
frame = Frame(18*mm, 17*mm, PAGE_W-36*mm, PAGE_H-36*mm, id="normal")
|
||
doc = BaseDocTemplate(str(path), pagesize=A4, leftMargin=18*mm, rightMargin=18*mm, topMargin=20*mm, bottomMargin=18*mm)
|
||
doc.addPageTemplates([PageTemplate(id="main", frames=frame, onPage=_header_footer)])
|
||
|
||
story = []
|
||
|
||
# Cover
|
||
story += [
|
||
Spacer(1, 24*mm),
|
||
_placeholder("GESCHÜTZTES ORIGINALLOGO", 25),
|
||
Spacer(1, 14*mm),
|
||
Paragraph("Prüfbericht zur Validierung", styles["CoverTitle"]),
|
||
Paragraph("Funktions- und Leistungsqualifikation Klein-Sterilisator", styles["Heading2"]),
|
||
Spacer(1, 10*mm),
|
||
_table([
|
||
["Hersteller / Gerät", f"{validation.device.manufacturer} / {validation.device.model}"],
|
||
["Seriennummer", validation.device.serial_number],
|
||
["Projekt", validation.project],
|
||
["Auftraggeber / Standort", f"{validation.customer.practice_name}<br/>{validation.customer.operator_name}<br/>{validation.customer.address}"],
|
||
["Ort und Datum der Prüfung", f"{validation.test_location}, {validation.test_date.strftime('%d.%m.%Y')}"],
|
||
["Technische Validierung durchgeführt", validation.examiner],
|
||
["Mitwirkende Personen", validation.participants or "–"],
|
||
["Berichtsnummer", validation.report_number],
|
||
], widths=[55*mm, 110*mm], header=False),
|
||
Spacer(1, 12*mm),
|
||
Paragraph("Nur gültig mit Unterschrift des verantwortlichen Betreibers und des Prüfers.", styles["Small"]),
|
||
Spacer(1, 12*mm),
|
||
_table([
|
||
["Ort / Datum", "Unterschrift verantwortlicher Betreiber"],
|
||
["", ""],
|
||
["Ort / Datum", "Unterschrift Prüfer / Berichtsersteller"],
|
||
["", ""],
|
||
], widths=[55*mm, 110*mm], header=False),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Summary
|
||
story += [
|
||
Paragraph("Zusammenfassendes Ergebnis der Validierung", styles["Chapter"]),
|
||
Paragraph(
|
||
f"Es wurde die Validierung (Leistungsbeurteilung) eines sich im Betrieb befindlichen "
|
||
f"Klein-Sterilisators (< 1 STE / 60 Liter) durchgeführt. Im Rahmen der Leistungsqualifikation "
|
||
f"wurden mechanische, thermoelektrische und verfahrenstechnische Leistungsanforderungen überprüft.",
|
||
styles["BodyV2"]
|
||
),
|
||
Paragraph(
|
||
"Die geforderten Spezifikationen, Prozesse und Umgebungsbedingungen wurden überprüft. "
|
||
"Die Bewertung der Messdaten wird nach dem Import der Winlog-Validation-Daten automatisch "
|
||
"in die Tabellen und Diagramme dieses Berichts übernommen.",
|
||
styles["BodyV2"]
|
||
),
|
||
_table([
|
||
["Prüfung", "Ergebnis"],
|
||
["Vakuumtest", validation.result],
|
||
["Bowie-Dick / Leerkammerprofil", validation.result],
|
||
["Testlauf 1 - 134 °C hohl verpackt", validation.result],
|
||
["Testlauf 2 - 134 °C hohl verpackt", validation.result],
|
||
["Testlauf 3 - 134 °C hohl verpackt", validation.result],
|
||
["Gesamtergebnis", validation.result],
|
||
], widths=[120*mm, 45*mm]),
|
||
Spacer(1, 8*mm),
|
||
Paragraph(
|
||
f"Die nächste Leistungsbeurteilung / Validierung ist vorgesehen für "
|
||
f"{validation.next_validation.strftime('%m/%Y') if validation.next_validation else 'noch festzulegen'}. "
|
||
"Prozessrelevante Änderungen können eine erneute Leistungsüberprüfung notwendig machen.",
|
||
styles["BodyV2"]
|
||
),
|
||
PageBreak(),
|
||
]
|
||
|
||
# TOC-style overview
|
||
toc = [
|
||
("1.", "Funktionsqualifikation (BQ)"),
|
||
("1.1", "Anlass und Ziel der Prüfung"),
|
||
("1.2", "Gesetzliche Grundlagen"),
|
||
("1.3", "Angaben zum Gerät"),
|
||
("1.4", "Leistungsüberprüfung"),
|
||
("1.5", "Checkliste Leistungsanforderung"),
|
||
("1.8", "Umgebungsbedingungen"),
|
||
("1.9", "Chargenkontrolle"),
|
||
("1.10", "Beschreibung der verwendeten Programme"),
|
||
("1.11", "Beladungsbeschreibungen"),
|
||
("2.", "Eingesetzte Prüfmittel"),
|
||
("2.1", "Beschreibung der Messgeräte"),
|
||
("2.2", "Prüfmittel zur thermoelektrischen Untersuchung"),
|
||
("3.", "Prüfkonfiguration"),
|
||
("3.1", "Leistungsbeurteilung - Thermoelektrische Prüfungen"),
|
||
("4.", "Ergebnisse der Validierung"),
|
||
("4.2", "Empfehlungen und Auflagen"),
|
||
("5.", "Anhang"),
|
||
("6.", "Programmabläufe / Zyklen"),
|
||
("7.", "Risikoeinstufung / Abschlussgespräch"),
|
||
("8.", "Zertifikate"),
|
||
("9.", "Werkskalibrierzertifikate Sensoren"),
|
||
]
|
||
story += [Paragraph("Inhaltsverzeichnis", styles["Chapter"]), _table([["Kapitel","Inhalt"]] + toc, widths=[25*mm,140*mm]), PageBreak()]
|
||
|
||
# Static chapters
|
||
story += [
|
||
Paragraph("1. Funktionsqualifikation (BQ)", styles["Chapter"]),
|
||
Paragraph("1.1 Anlass und Ziel der Prüfung", styles["Section"]),
|
||
Paragraph(
|
||
f"Es wurde die Validierung eines sich im Betrieb befindlichen {validation.device.manufacturer} "
|
||
f"{validation.device.model} Klein-Sterilisators durchgeführt. Die Validierung gilt nur für das "
|
||
"zum Zeitpunkt der Prüfung berücksichtigte Gerät, die geprüfte Beladung und die verwendeten Programme.",
|
||
styles["BodyV2"]
|
||
),
|
||
Paragraph(
|
||
"Mit der Validierung wird der dokumentierte Nachweis erbracht, dass ein Verfahren die vorher "
|
||
"spezifizierten Anforderungen im praktischen Einsatz reproduzierbar erfüllt.",
|
||
styles["BodyV2"]
|
||
),
|
||
Paragraph("1.2 Gesetzliche Grundlagen", styles["Section"]),
|
||
Paragraph(
|
||
"Die Validierung erfolgt in Anlehnung an die für den Anwendungsbereich maßgeblichen Regelwerke, "
|
||
"Normen, Leitlinien und Empfehlungen. Die in der Originalvorlage enthaltenen fachlichen Textbausteine "
|
||
"werden in der finalen Berichtsversion unverändert versioniert übernommen.",
|
||
styles["BodyV2"]
|
||
),
|
||
_table([
|
||
["Regelwerk / Grundlage"],
|
||
["DIN EN ISO 17665 - Sterilisation von Produkten für die Gesundheitsfürsorge"],
|
||
["DIN EN 13060 - Dampf-Kleinsterilisatoren"],
|
||
["Leitlinien der DGKH, DGSV und AKI"],
|
||
["Empfehlungen von RKI und BfArM zur Aufbereitung von Medizinprodukten"],
|
||
], widths=[165*mm]),
|
||
PageBreak(),
|
||
]
|
||
|
||
story += [
|
||
Paragraph("1.3 Angaben zum Gerät", styles["Chapter"]),
|
||
_table([
|
||
["Merkmal", "Angabe"],
|
||
["Sterilisator-Typ", f"{validation.device.manufacturer} {validation.device.model}"],
|
||
["Seriennummer", validation.device.serial_number],
|
||
["Baujahr", validation.device.year_built or "–"],
|
||
["Erstinbetriebnahme", validation.device.commissioning_date.strftime("%d.%m.%Y") if validation.device.commissioning_date else "–"],
|
||
["Kammervolumen", validation.device.chamber_volume or "–"],
|
||
["Art der Dampferzeugung", validation.device.steam_generation or "–"],
|
||
["Wasserqualität / Versorgung", validation.device.water_supply or "–"],
|
||
["Chargendokumentation", validation.device.documentation or "–"],
|
||
["Lieferant", validation.device.supplier or "–"],
|
||
["Betreiber", validation.customer.operator_name],
|
||
["QM / Hygienebeauftragte", validation.customer.hygiene_contact or "–"],
|
||
], widths=[60*mm,105*mm]),
|
||
Spacer(1, 5*mm),
|
||
_placeholder("Gerätefoto - aktueller Sterilisator", 80),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Checklists
|
||
checklist_rows = [
|
||
["Prüfkriterium", "Ja", "Nein", "Kommentar"],
|
||
["Trennung von Controlling- und Monitoring-Schaltkreisen", "X", "", ""],
|
||
["Temperaturüberwachung Sterilisationsprozess", "X", "", ""],
|
||
["Drucküberwachung Sterilisationsprozess", "X", "", ""],
|
||
["Wasserzulauf / Ablauf", "X", "", "Automatische Versorgung"],
|
||
["Automatische Aufzeichnung der Sterilisationsergebnisse", "X", "", validation.device.documentation or ""],
|
||
["Überwachung Wasserqualität", "X", "", "Interne Überwachung"],
|
||
["Separater Netzanschluss", "X", "", ""],
|
||
["Sichtkontrolle Kessel, Sensoren, Türbereich", "X", "", ""],
|
||
["Bedienungshandbuch / Installationsprotokoll", "X", "", "Vorhanden"],
|
||
["Wartungshandbuch", "X", "", "Vorhanden"],
|
||
["Arbeitsanweisungen / Checklisten / Beladungsmuster", "X", "", "Werden im System dokumentiert"],
|
||
["Schulungsnachweise der Mitarbeiter", "X", "", ""],
|
||
["Risikoeinstufung der Medizinprodukte", "X", "", ""],
|
||
["Freigabedokumentation vorhanden", "X", "", ""],
|
||
]
|
||
story += [
|
||
Paragraph("1.5 Checkliste Leistungsanforderung", styles["Chapter"]),
|
||
_table(checklist_rows, widths=[90*mm,15*mm,15*mm,45*mm]),
|
||
PageBreak(),
|
||
Paragraph("1.8 Umgebungsbedingungen", styles["Chapter"]),
|
||
_table([
|
||
["Kontrolle", "Ja", "Nein", "Kommentar"],
|
||
["Änderungen seit letzter Validierung", "", "X", validation.validation_type],
|
||
["Aufstellungsort und Umgebungsbedingungen in Ordnung", "X", "", ""],
|
||
["Relative Luftfeuchtigkeit in Ordnung", "X", "", validation.room_humidity or ""],
|
||
["Gerät nach Sichtprüfung in Ordnung", "X", "", ""],
|
||
["Trennung zwischen Rein- und Unreinbereich", "X", "", ""],
|
||
["Sterilgutlagerung unter geschützten Bedingungen", "X", "", ""],
|
||
["Raumbelüftung vorhanden", "X", "", ""],
|
||
["Desinfektionsmittel-, Seifen- und Papierhandtuchspender vorhanden", "X", "", ""],
|
||
], widths=[92*mm,14*mm,14*mm,45*mm]),
|
||
Spacer(1, 6*mm),
|
||
Paragraph(
|
||
f"Aufbereitungsraum: Raumtemperatur {validation.room_temperature or '–'}, "
|
||
f"relative Luftfeuchtigkeit {validation.room_humidity or '–'}, gemessen am "
|
||
f"{validation.test_date.strftime('%d.%m.%Y')}.",
|
||
styles["BodyV2"]
|
||
),
|
||
_placeholder("Abbildung: Übersicht Aufbereitungsraum", 55),
|
||
Spacer(1, 4*mm),
|
||
_placeholder("Abbildung: Reiner / unreiner Bereich", 55),
|
||
PageBreak(),
|
||
]
|
||
|
||
story += [
|
||
Paragraph("1.9 Chargenkontrolle", styles["Chapter"]),
|
||
_table([
|
||
["Kontrolle", "Durchführung", "Prüfintervall", "Prüfkörper"],
|
||
["Vakuumtest", "Ja", "wöchentlich", "–"],
|
||
["Bowie-Dick-Test", "nach Bedarf", "gemäß Validierungsplan", "Prüfkörper"],
|
||
["Chargenüberwachung Chemoindikatoren", "Ja", "bei jeder Charge", "Helix-Test"],
|
||
], widths=[55*mm,35*mm,40*mm,35*mm]),
|
||
Paragraph("1.10 Beschreibung der verwendeten Programme", styles["Chapter"]),
|
||
Paragraph(
|
||
"Für alle praxisrelevanten Beladungen werden die nachfolgenden Programme verwendet: "
|
||
"Vakuumtest, Leerkammerprofil / Bowie-Dick-Simulation sowie drei Testläufe im Programm "
|
||
"134 °C hohl verpackt.",
|
||
styles["BodyV2"]
|
||
),
|
||
Paragraph("1.11 Beladungsbeschreibungen", styles["Chapter"]),
|
||
Paragraph(
|
||
"Als schwierigste zu sterilisierende Beladung wird eine repräsentative Worst-Case-Beladung "
|
||
"aus dem tatsächlichen Instrumentarium definiert. Die konkrete Beladung und alle Fotos werden "
|
||
"je Validierung neu erfasst.",
|
||
styles["BodyV2"]
|
||
),
|
||
_placeholder("Referenzbeladung - Testlauf 1", 60),
|
||
Spacer(1, 4*mm),
|
||
_placeholder("Referenzbeladung - Testlauf 2", 60),
|
||
PageBreak(),
|
||
_placeholder("Referenzbeladung - Testlauf 3", 60),
|
||
Spacer(1, 5*mm),
|
||
_placeholder("Sensorpositionierung / Prüfkonfiguration", 80),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Equipment
|
||
eq_data = [["Bezeichnung", "Typ", "Seriennummer", "Messbereich", "Kalibrierung"]]
|
||
for e in equipment:
|
||
eq_data.append([
|
||
e.designation,
|
||
f"{e.manufacturer or ''} {e.model or ''}".strip(),
|
||
e.serial_number,
|
||
e.measurement_range or "–",
|
||
e.calibration_date.strftime("%d.%m.%Y") if e.calibration_date else "–",
|
||
])
|
||
story += [
|
||
Paragraph("2. Eingesetzte Prüfmittel", styles["Chapter"]),
|
||
Paragraph(
|
||
"Für die Reproduzierbarkeit der thermoelektrischen Prüfungen werden hochauflösende "
|
||
"Temperatur- und Drucksensoren eingesetzt. Die Auswahl erfolgt je Validierung aus dem "
|
||
"Prüfmittelregister.",
|
||
styles["BodyV2"]
|
||
),
|
||
Paragraph("2.1 Beschreibung der Messgeräte", styles["Section"]),
|
||
_table(eq_data, widths=[40*mm,42*mm,30*mm,33*mm,25*mm]),
|
||
PageBreak(),
|
||
Paragraph("2.2 Prüfmittel zur thermoelektrischen Untersuchung", styles["Chapter"]),
|
||
_placeholder("Abbildung / Beschreibung Bowie-Dick-Simulationstest", 70),
|
||
Spacer(1, 5*mm),
|
||
_placeholder("Abbildung / Beschreibung Chargenüberwachungssystem", 70),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Performance qualification
|
||
story += [
|
||
Paragraph("3. Prüfkonfiguration", styles["Chapter"]),
|
||
_placeholder("Beladungsebenen und Positionierung der Sensoren", 110),
|
||
Spacer(1, 6*mm),
|
||
_table([
|
||
["Sensor", "Position / Kanal"],
|
||
["Drucksensor P111", "Ebene 1, hinten Mitte"],
|
||
["Temperatursensor T235", "Ebene 1, vorne Mitte"],
|
||
["Temperatursensor T240", "Position gemäß Fotodokumentation"],
|
||
], widths=[70*mm,95*mm]),
|
||
PageBreak(),
|
||
Paragraph("3. Leistungsqualifikation (LQ)", styles["Chapter"]),
|
||
Paragraph("3.1 Leistungsbeurteilung - Thermoelektrische Prüfungen", styles["Section"]),
|
||
Paragraph(
|
||
"Der Nachweis der erforderlichen Prozessleistung gilt als erbracht, wenn die in der "
|
||
"Berichtsvorlage festgelegten Temperatur-, Druck-, Zeit- und Trocknungskriterien eingehalten werden. "
|
||
"Nach Vorliegen des Winlog-Exports werden diese Regeln automatisch auf die importierten Messdaten angewandt.",
|
||
styles["BodyV2"]
|
||
),
|
||
Paragraph("1. Vakuumtest", styles["Section"]),
|
||
Paragraph(
|
||
f"Der Leckagetest wurde durchgeführt. Erfasste Leckrate: "
|
||
f"{validation.vacuum_leak_rate or 'noch nicht importiert'}.",
|
||
styles["BodyV2"]
|
||
),
|
||
_placeholder("Winlog-Diagramm Vakuumtest", 95),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Test runs, each 3 pages
|
||
for idx in range(1, 4):
|
||
story += [
|
||
Paragraph(f"{idx+1}. Standardbeladung ({idx}. Durchlauf)", styles["Chapter"]),
|
||
Paragraph(f"Testlauf {idx} - 134 °C hohl verpackt", styles["Section"]),
|
||
_table([
|
||
["Parameter", "Wert"],
|
||
["Programm", "134 °C hohl verpackt"],
|
||
["Beladung", f"Referenzbeladung {idx}"],
|
||
["Gesamtergebnis", validation.result],
|
||
["Sterilisationstemperatur", "Winlog-Import ausstehend"],
|
||
["Haltezeit", "Winlog-Import ausstehend"],
|
||
["Temperaturband", "Winlog-Import ausstehend"],
|
||
["Ausgleichszeit", "Winlog-Import ausstehend"],
|
||
], widths=[70*mm,95*mm]),
|
||
Spacer(1, 6*mm),
|
||
_placeholder(f"Beladungsfoto Testlauf {idx}", 70),
|
||
PageBreak(),
|
||
Paragraph(f"Gesamte Messung Testlauf {idx}", styles["Chapter"]),
|
||
_placeholder(f"Winlog-Gesamtdiagramm Testlauf {idx}", 150),
|
||
PageBreak(),
|
||
Paragraph(f"Gesamtergebnis Testlauf {idx}", styles["Chapter"]),
|
||
_table([
|
||
["Bewertungskriterium", "Ergebnis"],
|
||
["Temperaturen nicht unter Sterilisationstemperatur", "ausstehend"],
|
||
["Maximal 3 K oberhalb der gewählten Temperatur", "ausstehend"],
|
||
["Abweichung zwischen Sensoren maximal 2 K", "ausstehend"],
|
||
["Ausgleichszeit innerhalb Vorgabe", "ausstehend"],
|
||
["Haltezeit eingehalten", "ausstehend"],
|
||
["Gesamtergebnis", validation.result],
|
||
], widths=[120*mm,45*mm]),
|
||
Spacer(1, 8*mm),
|
||
_placeholder(f"Detaildiagramme / Kanäle Testlauf {idx}", 95),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Results and recommendations
|
||
story += [
|
||
Paragraph("4. Ergebnisse der Validierung", styles["Chapter"]),
|
||
Paragraph("4.1 Vakuumtest", styles["Section"]),
|
||
_table([
|
||
["Prüfung", "Soll", "Ist", "Bewertung"],
|
||
["Leckrate", "max. 1,3 mbar/min", validation.vacuum_leak_rate or "–", validation.result],
|
||
], widths=[45*mm,45*mm,35*mm,40*mm]),
|
||
Paragraph("4.1.2 Testläufe 1 bis 3 - Programm 134 °C", styles["Section"]),
|
||
_table([
|
||
["Testlauf", "Temperaturband", "Haltezeit", "Trocknung", "Ergebnis"],
|
||
["Test 1", "Winlog", "Winlog", "Prüfung", validation.result],
|
||
["Test 2", "Winlog", "Winlog", "Prüfung", validation.result],
|
||
["Test 3", "Winlog", "Winlog", "Prüfung", validation.result],
|
||
], widths=[30*mm,38*mm,32*mm,32*mm,33*mm]),
|
||
Spacer(1, 8*mm),
|
||
Paragraph("Nachweis der Trocknungseigenschaften Testläufe 1 bis 3", styles["Section"]),
|
||
_placeholder("Trocknungsnachweis / Wägeergebnisse", 70),
|
||
PageBreak(),
|
||
Paragraph("4.2 Empfehlungen und Auflagen", styles["Chapter"]),
|
||
Paragraph(validation.recommendations or "Keine Auflagen erfasst.", styles["BodyV2"]),
|
||
_table([
|
||
["Nr.", "Auflage / Empfehlung", "Frist", "Status"],
|
||
["1", "Wird über die Weboberfläche erfasst.", "–", "offen"],
|
||
], widths=[15*mm,105*mm,25*mm,25*mm]),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Annex skeleton. This deliberately yields a long report similar to source.
|
||
annexes = [
|
||
"5.1 Vakuumtest - Winlog-Auswertung",
|
||
"Testlauf Bowie-Dick / Leerkammerprofil",
|
||
"Testlauf 1 - Allgemeine Angaben",
|
||
"Testlauf 1 - Gesamte Messung",
|
||
"Testlauf 1 - Gesamtergebnis",
|
||
"Testlauf 2 - Allgemeine Angaben",
|
||
"Testlauf 2 - Gesamte Messung",
|
||
"Testlauf 2 - Gesamtergebnis",
|
||
"Testlauf 3 - Allgemeine Angaben",
|
||
"Testlauf 3 - Gesamte Messung",
|
||
"Testlauf 3 - Gesamtergebnis",
|
||
"Chargen- und Freigabedokumentation der Praxis",
|
||
"Chargendokumentation Bowie-Dick / Leerkammerprofil",
|
||
"Chargendokumentation Testlauf 1",
|
||
"Chargendokumentation Testlauf 2",
|
||
"Chargendokumentation Testlauf 3",
|
||
"5.2 Freigabedokumentation / Routineprüfungen",
|
||
"5.3 Nachweis umgeschlagener Indikatoren",
|
||
"6. Programmabläufe / Zyklen des Sterilisators",
|
||
"6.1 Zertifikate Praxis",
|
||
"7. Risikoeinstufung der Praxis",
|
||
"7.1 Abschlussgespräch",
|
||
"8. Zertifikate",
|
||
"9. Werkskalibrierzertifikate Sensoren",
|
||
]
|
||
|
||
for annex in annexes:
|
||
story += [
|
||
Paragraph(annex, styles["Chapter"]),
|
||
Paragraph(
|
||
"Dieses Kapitel ist bereits fest in der Berichtsvorlage vorgesehen. "
|
||
"Die Originalbilder, Protokolle, Zertifikate oder importierten PDF-Seiten werden "
|
||
"im produktiven System automatisch an dieser Stelle eingefügt.",
|
||
styles["BodyV2"]
|
||
),
|
||
_placeholder(annex, 170),
|
||
PageBreak(),
|
||
]
|
||
|
||
# Extra certificate placeholders to reach realistic report length.
|
||
for i, e in enumerate(equipment, start=1):
|
||
story += [
|
||
Paragraph(f"Kalibrierzertifikat {i}: {e.designation}", styles["Chapter"]),
|
||
_placeholder(f"Kalibrierzertifikat {e.serial_number} - Seite 1", 205),
|
||
PageBreak(),
|
||
Paragraph(f"Kalibrierzertifikat {i}: {e.designation} - Fortsetzung", styles["Chapter"]),
|
||
_placeholder(f"Kalibrierzertifikat {e.serial_number} - Seite 2", 205),
|
||
PageBreak(),
|
||
]
|
||
|
||
story += [
|
||
Paragraph("Abschlussvermerk", styles["Chapter"]),
|
||
Paragraph(
|
||
"Diese V2-Vorschau bildet die vollständige Berichtsstruktur ab. Das geschützte Logo, "
|
||
"die Originalbilder, die konkreten Praxis- und Gerätedaten sowie die Winlog-Messwerte "
|
||
"werden zukünftig je Validierung dynamisch eingesetzt. Die fachlichen Textbausteine "
|
||
"werden nach der finalen Abstimmung unverändert versioniert.",
|
||
styles["BodyV2"]
|
||
),
|
||
Spacer(1, 15*mm),
|
||
_table([
|
||
["Ort / Datum", "Unterschrift verantwortlicher Betreiber"],
|
||
["", ""],
|
||
["Ort / Datum", "Unterschrift Prüfer / Berichtsersteller"],
|
||
["", ""],
|
||
], widths=[55*mm,110*mm], header=False),
|
||
]
|
||
|
||
doc.build(story)
|
||
return filename
|