diff --git a/src/report_generator.py b/src/report_generator.py index 3dac2c0..1949a2c 100644 --- a/src/report_generator.py +++ b/src/report_generator.py @@ -417,9 +417,9 @@ def _slug_scope_label(scope: str, sections: set[str] | None) -> str: if sections == {"zusammenfassung"}: return "Zusammenfassung" if "timeline" in sections: - return "Vollstaendiger Bericht" + return "Vollständiger Bericht" return "Lagebericht" - return {"summary": "Zusammenfassung", "report": "Lagebericht", "full": "Vollstaendiger Bericht"}.get( + return {"summary": "Zusammenfassung", "report": "Lagebericht", "full": "Vollständiger Bericht"}.get( scope, "Lagebericht" ) @@ -434,6 +434,7 @@ def _build_export_metadata( sections: set[str] | None, organization_name: str | None, top_locations: list[str] | None, + snapshot_count: int = 0, ) -> dict: """Einheitlicher Metadaten-Dict fuer PDF (HTML-Meta-Tags) und DOCX (core_properties).""" is_research = incident.get("type") == "research" @@ -522,7 +523,7 @@ def _build_export_metadata( identifier = f"urn:aegissight:incident:{incident.get('id', '0')}:{now.strftime('%Y%m%dT%H%M%S')}" rights = ( "Vertrauliche Lageanalyse — AegisSight Monitor. " - "Weitergabe nur an autorisierte Empfaenger." + "Weitergabe nur an autorisierte Empfänger." ) return { @@ -546,6 +547,7 @@ def _build_export_metadata( "identifier": identifier, "rights": rights, "doc_type": "Report", + "version_id": str(max(1, snapshot_count)), } @@ -624,6 +626,18 @@ def _enrich_pdf_metadata(pdf_bytes: bytes, meta: dict) -> bytes: # xmpMM: Document- und Instance-ID fuer DMS-Versionierung xmp["xmpMM:DocumentID"] = doc_uuid xmp["xmpMM:InstanceID"] = instance_uuid + xmp["xmpMM:VersionID"] = meta.get("version_id", "1") + + # xmpMM:History — Audit-Event fuer diesen Export (einzeiliger Eintrag je Seq-Item) + history_when = (modified or datetime.now(TIMEZONE)).strftime("%Y-%m-%dT%H:%M:%S%z") + history_entry = ( + f"action=published; when={history_when}; " + f"softwareAgent={meta.get('creator_app', 'AegisSight Monitor')}; " + f"instanceID={instance_uuid}; " + f"scope={meta.get('scope_label', '')}; " + f"version={meta.get('version_id', '1')}" + ) + xmp["xmpMM:History"] = [history_entry] buf_out = io.BytesIO() pdf.save(buf_out) @@ -639,6 +653,7 @@ async def generate_pdf( sections: set[str] | None = None, organization_name: str | None = None, top_locations: list[str] | None = None, + snapshot_count: int = 0, ) -> bytes: """PDF-Report via WeasyPrint generieren.""" # Sections aus scope ableiten wenn nicht explizit angegeben @@ -670,7 +685,7 @@ async def generate_pdf( meta = _build_export_metadata( incident, articles, fact_checks, all_sources, creator, scope, sections, - organization_name, top_locations, + organization_name, top_locations, snapshot_count=snapshot_count, ) env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR))) @@ -721,6 +736,7 @@ async def generate_docx( sections: set[str] | None = None, organization_name: str | None = None, top_locations: list[str] | None = None, + snapshot_count: int = 0, ) -> bytes: """Word-Report via python-docx generieren.""" doc = Document() @@ -738,7 +754,7 @@ async def generate_docx( is_research = incident.get("type") == "research" all_sources = _prepare_sources(incident) zusammenfassung_text = executive_summary_text - bericht_summary = incident.get("summary") or "Keine Zusammenfassung verfuegbar." + bericht_summary = incident.get("summary") or "Keine Zusammenfassung verfügbar." zusammenfassung_title = "Zusammenfassung" zusammenfassung_lines: list[str] = [] @@ -751,7 +767,7 @@ async def generate_docx( meta = _build_export_metadata( incident, articles, fact_checks, all_sources, creator, scope, sections, - organization_name, top_locations, + organization_name, top_locations, snapshot_count=snapshot_count, ) # Dateimetadaten setzen (sichtbar in Explorer/Finder, DMS-Systemen) diff --git a/src/routers/incidents.py b/src/routers/incidents.py index f129152..1c1fd99 100644 --- a/src/routers/incidents.py +++ b/src/routers/incidents.py @@ -1009,6 +1009,13 @@ async def export_incident( ) top_locations = [r["location_name"] for r in await cursor.fetchall() if r["location_name"]] + # Snapshot-Count (als xmpMM:VersionID im PDF) + cursor = await db.execute( + "SELECT COUNT(*) AS cnt FROM incident_snapshots WHERE incident_id = ?", + (incident_id,), + ) + snapshot_count = (await cursor.fetchone())["cnt"] or 0 + # Artikel cursor = await db.execute( "SELECT * FROM articles WHERE incident_id = ? ORDER BY collected_at DESC", @@ -1063,6 +1070,7 @@ async def export_incident( sections=sections_set, organization_name=organization_name, top_locations=top_locations, + snapshot_count=snapshot_count, ) filename = f"{slug}_{scope_labels_key}_{date_str}.pdf" return StreamingResponse( @@ -1076,6 +1084,7 @@ async def export_incident( sections=sections_set, organization_name=organization_name, top_locations=top_locations, + snapshot_count=snapshot_count, ) filename = f"{slug}_{scope_labels_key}_{date_str}.docx" return StreamingResponse(