{# Projektprotokoll — Schritte, Abweichungen und Laufkarten eines Projekts. Route: /project//protocol. Bis zum 31.08.2026 die Python-Konstante PROJECT_PROTOCOL_HTML (§2b), danach eine Flussspalte in einer Datei. Seit dem 31.08.2026 baut es — wie der Container-Ausdruck seit §2d — echte A4-Blaetter aus `_blatt.css.html` und `_umbruch.js.html`: der Fluss steht unsichtbar in `#quelle`, das Skript misst ihn und verteilt ihn auf Blaetter in `#ausgabe`. Zwei Gruende, in dieser Reihenfolge: 1. Kopf und Fuss standen in `@page`-Randboxen (`@top-left`, `@bottom-right` mit `counter(page)`). Die unterstuetzt KEIN Browser. Der Ausdruck hatte damit weder Kopfzeile noch Dok.-Nr. noch Klassifikation noch Seitenzahl — ein einzelnes Blatt aus dem Stapel erklaerte sich nicht selbst. Bei einem Pruefprotokoll ist das der ernstere Teil. Jetzt sind es echte Elemente (`.blatt-kopf` / `.blatt-fuss`) auf jedem Blatt. 2. Wo der Drucker schneidet, sah man erst auf dem Papier. Was der Umbau am Dokument geaendert hat — und warum: * Der aeussere `.step-block` um Schritt UND Unterschritte ist aufgeloest. Das Umbruch-Skript teilt je Block genau EINE Tabelle; ein Rahmen um mehrere Tabellen waere unteilbar gewesen und ein langer Schritt haette sich abgeschnitten. Jetzt ist jedes Paar aus Kopfzeile und Tabelle ein eigener Block, der sich teilen darf und auf der Fortsetzung seine Kopfzeile mit "— continued" wiederholt. Die Zusammengehoerigkeit traegt die farbige Kopfzeile, nicht mehr der Rahmen. * Die Operation Cards standen in EINER Tabelle, in der die Zyklusspalte per `rowspan` ueber die Karten eines Zyklus lief. Beim Teilen waere die Spalte auf der Fortsetzung weggefallen und alle Zellen um eine nach links gerutscht. Jetzt: ein Block je Zyklus, ohne `rowspan`. * Das Deckblatt ist Kopfteil (ein Blatt) plus Anlagen (Operation Cards, Deviations, OEM Requests), die ueberlaufen duerfen. Bei vielen Abweichungen passte es ohnehin nie auf eine Seite — vorher unsichtbar. Wer das Format aendert, verschiebt die Seitenlage: tests/protocol_umbruch_test.py misst nach, dass keine Zeile verlorengeht. -#} Test Protocol – {{ project.get('name', project.get('project_code','Project')) }} {% import 'docs/_leiste.html' as ui %} {# seiten=true: seit dem gemessenen Umbruch kennt auch dieses Dokument seine Seitenzahl — das Umbruch-Skript traegt sie in die Leiste nach. #} {% call ui.leiste(etikett='Test Protocol', seiten=true, zurueck_url=url_for('serve_engineer_report', project_uuid=project_uuid), zurueck_text='Zurück zum Report') -%} {{ project.get('name', project.get('project_code','')) }} · SN {{ project.get('engine_serial','—') }} · Stand {{ datenstand }} {%- endcall %}
{{ branding.company_name }} Logo
{{ branding.company_legal_name }}
MBET
Test Protocol
Step & Instruction Sign-off Record  · 
Project {{ project.get('name', project.get('project_code','—')) }} Engine Serial Number {{ project.get('engine_serial','—') }} Engine Type {{ project.get('engine_type','—') }} Test Cell {{ project.get('test_cell', branding.default_test_cell) }}
Document No. TP-{{ project.get('engine_serial','—') }} Revision {{ project.get('revision','A') }} Customer {{ project.get('customer','—') }}

Test Period
{{ summary.test_start }} – {{ summary.test_end }}
Duration: {{ summary.test_duration }}
Test Team
{% if summary.technicians %}{% for t in summary.technicians %}
{{ t }}
{% endfor %}{% else %}
{% endif %}
Slave Units
{{ summary.slave_count }}
installed
Deviations
{{ summary.devs_open }} open · {{ summary.devs_closed }} closed
{{ summary.devs_total }} total
OEM Requests
{{ oem_requests|length }}
recorded
{% if lk_template %} {% for cycle_num in range(1, max_cycle + 1) %}
SAP Operation Cards  ·  Cycle {{ cycle_num }}
{% for lk in lk_template %} {% endfor %}
Operation Card Service Product CS-Order
{{ lk.name }} {{ lk.serviceproduct or '—' }} {{ lk_data.get(cycle_num|string, {}).get(lk.id, '') or '—' }}
{% endfor %} {% endif %} {% if deviations %}
Deviations
{% for dev in deviations %} {% endfor %}
ID Step Reason Status Reported at HUB No.
DEV {{ dev.dev_id if dev.dev_id else 'DEV-%03d' % loop.index }} {{ dev.step_name }} {{ dev.reason or '—' }} {% if dev.status == 'Open' %}Open{% else %}Closed{% endif %} {{ dev.reported_at or '—' }}{% if dev.late_entry %}Late entry{% if dev.late_after_at %} · step signed {{ dev.late_after_at }}{% endif %}{% endif %} {% if dev.hub_incidents %}{% for inc in dev.hub_incidents %}{{ inc.nr }}{% endfor %}{% else %}{% endif %}
{% endif %} {% if oem_requests %}
OEM Requests
{% for req in oem_requests %} {% endfor %}
ID Title Description
OEM {{ req.request_id or '—' }} {{ req.title or '—' }} {{ req.description or '—' }}
{% endif %} {# Der Inhalt beginnt auf einem neuen Blatt — frueher `page-break-after` am Deckblatt, jetzt eine Marke fuer den gemessenen Umbruch. #}
{% set ns = namespace(sec=1) %}
{{ ns.sec }}   Steps & Instructions
{% set ns.sec = ns.sec + 1 %} {% set ns.last_cycle = None %} {% for step in protocol_steps %} {% if step.cycle_number and step.cycle_number != ns.last_cycle %} {% set ns.last_cycle = step.cycle_number %}
Cycle {{ step.cycle_number }}
{% endif %}
{{ step.id_str }}  {{ step.name }} {# `completed_at` traegt hier bereits das Ausfuehrungsdatum: die Rangfolge performed_at vor completed_at wird in `_collect_protocol_data.build_node` aufgeloest, samt Formatierung auf TT.MM.JJJJ. Bis zum 09.09.2026 stand hier `performed_at or completed_at` — ein Feld, das die Daten gar nicht mehr fuehren; der Ausdruck lieferte nur deshalb das Richtige, weil Undefined falsy ist. #} {% if step.status == 'Completed' %}✓ Completed {% elif step.status == 'N/A' %}N/A {% elif step.status == 'In Progress' %}⌛ In Progress {% elif step.status == 'On Hold' %}▮ On Hold {% else %}{{ step.status or '—' }}{% endif %} {% if step.completed_by %} · {{ step.completed_by }}{% if step.completed_at %} ({{ step.completed_at }}){% endif %}{% endif %}
{% if step.instructions %} {% for inst in step.instructions %} {% endfor %}
No.Instruction Selection / InputStatus Signed off byDate
{{ inst.step_num or '—' }}{{ inst.text_clean }} {{ inst.detail or '' }}{% if inst.proof %}{{ inst.proof }}{% endif %} {% if inst.checked %}i.O.{% elif inst.nr %}N/A{% else %}{% endif %} {{ inst.completed_by or '—' }}{{ inst.completed_at or '—' }}
{% endif %}
{% for child in step.children %}
{{ child.id_str }}  {{ child.name }} {% if child.status == 'Completed' %}✓ Completed {% elif child.status == 'N/A' %}N/A {% elif child.status == 'In Progress' %}⌛ In Progress {% elif child.status == 'On Hold' %}▮ On Hold {% else %}{{ child.status or '—' }}{% endif %} {% if child.completed_by %} · {{ child.completed_by }}{% if child.completed_at %} ({{ child.completed_at }}){% endif %}{% endif %}
{% if child.instructions %} {% for inst in child.instructions %} {% endfor %}
No.Instruction Selection / InputStatus Signed off byDate
{{ inst.step_num or '—' }}{{ inst.text_clean }} {{ inst.detail or '' }}{% if inst.proof %}{{ inst.proof }}{% endif %} {% if inst.checked %}i.O.{% elif inst.nr %}N/A{% else %}{% endif %} {{ inst.completed_by or '—' }}{{ inst.completed_at or '—' }}
{% endif %}
{% endfor %} {% endfor %} {% if all_slave_units %}
{{ ns.sec }}   Slave Units ({{ all_slave_units|length }})
{% set ns.sec = ns.sec + 1 %} {% for su in all_slave_units %} {% endfor %}
Component Part Number Serial Number
{{ su.label }} {{ su.pn or '—' }} {{ su.sn or '—' }}
{% endif %}