diff --git a/lxc-frontend/app/core/mac_client.py b/lxc-frontend/app/core/mac_client.py
index 079f407..a8ea06b 100644
--- a/lxc-frontend/app/core/mac_client.py
+++ b/lxc-frontend/app/core/mac_client.py
@@ -146,16 +146,28 @@ class MacClient:
job_id,
)
- async def export_docx(self, data: dict, profile: str | None = None, job_id: int | None = None) -> bytes:
+ async def export_docx(
+ self,
+ data: dict,
+ profile: str | None = None,
+ job_id: int | None = None,
+ full_transcript: list[dict] | None = None,
+ ) -> bytes:
return await self._post_bytes_retry(
f"{self.base_url}/api/export/docx",
- {"data": data, "profile": profile},
+ {"data": data, "profile": profile, "full_transcript": full_transcript or []},
job_id,
)
- async def export_pdf(self, data: dict, profile: str | None = None, job_id: int | None = None) -> bytes:
+ async def export_pdf(
+ self,
+ data: dict,
+ profile: str | None = None,
+ job_id: int | None = None,
+ full_transcript: list[dict] | None = None,
+ ) -> bytes:
return await self._post_bytes_retry(
f"{self.base_url}/api/export/pdf",
- {"data": data, "profile": profile},
+ {"data": data, "profile": profile, "full_transcript": full_transcript or []},
job_id,
)
diff --git a/lxc-frontend/app/services/pipeline.py b/lxc-frontend/app/services/pipeline.py
index 9f3d029..1f880ac 100644
--- a/lxc-frontend/app/services/pipeline.py
+++ b/lxc-frontend/app/services/pipeline.py
@@ -166,6 +166,11 @@ async def run_pipeline(job_id: int) -> None:
protocol_path.write_text(json.dumps(protocol, ensure_ascii=False, indent=2), encoding="utf-8")
_update(job_id, protocol_path=str(protocol_path), progress=85)
+ # Volltranskript-Segmente: optionaler Anhang ans DOCX/PDF, falls das
+ # Profil `docx.include_full_transcript` setzt. Renderer ignoriert die
+ # Liste sonst — wir reichen sie trotzdem immer durch.
+ full_segments = tx.get("segments", []) or []
+
# 4) DOCX
if existing_dx_path:
log.info("[job %s] reuse docx %s", job_id, existing_dx_path.name)
@@ -174,7 +179,8 @@ async def run_pipeline(job_id: int) -> None:
log.info("[job %s] export docx", job_id)
docx_bytes = await _with_heartbeat(
job_id,
- client.export_docx(protocol, profile=profile, job_id=job_id),
+ client.export_docx(protocol, profile=profile, job_id=job_id,
+ full_transcript=full_segments),
)
docx_path = job_dir / "protocol.docx"
docx_path.write_bytes(docx_bytes)
@@ -189,7 +195,8 @@ async def run_pipeline(job_id: int) -> None:
log.info("[job %s] export pdf", job_id)
pdf_bytes = await _with_heartbeat(
job_id,
- client.export_pdf(protocol, profile=profile, job_id=job_id),
+ client.export_pdf(protocol, profile=profile, job_id=job_id,
+ full_transcript=full_segments),
)
pdf_path = job_dir / "protocol.pdf"
pdf_path.write_bytes(pdf_bytes)
diff --git a/mac-worker/app/core/docx_export.py b/mac-worker/app/core/docx_export.py
index 587a058..292b8e6 100644
--- a/mac-worker/app/core/docx_export.py
+++ b/mac-worker/app/core/docx_export.py
@@ -19,7 +19,11 @@ from docx.shared import Pt
from . import profiles
-def build_docx(data: dict[str, Any], profile_name: str | None = None) -> bytes:
+def build_docx(
+ data: dict[str, Any],
+ profile_name: str | None = None,
+ full_transcript: list[dict] | None = None,
+) -> bytes:
profile = profiles.get(profile_name)
cfg = profile.docx or {}
@@ -37,6 +41,9 @@ def build_docx(data: dict[str, Any], profile_name: str | None = None) -> bytes:
for section in cfg.get("sections", []) or []:
_render_section(doc, data, section)
+ if cfg.get("include_full_transcript") and full_transcript:
+ _render_full_transcript(doc, full_transcript)
+
if data.get("_parse_error") and data.get("_raw"):
doc.add_heading("Roh-Antwort des Modells", level=1)
doc.add_paragraph(data["_raw"])
@@ -46,6 +53,23 @@ def build_docx(data: dict[str, Any], profile_name: str | None = None) -> bytes:
return buf.getvalue()
+def _render_full_transcript(doc, segments: list[dict]) -> None:
+ """Hängt den Volltranskript-Anhang an: eine Überschrift, dann ein Absatz
+ pro Whisper-Segment in 9 pt (damit das Dokument nicht explodiert).
+ Leere Segmente werden übersprungen.
+ """
+ doc.add_page_break()
+ doc.add_heading("Volltranskript", level=1)
+ for seg in segments:
+ text = (seg.get("text") if isinstance(seg, dict) else str(seg)) or ""
+ text = text.strip()
+ if not text:
+ continue
+ p = doc.add_paragraph()
+ run = p.add_run(text)
+ run.font.size = Pt(9)
+
+
def _render_section(doc, data: dict, section: dict) -> None:
val = data.get(section["key"])
if val in (None, "", [], {}):
diff --git a/mac-worker/app/core/pdf_export.py b/mac-worker/app/core/pdf_export.py
index 982134a..848498e 100644
--- a/mac-worker/app/core/pdf_export.py
+++ b/mac-worker/app/core/pdf_export.py
@@ -69,7 +69,11 @@ def _para(text: str, style_name: str = "Body2") -> Paragraph:
return Paragraph(_esc(text).replace("\n", "
"), _STYLES[style_name])
-def build_pdf(data: dict[str, Any], profile_name: str | None = None) -> bytes:
+def build_pdf(
+ data: dict[str, Any],
+ profile_name: str | None = None,
+ full_transcript: list[dict] | None = None,
+) -> bytes:
profile = profiles.get(profile_name)
cfg = profile.docx or {}
@@ -108,6 +112,9 @@ def build_pdf(data: dict[str, Any], profile_name: str | None = None) -> bytes:
for section in cfg.get("sections", []) or []:
_render_section(story, data, section)
+ if cfg.get("include_full_transcript") and full_transcript:
+ _render_full_transcript(story, full_transcript)
+
# Parse-Error-Fallback
if data.get("_parse_error") and data.get("_raw"):
story.append(Paragraph("Roh-Antwort des Modells", _STYLES["SectionHeading"]))
@@ -117,6 +124,27 @@ def build_pdf(data: dict[str, Any], profile_name: str | None = None) -> bytes:
return buf.getvalue()
+def _render_full_transcript(story: list, segments: list[dict]) -> None:
+ """Anhang nach allen Sektionen: ein Absatz pro Segment in 9 pt.
+ Vor dem Block ein PageBreak, damit das Volltranskript auf einer
+ eigenen Seite beginnt und das Protokoll nicht zerreißt.
+ """
+ from reportlab.platypus import PageBreak # local import — selten genutzt
+
+ story.append(PageBreak())
+ story.append(Paragraph("Volltranskript", _STYLES["SectionHeading"]))
+ transcript_style = ParagraphStyle(
+ name="TranscriptLine", parent=_STYLES["Body2"],
+ fontSize=9, leading=12, spaceAfter=3,
+ )
+ for seg in segments:
+ text = (seg.get("text") if isinstance(seg, dict) else str(seg)) or ""
+ text = text.strip()
+ if not text:
+ continue
+ story.append(Paragraph(_esc(text).replace("\n", "
"), transcript_style))
+
+
def _render_section(story: list, data: dict, section: dict) -> None:
val = data.get(section["key"])
if val in (None, "", [], {}):
diff --git a/mac-worker/app/main.py b/mac-worker/app/main.py
index 6b045c2..48c6ae2 100644
--- a/mac-worker/app/main.py
+++ b/mac-worker/app/main.py
@@ -82,11 +82,15 @@ class ProtocolIn(BaseModel):
class DocxIn(BaseModel):
data: dict
profile: str | None = None
+ # Optional: Whisper-Segmente; werden vom Renderer nur genutzt, wenn das
+ # Profil `docx.include_full_transcript: true` setzt.
+ full_transcript: list[dict] = []
class PdfIn(BaseModel):
data: dict
profile: str | None = None
+ full_transcript: list[dict] = []
class PreloadIn(BaseModel):
@@ -170,7 +174,11 @@ async def protocol(payload: ProtocolIn):
@app.post("/api/export/docx")
async def export_docx(payload: DocxIn):
- data = build_docx(payload.data, profile_name=payload.profile)
+ data = build_docx(
+ payload.data,
+ profile_name=payload.profile,
+ full_transcript=payload.full_transcript,
+ )
return Response(
content=data,
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -180,7 +188,11 @@ async def export_docx(payload: DocxIn):
@app.post("/api/export/pdf")
async def export_pdf(payload: PdfIn):
- data = build_pdf(payload.data, profile_name=payload.profile)
+ data = build_pdf(
+ payload.data,
+ profile_name=payload.profile,
+ full_transcript=payload.full_transcript,
+ )
return Response(
content=data,
media_type="application/pdf",
diff --git a/mac-worker/profiles/interview.yaml b/mac-worker/profiles/interview.yaml
index 711d2da..1a7e830 100644
--- a/mac-worker/profiles/interview.yaml
+++ b/mac-worker/profiles/interview.yaml
@@ -44,6 +44,7 @@ protocol_prompt: |
docx:
title_field: title
default_title: Bewerbergespräch
+ include_full_transcript: true
meta:
- { key: date, label: "Datum" }
- { key: candidate, label: "Bewerber/in" }
diff --git a/mac-worker/profiles/meeting.yaml b/mac-worker/profiles/meeting.yaml
index 9afcd47..450749e 100644
--- a/mac-worker/profiles/meeting.yaml
+++ b/mac-worker/profiles/meeting.yaml
@@ -50,6 +50,10 @@ protocol_prompt: |
docx:
title_field: title
default_title: Sitzungsprotokoll
+ # Hängt das vollständige Whisper-Transkript als Anhang ans Dokument.
+ # Pipeline reicht die Segmente an den Renderer durch; LLM-Komprimierung
+ # im transcript_excerpt-Feld bleibt unverändert.
+ include_full_transcript: true
meta:
- { key: date, label: "Datum" }
- { key: topic, label: "Thema" }
diff --git a/mac-worker/profiles/phone-call.yaml b/mac-worker/profiles/phone-call.yaml
index 1a713f2..085068d 100644
--- a/mac-worker/profiles/phone-call.yaml
+++ b/mac-worker/profiles/phone-call.yaml
@@ -45,6 +45,7 @@ protocol_prompt: |
docx:
title_field: title
default_title: Telefonnotiz
+ include_full_transcript: true
meta:
- { key: date, label: "Datum" }
- { key: caller, label: "Anrufer" }