from __future__ import annotations from copy import deepcopy from dataclasses import dataclass from datetime import date from pathlib import Path import shutil from typing import Any from dateutil.relativedelta import relativedelta from sqlalchemy import delete, select from sqlalchemy.orm import Session from app.models.contact import Contact from app.models.customer import Customer, CustomerType from app.models.device import Device from app.models.equipment import Equipment, EquipmentKind, EquipmentStatus from app.models.location import Location from app.models.validation import Validation, ValidationStatus from app.modules.orion.service import OrionReportService from app.modules.orion.template_service import ReportTemplateService from app.services.validation_workflow import ValidationWorkflowService DEMO_TAG = "DEMO_DATA" DEMO_REPORT_PREFIX = "DEMO-VAL" DEMO_DEVICE_PREFIX = "DEMO-DEV" DEMO_EQUIPMENT_PREFIX = "DEMO-EQ" DEMO_UPLOAD_ROOT = Path("/app/uploads/demo-data") DEMO_REPORT_ROOT = Path("/app/reports") @dataclass(frozen=True) class DemoCustomerTemplate: name: str specialty: str street: str postal_code: str city: str phone: str email: str hygiene_officer: str quality_manager: str operator: str locations: list[dict[str, Any]] contacts: list[dict[str, Any]] device: dict[str, Any] CUSTOMER_TEMPLATES: list[DemoCustomerTemplate] = [ DemoCustomerTemplate( name="Zahnzentrum Alster", specialty="Zahnarzt", street="Alsterufer 18", postal_code="20354", city="Hamburg", phone="040 5550010", email="kontakt@zahnzentrum-alster.schubamed.de", hygiene_officer="Dr. Jana Peters", quality_manager="M. Kruse", operator="Dr. Jana Peters", locations=[ {"name": "Hauptpraxis", "street": "Alsterufer 18", "postal_code": "20354", "city": "Hamburg", "room": "Steri 1"}, {"name": "OP-Bereich", "street": "Alsterufer 18", "postal_code": "20354", "city": "Hamburg", "room": "OP 2"}, ], contacts=[ {"full_name": "Dr. Jana Peters", "function": "Betreiberin", "email": "jana.peters@schubamed.de", "phone": "040 5550011"}, {"full_name": "M. Kruse", "function": "QM-Beauftragte", "email": "qm.zahnzentrum.alster@schubamed.de", "phone": "040 5550012"}, ], device={ "manufacturer": "Euronda SpA", "model": "E10.7", "serial_number": "SN-DEMO-001", "year_built": 2023, "commissioned_on": date(2023, 6, 14), "chamber_volume_liters": 24, "steam_generation": "Eigendampferzeugung", "water_treatment": "Vollentsalzung", "documentation": "CF-Karte und PDF-Protokoll", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Urologie Zentrum Elbe", specialty="Urologie", street="Mönckebergstr. 11", postal_code="20095", city="Hamburg", phone="040 5550020", email="kontakt@urologie-elbe.schubamed.de", hygiene_officer="Dr. Felix Brandt", quality_manager="S. Lange", operator="Dr. Felix Brandt", locations=[ {"name": "Praxis Mitte", "street": "Mönckebergstr. 11", "postal_code": "20095", "city": "Hamburg", "room": "Steri A"}, ], contacts=[ {"full_name": "Dr. Felix Brandt", "function": "Betreiber", "email": "felix.brandt@schubamed.de", "phone": "040 5550021"}, ], device={ "manufacturer": "Euronda SpA", "model": "E9", "serial_number": "SN-DEMO-002", "year_built": 2022, "commissioned_on": date(2022, 11, 2), "chamber_volume_liters": 18, "steam_generation": "Generator integriert", "water_treatment": "Wasserkonditionierung", "documentation": "Digitale Dokumentation", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Orthopädie am Park", specialty="Orthopädie", street="Königsallee 44", postal_code="40212", city="Düsseldorf", phone="0211 5550030", email="kontakt@orthopaedie-park.schubamed.de", hygiene_officer="Dr. Lea Sommer", quality_manager="A. Hoffmann", operator="Dr. Lea Sommer", locations=[ {"name": "Ambulanz", "street": "Königsallee 44", "postal_code": "40212", "city": "Düsseldorf", "room": "Steri 1"}, {"name": "Behandlungszentrum", "street": "Kaiserswerther Str. 99", "postal_code": "40474", "city": "Düsseldorf", "room": "Steri 2"}, ], contacts=[ {"full_name": "Dr. Lea Sommer", "function": "Betreiberin", "email": "lea.sommer@schubamed.de", "phone": "0211 5550031"}, {"full_name": "A. Hoffmann", "function": "Hygienebeauftragte", "email": "hygiene.orthopaedie@schubamed.de", "phone": "0211 5550032"}, ], device={ "manufacturer": "MELAG", "model": "Vacuklav 41 B+", "serial_number": "SN-DEMO-003", "year_built": 2021, "commissioned_on": date(2021, 9, 8), "chamber_volume_liters": 22, "steam_generation": "Dampferzeuger intern", "water_treatment": "VE-Wasser", "documentation": "CF-Karte, USB-Export", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Dermatologie Rhein", specialty="Dermatologie", street="Breite Str. 8", postal_code="50667", city="Köln", phone="0221 5550040", email="kontakt@dermatologie-rhein.schubamed.de", hygiene_officer="Dr. Sophie Keller", quality_manager="D. Neumann", operator="Dr. Sophie Keller", locations=[ {"name": "Hautpraxis", "street": "Breite Str. 8", "postal_code": "50667", "city": "Köln", "room": "Steri"}, ], contacts=[ {"full_name": "Dr. Sophie Keller", "function": "Betreiberin", "email": "sophie.keller@schubamed.de", "phone": "0221 5550041"}, {"full_name": "D. Neumann", "function": "QM", "email": "qm.dermatologie.rhein@schubamed.de", "phone": "0221 5550042"}, ], device={ "manufacturer": "MELAG", "model": "Vacuklav 44 B+", "serial_number": "SN-DEMO-004", "year_built": 2020, "commissioned_on": date(2020, 4, 16), "chamber_volume_liters": 24, "steam_generation": "Eigendampferzeugung", "water_treatment": "Osmose", "documentation": "Digitale Dokumentation und Ausdruck", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="HNO Zentrum Forum", specialty="HNO", street="Theatinerstr. 19", postal_code="80333", city="München", phone="089 5550050", email="kontakt@hno-forum.schubamed.de", hygiene_officer="Dr. Tom Berger", quality_manager="R. Wagner", operator="Dr. Tom Berger", locations=[ {"name": "Forum Praxis", "street": "Theatinerstr. 19", "postal_code": "80333", "city": "München", "room": "Steri 1"}, ], contacts=[ {"full_name": "Dr. Tom Berger", "function": "Betreiber", "email": "tom.berger@schubamed.de", "phone": "089 5550051"}, ], device={ "manufacturer": "MELAG", "model": "PrimeLine", "serial_number": "SN-DEMO-005", "year_built": 2024, "commissioned_on": date(2024, 3, 1), "chamber_volume_liters": 29, "steam_generation": "Generator integriert", "water_treatment": "VE-Wasser", "documentation": "USB-Export", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Gynäkologie Marienhof", specialty="Gynäkologie", street="Marienplatz 3", postal_code="86150", city="Augsburg", phone="0821 5550060", email="kontakt@gyn-marienhof.schubamed.de", hygiene_officer="Dr. Miriam Wolf", quality_manager="K. Braun", operator="Dr. Miriam Wolf", locations=[ {"name": "Frauenpraxis", "street": "Marienplatz 3", "postal_code": "86150", "city": "Augsburg", "room": "Steri"}, {"name": "Ambulanz Süd", "street": "Bürgermeister-Fischer-Str. 12", "postal_code": "86150", "city": "Augsburg", "room": "Steri 2"}, ], contacts=[ {"full_name": "Dr. Miriam Wolf", "function": "Betreiberin", "email": "miriam.wolf@schubamed.de", "phone": "0821 5550061"}, ], device={ "manufacturer": "MELAG", "model": "ProLine", "serial_number": "SN-DEMO-006", "year_built": 2019, "commissioned_on": date(2019, 8, 23), "chamber_volume_liters": 18, "steam_generation": "Kompaktgenerator", "water_treatment": "VE-Wasser", "documentation": "CF-Card", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Chirurgie Ost", specialty="Chirurgie", street="Lindenstr. 7", postal_code="04109", city="Leipzig", phone="0341 5550070", email="kontakt@chirurgie-ost.schubamed.de", hygiene_officer="Dr. Paul Richter", quality_manager="F. Scholz", operator="Dr. Paul Richter", locations=[ {"name": "OP-Zentrum", "street": "Lindenstr. 7", "postal_code": "04109", "city": "Leipzig", "room": "Steri OP"}, ], contacts=[ {"full_name": "Dr. Paul Richter", "function": "Betreiber", "email": "paul.richter@schubamed.de", "phone": "0341 5550071"}, {"full_name": "F. Scholz", "function": "Hygiene", "email": "hygiene.chirurgie.ost@schubamed.de", "phone": "0341 5550072"}, ], device={ "manufacturer": "W&H", "model": "Lara XL", "serial_number": "SN-DEMO-007", "year_built": 2023, "commissioned_on": date(2023, 2, 15), "chamber_volume_liters": 17, "steam_generation": "Dampferzeuger intern", "water_treatment": "Mikrofiltration", "documentation": "USB und Ausdruck", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Allgemeinmedizin West", specialty="Allgemeinmedizin", street="Bergstr. 25", postal_code="70173", city="Stuttgart", phone="0711 5550080", email="kontakt@allgemeinmedizin-west.schubamed.de", hygiene_officer="Dr. Nina Beck", quality_manager="J. Hartmann", operator="Dr. Nina Beck", locations=[ {"name": "Hausarztpraxis", "street": "Bergstr. 25", "postal_code": "70173", "city": "Stuttgart", "room": "Steri"}, ], contacts=[ {"full_name": "Dr. Nina Beck", "function": "Betreiberin", "email": "nina.beck@schubamed.de", "phone": "0711 5550081"}, ], device={ "manufacturer": "W&H", "model": "Lisa", "serial_number": "SN-DEMO-008", "year_built": 2022, "commissioned_on": date(2022, 7, 12), "chamber_volume_liters": 22, "steam_generation": "Eigendampferzeugung", "water_treatment": "VE-Wasser", "documentation": "Digital und Papier", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="Augenärzte am Ring", specialty="Augenarzt", street="Ringstr. 52", postal_code="50672", city="Köln", phone="0221 5550090", email="kontakt@augenaerzte-ring.schubamed.de", hygiene_officer="Dr. Karin Otto", quality_manager="M. Franke", operator="Dr. Karin Otto", locations=[ {"name": "Augenzentrum", "street": "Ringstr. 52", "postal_code": "50672", "city": "Köln", "room": "Steri 1"}, {"name": "Laserzentrum", "street": "Ringstr. 54", "postal_code": "50672", "city": "Köln", "room": "Steri 2"}, ], contacts=[ {"full_name": "Dr. Karin Otto", "function": "Betreiberin", "email": "karin.otto@schubamed.de", "phone": "0221 5550091"}, {"full_name": "M. Franke", "function": "QM", "email": "qm.augen@schubamed.de", "phone": "0221 5550092"}, ], device={ "manufacturer": "Mocom", "model": "B Classic", "serial_number": "SN-DEMO-009", "year_built": 2021, "commissioned_on": date(2021, 5, 20), "chamber_volume_liters": 12, "steam_generation": "Kompaktgenerator", "water_treatment": "Vollentsalzung", "documentation": "USB-Export", "supplier": "schubamed Medizintechnik", }, ), DemoCustomerTemplate( name="MVZ Medikon", specialty="MVZ", street="Forum 9", postal_code="90402", city="Nürnberg", phone="0911 5550100", email="kontakt@mvz-medikon.schubamed.de", hygiene_officer="Dr. Lara Stein", quality_manager="C. Meier", operator="Dr. Lara Stein", locations=[ {"name": "Hauptstandort", "street": "Forum 9", "postal_code": "90402", "city": "Nürnberg", "room": "Steri A"}, {"name": "Nebenstandort", "street": "Forum 11", "postal_code": "90402", "city": "Nürnberg", "room": "Steri B"}, ], contacts=[ {"full_name": "Dr. Lara Stein", "function": "Betreiberin", "email": "lara.stein@schubamed.de", "phone": "0911 5550101"}, {"full_name": "C. Meier", "function": "Hygiene", "email": "hygiene.mvz@schubamed.de", "phone": "0911 5550102"}, ], device={ "manufacturer": "Kronos", "model": "B23", "serial_number": "SN-DEMO-010", "year_built": 2024, "commissioned_on": date(2024, 1, 11), "chamber_volume_liters": 23, "steam_generation": "Eigendampferzeugung", "water_treatment": "VE-Wasser", "documentation": "Hybrid", "supplier": "schubamed Medizintechnik", }, ), ] EQUIPMENT_TEMPLATES: list[dict[str, Any]] = [ { "kind": EquipmentKind.temperature_logger, "manufacturer": "EBRO", "model": "EBI 11", "serial_number": "DEMO-EBI11-001", "calibrated_on": date(2025, 1, 17), "calibration_due_on": date(2026, 1, 17), "status": EquipmentStatus.green, "notes": "Demo-Temperaturlogger", }, { "kind": EquipmentKind.temperature_logger, "manufacturer": "EBRO", "model": "EBI 11", "serial_number": "DEMO-EBI11-002", "calibrated_on": date(2025, 2, 2), "calibration_due_on": date(2026, 2, 2), "status": EquipmentStatus.green, "notes": "Demo-Temperaturlogger", }, { "kind": EquipmentKind.pressure_logger, "manufacturer": "EBRO", "model": "Drucklogger", "serial_number": "DEMO-PR-001", "calibrated_on": date(2025, 1, 21), "calibration_due_on": date(2026, 1, 21), "status": EquipmentStatus.green, "notes": "Demo-Drucklogger", }, { "kind": EquipmentKind.sensor, "manufacturer": "Mettler Toledo", "model": "Waage", "serial_number": "DEMO-SCALE-001", "calibrated_on": date(2024, 12, 12), "calibration_due_on": date(2025, 12, 12), "status": EquipmentStatus.yellow, "notes": "Demo-Waage", }, { "kind": EquipmentKind.sensor, "manufacturer": "Mettler Toledo", "model": "Leitwertmessgerät", "serial_number": "DEMO-COND-001", "calibrated_on": date(2025, 3, 4), "calibration_due_on": date(2026, 3, 4), "status": EquipmentStatus.green, "notes": "Demo-Leitwertmessgerät", }, { "kind": EquipmentKind.sensor, "manufacturer": "Trotec", "model": "Raumklimamessgerät", "serial_number": "DEMO-CLIMATE-001", "calibrated_on": date(2024, 11, 1), "calibration_due_on": date(2025, 11, 1), "status": EquipmentStatus.red, "notes": "Demo-Raumklimamessgerät", }, ] class DemoDataService: def __init__(self, session: Session, upload_root: Path | None = None, report_root: Path | None = None) -> None: self.session = session self.upload_root = upload_root or DEMO_UPLOAD_ROOT.parent self.report_root = report_root or DEMO_REPORT_ROOT self.demo_upload_root = self.upload_root / "demo-data" def run( self, *, customers: int = 10, devices: int = 10, validations: int = 20, reports: bool = False, images: bool = False, reset: bool = False, ) -> dict[str, int]: if reset: self.reset() created_customers = self.ensure_customers(max(customers, 10)) created_equipment = self.ensure_equipment() created_devices = self.ensure_devices(max(devices, 10), created_customers) created_validations = self.ensure_validations(max(validations, 20), created_customers, created_devices, images=images) self.session.commit() if reports: self._generate_reports(created_validations) return { "customers": len(created_customers), "devices": len(created_devices), "validations": len(created_validations), "equipment": len(created_equipment), } def reset(self) -> None: demo_validation_ids = self.session.scalars( select(Validation.id).where( Validation.report_number.like(f"{DEMO_REPORT_PREFIX}%") | Validation.notes.like(f"%{DEMO_TAG}%") ) ).all() if demo_validation_ids: self.session.execute(delete(Validation).where(Validation.id.in_(demo_validation_ids))) self.session.execute( delete(Equipment).where(Equipment.serial_number.like("DEMO-%") | Equipment.notes.like(f"%{DEMO_TAG}%")) ) self.session.execute( delete(Device).where(Device.serial_number.like("SN-DEMO-%") | Device.notes.like(f"%{DEMO_TAG}%")) ) self.session.execute( delete(Contact).where( Contact.email.like("demo-%@schubamed.de") | Contact.notes.like(f"%{DEMO_TAG}%") ) ) self.session.execute( delete(Customer).where( Customer.email.like("demo-%@schubamed.de") | Customer.notes.like(f"%{DEMO_TAG}%") ) ) self.session.commit() if self.demo_upload_root.exists(): shutil.rmtree(self.demo_upload_root) self.report_root.mkdir(parents=True, exist_ok=True) for pdf in self.report_root.glob(f"{DEMO_REPORT_PREFIX}*.pdf"): pdf.unlink() def ensure_customers(self, count: int) -> list[Customer]: ensured: list[Customer] = [] for index in range(count): template = CUSTOMER_TEMPLATES[index % len(CUSTOMER_TEMPLATES)] occurrence = index // len(CUSTOMER_TEMPLATES) customer = self._ensure_customer(template, occurrence, index) ensured.append(customer) return ensured def ensure_equipment(self) -> list[Equipment]: items: list[Equipment] = [] for template in EQUIPMENT_TEMPLATES: equipment = self.session.scalar( select(Equipment).where(Equipment.serial_number == template["serial_number"]) ) if equipment is None: equipment = Equipment(**template) equipment.notes = f"{template['notes']} {DEMO_TAG}" self.session.add(equipment) self.session.flush() items.append(equipment) return items def ensure_devices(self, count: int, customers: list[Customer]) -> list[Device]: devices: list[Device] = [] all_locations = {customer.id: list(self.session.scalars(select(Location).where(Location.customer_id == customer.id))) for customer in customers} for index in range(count): customer = customers[index % len(customers)] template = CUSTOMER_TEMPLATES[index % len(CUSTOMER_TEMPLATES)].device occurrence = index // len(CUSTOMER_TEMPLATES) serial_number = template["serial_number"] if occurrence == 0 else f"{template['serial_number']}-{occurrence + 1}" device = self.session.scalar(select(Device).where(Device.serial_number == serial_number)) if device is None: location_candidates = all_locations.get(customer.id, []) location_id = location_candidates[0].id if location_candidates else None device_payload = deepcopy(template) device_payload["serial_number"] = serial_number device_payload["customer_id"] = customer.id device_payload["location_id"] = location_id device = Device(**device_payload) device.notes = f"{DEMO_TAG} {customer.name}" self.session.add(device) self.session.flush() devices.append(device) return devices def ensure_validations( self, count: int, customers: list[Customer], devices: list[Device], *, images: bool = False, ) -> list[Validation]: validations: list[Validation] = [] report_number_index = 1 for index in range(count): template = CUSTOMER_TEMPLATES[index % len(CUSTOMER_TEMPLATES)] customer = customers[index % len(customers)] device = devices[index % len(devices)] locations = list(self.session.scalars(select(Location).where(Location.customer_id == customer.id))) contacts = list(self.session.scalars(select(Contact).where(Contact.customer_id == customer.id))) location = locations[index % len(locations)] if locations else None contact = contacts[index % len(contacts)] if contacts else None report_number = f"{DEMO_REPORT_PREFIX}-{report_number_index:03d}" report_number_index += 1 validation = self.session.scalar(select(Validation).where(Validation.report_number == report_number)) if validation is None: validation = Validation( report_number=report_number, customer_id=customer.id, location_id=location.id if location else None, contact_id=contact.id if contact else None, device_id=device.id, validation_type="Erstvalidierung" if index % 2 == 0 else "Revalidierung", performed_on=date.today() - relativedelta(months=6 + index), examiner_name="Dr. Lara Stein", operator_name=template.operator, participants=f"{customer.name} - Team", status=[ ValidationStatus.draft.value, ValidationStatus.approved.value, ValidationStatus.completed.value, ][index % 3], result="bestanden" if index % 3 else "bestanden mit Auflagen", revalidation_interval_months=24, equipment_ids=[equipment.id for equipment in self.session.scalars(select(Equipment).limit(3))], environment_conditions={ "room_temperature": 22 + (index % 3), "humidity": 48 + (index % 5), "test_time": f"{8 + (index % 3)}:30", "checks": [ {"text": "Raumbedingungen stabil", "value": "yes", "comment": ""}, {"text": "Aufstellort frei zugänglich", "value": "yes", "comment": ""}, ], }, documentation_checklist=self._checklist("documentation_control", index), performance_checklist=self._checklist("sterilizer_description", index), programs=[ {"name": "Vakuumtest", "selected": True, "custom": False}, {"name": "Bowie-Dick / Leerkammerprofil", "selected": index % 2 == 0, "custom": False}, {"name": "134 C hohl verpackt", "selected": True, "custom": False}, ], loading_patterns=self._loading_patterns(index), measurement_data=self._measurement_data(index), drying={"start_weight": 12.5, "end_weight": 12.1, "difference": 0.4, "assessment": "in Ordnung", "comment": ""}, recommendations=[ {"number": 1, "text": "Routinekontrolle dokumentieren", "deadline": "6 Monate", "status": "offen"} ] if index % 4 == 0 else [], attachments=self._attachments_for_validation(index, images=images), notes=DEMO_TAG, ) self.session.add(validation) self.session.flush() ValidationWorkflowService(self.session).apply_revalidation_date(validation) validations.append(validation) return validations def _ensure_customer(self, template: DemoCustomerTemplate, occurrence: int, index: int) -> Customer: email = template.email if occurrence == 0 else f"demo-{index + 1:02d}@schubamed.de" customer = self.session.scalar(select(Customer).where(Customer.email == email)) if customer is None: customer = Customer( customer_type=CustomerType.practice, name=template.name if occurrence == 0 else f"{template.name} {occurrence + 1}", street=template.street, postal_code=template.postal_code, city=template.city, phone=template.phone, email=email, hygiene_officer=template.hygiene_officer, quality_manager=template.quality_manager, notes=f"{DEMO_TAG} {template.specialty}", ) self.session.add(customer) self.session.flush() for loc_index, location_payload in enumerate(template.locations, start=1): location = self.session.scalar( select(Location).where( Location.customer_id == customer.id, Location.name == location_payload["name"], ) ) if location is None: self.session.add(Location(customer_id=customer.id, **location_payload)) for contact_payload in template.contacts: contact = self.session.scalar( select(Contact).where( Contact.customer_id == customer.id, Contact.full_name == contact_payload["full_name"], ) ) if contact is None: self.session.add( Contact( customer_id=customer.id, full_name=contact_payload["full_name"], function=contact_payload["function"], email=contact_payload["email"], phone=contact_payload["phone"], notes=DEMO_TAG, ) ) self.session.flush() return customer def _checklist(self, key: str, index: int) -> list[dict[str, Any]]: bundle = ReportTemplateService(self.session).ensure_default_template() template = next((item for item in bundle.checklists if item.checklist_key == key), None) if template is None: return [] values = ["yes", "yes", "na"] if index % 2 == 0 else ["yes", "na", "yes"] return [ { "number": item["number"], "text": item["text"], "value": values[(item["number"] - 1) % len(values)], "comment": "", } for item in template.items ] def _loading_patterns(self, index: int) -> list[dict[str, Any]]: return [ { "run": 1, "pattern": "Standard", "description": f"Demo-Beladung {index + 1}", "images": [], }, { "run": 2, "pattern": "Standard", "description": "", "images": [], }, { "run": 3, "pattern": "Standard", "description": "", "images": [], }, ] def _measurement_data(self, index: int) -> list[dict[str, Any]]: base_temperature = 132 + (index % 3) return [ { "test_run": "Vakuumtest", "start_time": "08:00", "end_time": "08:08", "duration": "00:08", "leak_rate": "0.4", "result": "bestanden", }, { "test_run": "Testlauf 1", "start_time": "09:00", "end_time": "09:31", "duration": "00:31", "minimum_temperature": base_temperature, "maximum_temperature": base_temperature + 2, "temperature_band": "2.0", "holding_time": "3:00", "pressure": "2.1", "result": "bestanden", }, ] def _attachments_for_validation(self, index: int, *, images: bool) -> list[dict[str, Any]]: if not images or index >= 5: return [] image_dir = self.demo_upload_root / "images" image_dir.mkdir(parents=True, exist_ok=True) image_path = image_dir / f"demo-figure-{index + 1}.svg" if not image_path.exists(): image_path.write_text( "\n".join( [ '', '', '', 'SCHUBAMED Demo-Bild', f'Validierung {index + 1}', "", ] ), encoding="utf-8", ) return [ { "category": "Beladung", "filename": image_path.name, "content_type": "image/svg+xml", "description": f"Demo-Abbildung {index + 1}", "order": 1, "storage_path": str(image_path), } ] def _generate_reports(self, validations: list[Validation]) -> None: self.report_root.mkdir(parents=True, exist_ok=True) ReportTemplateService(self.session).ensure_default_template() for validation in validations: OrionReportService(self.session, self.report_root).render_pdf(validation.id)