fix(orion): correct report numbering toc and first page

This commit is contained in:
Schubert Ferenc 2026-07-11 12:48:01 +02:00
parent 503b343070
commit 47f54d3461
7 changed files with 283 additions and 137 deletions

View file

@ -27,6 +27,22 @@ def render_template_text(content: str, context: ReportContext) -> str:
return rendered
class NumberedComponent(ReportComponent):
anchor = ""
title = ""
def __init__(self, anchor: str | None = None, title: str | None = None, number: str | None = None) -> None:
if anchor is not None:
self.anchor = anchor
if title is not None:
self.title = title
self.number = number
@property
def bookmark_label(self) -> str:
return f"{self.number} {self.title}" if self.number else self.title
class CoverComponent(ReportComponent):
anchor = "cover"
title = "Deckblatt"
@ -63,7 +79,7 @@ class CoverComponent(ReportComponent):
'<div class="company-address">SCHUBAMED<br>Validation Suite<br>Medizintechnik und Validierung</div>'
f'<img class="cover-logo" src="{logo_uri}" alt="SCHUBAMED Validation Suite">'
"</div>"
"<h1>PRUEFBERICHT ZUR VALIDIERUNG</h1>"
"<h1>PRÜFBERICHT 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}"
@ -76,17 +92,27 @@ class TocComponent(ReportComponent):
anchor = "toc"
title = "Inhaltsverzeichnis"
def __init__(self, components: list[ReportComponent]) -> None:
self.components = components
def __init__(self, sections: list[dict]) -> None:
self.sections = sections
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"}
self._toc_item(item)
for item in self.sections
if item.get("toc")
)
return section(self.anchor, self.title, f'<ol class="toc-list">{links}</ol>')
def _toc_item(self, item: dict) -> str:
number = item.get("number")
title = item.get("title")
label = f"{number} {title}" if number else str(title)
depth = str(number).count(".") + 1 if number else 2
return (
f'<li class="toc-level-{depth}"><a href="#{text(item.get("key"))}">'
f'<span class="toc-label">{text(label)}</span></a></li>'
)
class SummaryComponent(ReportComponent):
anchor = "summary"
@ -121,17 +147,29 @@ class SummaryComponent(ReportComponent):
class StaticTextComponent(ReportComponent):
def __init__(self, anchor: str, title: str, body: str = "nicht erfasst", block_key: str | None = None) -> None:
def __init__(
self,
anchor: str,
title: str,
body: str = "nicht erfasst",
block_key: str | None = None,
number: str | None = None,
) -> None:
self.anchor = anchor
self.title = title
self.body = body
self.block_key = block_key
self.number = number
@property
def bookmark_label(self) -> str:
return f"{self.number} {self.title}" if self.number else self.title
def render(self, context: ReportContext) -> str:
if self.block_key and self.block_key in context.text_blocks:
content = render_template_text(context.text_blocks[self.block_key].content, context)
return section(self.anchor, self.title, f"<p>{paragraph(content)}</p>")
return section(self.anchor, self.title, f"<p>{paragraph(self.body)}</p>")
return section(self.anchor, self.title, f"<p>{paragraph(content)}</p>", self.bookmark_label)
return section(self.anchor, self.title, f"<p>{paragraph(self.body)}</p>", self.bookmark_label)
class CustomerComponent(ReportComponent):
@ -182,14 +220,14 @@ class CustomerComponent(ReportComponent):
return section(self.anchor, self.title, body)
class DeviceComponent(ReportComponent):
class DeviceComponent(NumberedComponent):
anchor = "device"
title = "Geraet"
def render(self, context: ReportContext) -> str:
device = context.device
if device is None:
return section(self.anchor, self.title, "")
return section(self.anchor, self.title, "<p>nicht erfasst</p>", self.bookmark_label)
body = definition_list(
[
("Hersteller", device.manufacturer),
@ -212,10 +250,10 @@ class DeviceComponent(ReportComponent):
("Lieferant", device.supplier),
]
)
return section(self.anchor, self.title, body)
return section(self.anchor, self.title, body, self.bookmark_label)
class EquipmentComponent(ReportComponent):
class EquipmentComponent(NumberedComponent):
anchor = "equipment"
title = "Pruefmittel"
@ -247,10 +285,11 @@ class EquipmentComponent(ReportComponent):
],
rows,
),
self.bookmark_label,
)
class EnvironmentComponent(ReportComponent):
class EnvironmentComponent(NumberedComponent):
anchor = "environment"
title = "Umgebungsbedingungen"
@ -269,10 +308,10 @@ class EnvironmentComponent(ReportComponent):
]
if rows:
body += table(["Pruefpunkt", "Bewertung", "Kommentar"], rows)
return section(self.anchor, self.title, body)
return section(self.anchor, self.title, body, self.bookmark_label)
class ChecklistComponent(ReportComponent):
class ChecklistComponent(NumberedComponent):
anchor = "checklists"
title = "Dokumentations- und Leistungschecklisten"
@ -286,7 +325,7 @@ class ChecklistComponent(ReportComponent):
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)
return section(self.anchor, self.title, body, self.bookmark_label)
def _render_items(self, items: list[dict]) -> str:
rows = [
@ -296,7 +335,7 @@ class ChecklistComponent(ReportComponent):
return table(["Nr.", "Pruefpunkt", "Bewertung", "Kommentar"], rows)
class ProgramComponent(ReportComponent):
class ProgramComponent(NumberedComponent):
anchor = "programs"
title = "Programme"
@ -306,10 +345,10 @@ class ProgramComponent(ReportComponent):
[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))
return section(self.anchor, self.title, table(["Nr.", "Programm", "Typ"], rows), self.bookmark_label)
class LoadingComponent(ReportComponent):
class LoadingComponent(NumberedComponent):
anchor = "loading"
title = "Beladungsmuster"
@ -327,10 +366,11 @@ class LoadingComponent(ReportComponent):
self.anchor,
self.title,
table(["Testlauf", "Beladungsmuster", "Beschreibung", "Bilder"], rows),
self.bookmark_label,
)
class MeasurementComponent(ReportComponent):
class MeasurementComponent(NumberedComponent):
anchor = "measurements"
title = "Messdaten"
@ -351,6 +391,7 @@ class MeasurementComponent(ReportComponent):
self.anchor,
self.title,
table(["Testlauf", "Messwert", "Wert", "Einheit", "Quelle", "Sicherheit"], rows, "compact"),
self.bookmark_label,
)
rows = [
[
@ -394,10 +435,10 @@ class MeasurementComponent(ReportComponent):
if winlog_rows:
body += "<h3>Winlog-Dateien</h3>" + table(["Bereich", "Datei", "Typ"], winlog_rows)
body += "<p>Messdaten noch nicht bestätigt.</p>"
return section(self.anchor, self.title, body)
return section(self.anchor, self.title, body, self.bookmark_label)
class DryingComponent(ReportComponent):
class DryingComponent(NumberedComponent):
anchor = "drying"
title = "Trocknung"
@ -412,10 +453,10 @@ class DryingComponent(ReportComponent):
("Bemerkung", data.get("comment")),
]
)
return section(self.anchor, self.title, body)
return section(self.anchor, self.title, body, self.bookmark_label)
class RecommendationComponent(ReportComponent):
class RecommendationComponent(NumberedComponent):
anchor = "recommendations"
title = "Empfehlungen und Auflagen"
@ -424,10 +465,10 @@ class RecommendationComponent(ReportComponent):
[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))
return section(self.anchor, self.title, table(["Nr.", "Text", "Frist", "Status"], rows), self.bookmark_label)
class AttachmentComponent(ReportComponent):
class AttachmentComponent(NumberedComponent):
anchor = "attachments"
title = "Bilder und Anlagen"
@ -456,7 +497,7 @@ class AttachmentComponent(ReportComponent):
body = table(["Reihenfolge", "Kategorie", "Datei", "Beschreibung"], rows)
if figures:
body += '<div class="figure-grid">' + "".join(figures) + "</div>"
return section(self.anchor, self.title, body)
return section(self.anchor, self.title, body, self.bookmark_label)
def _image_source(self, item: dict) -> str | None:
content_type = str(item.get("content_type") or "")