From c75f460ec52bc98957f818d5d6297fa34c84b805 Mon Sep 17 00:00:00 2001 From: Philipp Ostmeyer Date: Mon, 22 Jun 2026 12:45:55 +0200 Subject: [PATCH] feat: Externe Dokument-Referenzen erkennen, herunterladen & mitbewerten Nach der Transkription eines Dokuments werden referenzierte externe Dokumente (AGB, Verhaltenskodex) erkannt, sicher heruntergeladen und ihr Text dem Hauptdokument fuer die "Summen"-Bewertung angehaengt (Option D). Die Referenzen werden zusaetzlich als eigene, herunterladbare Zeilen gespeichert. - DB: dc_project_documents um parent_document_id, is_reference, source_url erweitert (DC_REFERENCES_ALTER.sql) - ORDS: Dokumentenliste um die Felder erweitert; neue Endpunkte POST /documents/:id/references und DELETE /projects/:id/references; q-Quote-Parserfehler im ai-costs-Handler ('[]') behoben - Backend: ReferenceDownloadService (SSRF-sicher via DNS-/Public-IP- Validierung, Redirect-Pruefung, Groessen-/Timeout-Limits, jsoup fuer HTML), EvaluationService.extractReferenceUrls (LLM-basierte URL-Erkennung), Orchestrierung (Discovery in Phase A, Referenz-Skip + combinedText in Phase B) - Config: dc.references.* in application.properties Verifiziert an Projekt 144: AGB-PDF/-HTML heruntergeladen, verknuepft, "Summen"-Bewertung zitiert AGB-Klauseln aus dem Referenzdokument. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 7 +- .idea/.gitignore | 10 + .idea/Dokumenten-Check.iml | 9 + .idea/compiler.xml | 18 + .idea/encodings.xml | 7 + .idea/jarRepositories.xml | 20 + .idea/misc.xml | 15 + .idea/modules.xml | 8 + .idea/remote-targets.xml | 23 + .idea/vcs.xml | 6 + .../10800752884462.ANF.pdf | Bin .../10800752884846.pdf | Bin .../15096339_Haix_21-04-26_Hann. Münden.pdf | Bin .../20042026081023-DOC200426.pdf | Bin .../38_1T0078671_FRIG_D74_D33_€800.pdf | Bin .../CarrierContract MHGZ95034.pdf | Bin .../Frigomed 27.04.2026 Hamburg - Buseck.pdf | Bin .../Ladeauftrag Unterschleißheim - Rodgau.pdf | Bin .../{ => transportauftraege}/Ladeauftrag.pdf | 0 .../M3-20260504-12595761.pdf | Bin .../P2604001395-BL_LADEAUFTRAG.pdf | Bin ...8-TRANSPORT+LS-----20260508-134124-992.pdf | Bin .../Seevetal - Regensburg - Frigomed.pdf | Bin ...11.05.2026 MM-CD Melsungen ( Frigomed).pdf | Bin ...12.05.2026 MM-CD Melsungen ( Frigomed).pdf | Bin ...TA 13.05.2026 MM- melsungen (Frigomed).pdf | Bin .../TA15.05.2026 MM- Melsungen (Frigomed).pdf | Bin .../TR_000000076534_FRIGOMED_GmbH.pdf | Bin .../Tour_10488670_20260421_085427.pdf | Bin .../Tour_124826_20260511_074915.pdf | Bin .../Tour_143070_20260421_111410.pdf | Bin .../Tour_69690_20260204_141117.pdf | Bin .../Transportauftrag 90947865.pdf | Bin .../Transportauftrag Frigomed 11.05.26.pdf | Bin .../Transportauftrag Frigomed 12.05.26.pdf | Bin .../Transportauftrag Frigomed 13.05.26.pdf | Bin .../Transportauftrag Frigomed 15.05.26.pdf | Bin .../doc25641820260508123727.pdf | Bin Beispiele/{ => zeitfenster}/4586_001.pdf | Bin CLAUDE.md | 6 +- Scripts/DC_BACKEND_PKG.sql | 50 +- Scripts/DC_DOCUMENT_PROGRESS_ALTER.sql | 50 ++ Scripts/DC_DOCUMENT_PROGRESS_ORDS.sql | 68 ++ Scripts/DC_PROJECTS_NOTIFY_ALTER.sql | 71 ++ Scripts/DC_REFERENCES_ALTER.sql | 81 ++ Scripts/DC_STRIP_TRANSLATED_FENCE.sql | 51 ++ Scripts/DC_UTILS_PKG.sql | 257 +++++- Scripts/Datenmodell DocumentCheck.sql | 13 + Scripts/ORDS REST Services.sql | 440 +++++++++- Scripts/deploy_complete_handler.sql | 217 +++++ dc-backend/Dockerfile | 4 +- dc-backend/build-and-push.sh | 0 dc-backend/follow_logs.sh | 20 + dc-backend/getPods.sh | 0 dc-backend/k8s/deployment.yaml | 4 +- dc-backend/logs.sh | 0 dc-backend/pod-ram-usage.sh | 174 ++++ dc-backend/pom.xml | 14 + .../frigosped/dc/client/MistralOcrClient.java | 19 + .../de/frigosped/dc/client/OrdsClient.java | 29 +- .../dc/client/OrdsLoggingFilter.java | 8 +- .../dc/model/CompleteProjectRequest.java | 22 + .../frigosped/dc/model/DocumentPdfEntry.java | 20 + .../dc/model/DocumentProgressRequest.java | 18 + .../frigosped/dc/model/MistralOcrRequest.java | 28 + .../dc/model/MistralOcrResponse.java | 37 + .../de/frigosped/dc/model/OrdsDocument.java | 23 + .../de/frigosped/dc/model/OrdsProject.java | 12 + .../dc/model/ReferenceCreateRequest.java | 38 + .../dc/service/CheckOrchestrationService.java | 222 ++++- .../dc/service/DocumentProcessingService.java | 68 +- .../dc/service/EvaluationService.java | 85 +- .../dc/service/MarkdownExportService.java | 376 +++++--- .../dc/service/MarkdownReportBuilder.java | 260 ++++++ .../dc/service/ReferenceDownloadService.java | 281 ++++++ .../src/main/resources/application.properties | 24 +- dc-backend/target/build-metrics.json | 1 - .../target/classes/application.properties | 24 +- .../de/frigosped/dc/client/OrdsClient.class | Bin 3339 -> 3948 bytes .../de/frigosped/dc/model/OrdsDocument.class | Bin 2886 -> 4106 bytes .../de/frigosped/dc/model/OrdsProject.class | Bin 3058 -> 3700 bytes .../service/CheckOrchestrationService.class | Bin 15364 -> 25314 bytes .../service/DocumentProcessingService.class | Bin 13770 -> 16529 bytes .../dc/service/EvaluationService.class | Bin 16029 -> 19229 bytes .../compile/default-compile/createdFiles.lst | 43 +- .../compile/default-compile/inputFiles.lst | 73 +- .../compile/null/createdFiles.lst | 12 - .../compile/null/inputFiles.lst | 15 - dc-backend/update.sh | 21 +- sqlrun.sh | 0 tmp/markdownb64.txt | 802 ++++++++++++++++++ 91 files changed, 3949 insertions(+), 255 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Dokumenten-Check.iml create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/remote-targets.xml create mode 100644 .idea/vcs.xml rename Beispiele/{ => transportauftraege}/10800752884462.ANF.pdf (100%) rename Beispiele/{ => transportauftraege}/10800752884846.pdf (100%) rename Beispiele/{ => transportauftraege}/15096339_Haix_21-04-26_Hann. Münden.pdf (100%) rename Beispiele/{ => transportauftraege}/20042026081023-DOC200426.pdf (100%) rename Beispiele/{ => transportauftraege}/38_1T0078671_FRIG_D74_D33_€800.pdf (100%) rename Beispiele/{ => transportauftraege}/CarrierContract MHGZ95034.pdf (100%) rename Beispiele/{ => transportauftraege}/Frigomed 27.04.2026 Hamburg - Buseck.pdf (100%) rename Beispiele/{ => transportauftraege}/Ladeauftrag Unterschleißheim - Rodgau.pdf (100%) rename Beispiele/{ => transportauftraege}/Ladeauftrag.pdf (100%) rename Beispiele/{ => transportauftraege}/M3-20260504-12595761.pdf (100%) rename Beispiele/{ => transportauftraege}/P2604001395-BL_LADEAUFTRAG.pdf (100%) rename Beispiele/{ => transportauftraege}/P260538-TRANSPORT+LS-----20260508-134124-992.pdf (100%) rename Beispiele/{ => transportauftraege}/Seevetal - Regensburg - Frigomed.pdf (100%) rename Beispiele/{ => transportauftraege}/TA 11.05.2026 MM-CD Melsungen ( Frigomed).pdf (100%) rename Beispiele/{ => transportauftraege}/TA 12.05.2026 MM-CD Melsungen ( Frigomed).pdf (100%) rename Beispiele/{ => transportauftraege}/TA 13.05.2026 MM- melsungen (Frigomed).pdf (100%) rename Beispiele/{ => transportauftraege}/TA15.05.2026 MM- Melsungen (Frigomed).pdf (100%) rename Beispiele/{ => transportauftraege}/TR_000000076534_FRIGOMED_GmbH.pdf (100%) rename Beispiele/{ => transportauftraege}/Tour_10488670_20260421_085427.pdf (100%) rename Beispiele/{ => transportauftraege}/Tour_124826_20260511_074915.pdf (100%) rename Beispiele/{ => transportauftraege}/Tour_143070_20260421_111410.pdf (100%) rename Beispiele/{ => transportauftraege}/Tour_69690_20260204_141117.pdf (100%) rename Beispiele/{ => transportauftraege}/Transportauftrag 90947865.pdf (100%) rename Beispiele/{ => transportauftraege}/Transportauftrag Frigomed 11.05.26.pdf (100%) rename Beispiele/{ => transportauftraege}/Transportauftrag Frigomed 12.05.26.pdf (100%) rename Beispiele/{ => transportauftraege}/Transportauftrag Frigomed 13.05.26.pdf (100%) rename Beispiele/{ => transportauftraege}/Transportauftrag Frigomed 15.05.26.pdf (100%) rename Beispiele/{ => transportauftraege}/doc25641820260508123727.pdf (100%) rename Beispiele/{ => zeitfenster}/4586_001.pdf (100%) create mode 100644 Scripts/DC_DOCUMENT_PROGRESS_ALTER.sql create mode 100644 Scripts/DC_DOCUMENT_PROGRESS_ORDS.sql create mode 100644 Scripts/DC_PROJECTS_NOTIFY_ALTER.sql create mode 100644 Scripts/DC_REFERENCES_ALTER.sql create mode 100644 Scripts/DC_STRIP_TRANSLATED_FENCE.sql create mode 100644 Scripts/deploy_complete_handler.sql mode change 100644 => 100755 dc-backend/build-and-push.sh create mode 100755 dc-backend/follow_logs.sh mode change 100644 => 100755 dc-backend/getPods.sh mode change 100644 => 100755 dc-backend/logs.sh create mode 100755 dc-backend/pod-ram-usage.sh create mode 100644 dc-backend/src/main/java/de/frigosped/dc/client/MistralOcrClient.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/model/CompleteProjectRequest.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/model/DocumentPdfEntry.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/model/DocumentProgressRequest.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrRequest.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrResponse.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/model/ReferenceCreateRequest.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/service/MarkdownReportBuilder.java create mode 100644 dc-backend/src/main/java/de/frigosped/dc/service/ReferenceDownloadService.java delete mode 100644 dc-backend/target/build-metrics.json delete mode 100644 dc-backend/target/maven-status/maven-compiler-plugin/compile/null/createdFiles.lst delete mode 100644 dc-backend/target/maven-status/maven-compiler-plugin/compile/null/inputFiles.lst mode change 100644 => 100755 dc-backend/update.sh mode change 100644 => 100755 sqlrun.sh create mode 100644 tmp/markdownb64.txt diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 876acec..6540ff8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -42,7 +42,12 @@ "Bash(KUBECONFIG=../env/frigo-dev.yaml kubectl apply *)", "Bash(KUBECONFIG=../env/frigo-dev.yaml kubectl rollout restart deployment/dc-backend -n ai-env)", "Bash(KUBECONFIG=../env/frigo-dev.yaml kubectl rollout status deployment/dc-backend -n ai-env --timeout=120s)", - "Bash(KUBECONFIG=../env/frigo-dev.yaml kubectl logs *)" + "Bash(KUBECONFIG=../env/frigo-dev.yaml kubectl logs *)", + "WebSearch", + "WebFetch(domain:docs.langchain4j.dev)", + "WebFetch(domain:mvnrepository.com)", + "WebFetch(domain:github.com)", + "WebFetch(domain:docs.mistral.ai)" ] } } diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..3575a72 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Dokumenten-Check.iml b/.idea/Dokumenten-Check.iml new file mode 100644 index 0000000..18ec59d --- /dev/null +++ b/.idea/Dokumenten-Check.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..5d597bf --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..d0a686e --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a468a99 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7eb748c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,15 @@ + + + + {} + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bee60f2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/remote-targets.xml b/.idea/remote-targets.xml new file mode 100644 index 0000000..7134cf5 --- /dev/null +++ b/.idea/remote-targets.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c8397c9 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Beispiele/10800752884462.ANF.pdf b/Beispiele/transportauftraege/10800752884462.ANF.pdf similarity index 100% rename from Beispiele/10800752884462.ANF.pdf rename to Beispiele/transportauftraege/10800752884462.ANF.pdf diff --git a/Beispiele/10800752884846.pdf b/Beispiele/transportauftraege/10800752884846.pdf similarity index 100% rename from Beispiele/10800752884846.pdf rename to Beispiele/transportauftraege/10800752884846.pdf diff --git a/Beispiele/15096339_Haix_21-04-26_Hann. Münden.pdf b/Beispiele/transportauftraege/15096339_Haix_21-04-26_Hann. Münden.pdf similarity index 100% rename from Beispiele/15096339_Haix_21-04-26_Hann. Münden.pdf rename to Beispiele/transportauftraege/15096339_Haix_21-04-26_Hann. Münden.pdf diff --git a/Beispiele/20042026081023-DOC200426.pdf b/Beispiele/transportauftraege/20042026081023-DOC200426.pdf similarity index 100% rename from Beispiele/20042026081023-DOC200426.pdf rename to Beispiele/transportauftraege/20042026081023-DOC200426.pdf diff --git a/Beispiele/38_1T0078671_FRIG_D74_D33_€800.pdf b/Beispiele/transportauftraege/38_1T0078671_FRIG_D74_D33_€800.pdf similarity index 100% rename from Beispiele/38_1T0078671_FRIG_D74_D33_€800.pdf rename to Beispiele/transportauftraege/38_1T0078671_FRIG_D74_D33_€800.pdf diff --git a/Beispiele/CarrierContract MHGZ95034.pdf b/Beispiele/transportauftraege/CarrierContract MHGZ95034.pdf similarity index 100% rename from Beispiele/CarrierContract MHGZ95034.pdf rename to Beispiele/transportauftraege/CarrierContract MHGZ95034.pdf diff --git a/Beispiele/Frigomed 27.04.2026 Hamburg - Buseck.pdf b/Beispiele/transportauftraege/Frigomed 27.04.2026 Hamburg - Buseck.pdf similarity index 100% rename from Beispiele/Frigomed 27.04.2026 Hamburg - Buseck.pdf rename to Beispiele/transportauftraege/Frigomed 27.04.2026 Hamburg - Buseck.pdf diff --git a/Beispiele/Ladeauftrag Unterschleißheim - Rodgau.pdf b/Beispiele/transportauftraege/Ladeauftrag Unterschleißheim - Rodgau.pdf similarity index 100% rename from Beispiele/Ladeauftrag Unterschleißheim - Rodgau.pdf rename to Beispiele/transportauftraege/Ladeauftrag Unterschleißheim - Rodgau.pdf diff --git a/Beispiele/Ladeauftrag.pdf b/Beispiele/transportauftraege/Ladeauftrag.pdf similarity index 100% rename from Beispiele/Ladeauftrag.pdf rename to Beispiele/transportauftraege/Ladeauftrag.pdf diff --git a/Beispiele/M3-20260504-12595761.pdf b/Beispiele/transportauftraege/M3-20260504-12595761.pdf similarity index 100% rename from Beispiele/M3-20260504-12595761.pdf rename to Beispiele/transportauftraege/M3-20260504-12595761.pdf diff --git a/Beispiele/P2604001395-BL_LADEAUFTRAG.pdf b/Beispiele/transportauftraege/P2604001395-BL_LADEAUFTRAG.pdf similarity index 100% rename from Beispiele/P2604001395-BL_LADEAUFTRAG.pdf rename to Beispiele/transportauftraege/P2604001395-BL_LADEAUFTRAG.pdf diff --git a/Beispiele/P260538-TRANSPORT+LS-----20260508-134124-992.pdf b/Beispiele/transportauftraege/P260538-TRANSPORT+LS-----20260508-134124-992.pdf similarity index 100% rename from Beispiele/P260538-TRANSPORT+LS-----20260508-134124-992.pdf rename to Beispiele/transportauftraege/P260538-TRANSPORT+LS-----20260508-134124-992.pdf diff --git a/Beispiele/Seevetal - Regensburg - Frigomed.pdf b/Beispiele/transportauftraege/Seevetal - Regensburg - Frigomed.pdf similarity index 100% rename from Beispiele/Seevetal - Regensburg - Frigomed.pdf rename to Beispiele/transportauftraege/Seevetal - Regensburg - Frigomed.pdf diff --git a/Beispiele/TA 11.05.2026 MM-CD Melsungen ( Frigomed).pdf b/Beispiele/transportauftraege/TA 11.05.2026 MM-CD Melsungen ( Frigomed).pdf similarity index 100% rename from Beispiele/TA 11.05.2026 MM-CD Melsungen ( Frigomed).pdf rename to Beispiele/transportauftraege/TA 11.05.2026 MM-CD Melsungen ( Frigomed).pdf diff --git a/Beispiele/TA 12.05.2026 MM-CD Melsungen ( Frigomed).pdf b/Beispiele/transportauftraege/TA 12.05.2026 MM-CD Melsungen ( Frigomed).pdf similarity index 100% rename from Beispiele/TA 12.05.2026 MM-CD Melsungen ( Frigomed).pdf rename to Beispiele/transportauftraege/TA 12.05.2026 MM-CD Melsungen ( Frigomed).pdf diff --git a/Beispiele/TA 13.05.2026 MM- melsungen (Frigomed).pdf b/Beispiele/transportauftraege/TA 13.05.2026 MM- melsungen (Frigomed).pdf similarity index 100% rename from Beispiele/TA 13.05.2026 MM- melsungen (Frigomed).pdf rename to Beispiele/transportauftraege/TA 13.05.2026 MM- melsungen (Frigomed).pdf diff --git a/Beispiele/TA15.05.2026 MM- Melsungen (Frigomed).pdf b/Beispiele/transportauftraege/TA15.05.2026 MM- Melsungen (Frigomed).pdf similarity index 100% rename from Beispiele/TA15.05.2026 MM- Melsungen (Frigomed).pdf rename to Beispiele/transportauftraege/TA15.05.2026 MM- Melsungen (Frigomed).pdf diff --git a/Beispiele/TR_000000076534_FRIGOMED_GmbH.pdf b/Beispiele/transportauftraege/TR_000000076534_FRIGOMED_GmbH.pdf similarity index 100% rename from Beispiele/TR_000000076534_FRIGOMED_GmbH.pdf rename to Beispiele/transportauftraege/TR_000000076534_FRIGOMED_GmbH.pdf diff --git a/Beispiele/Tour_10488670_20260421_085427.pdf b/Beispiele/transportauftraege/Tour_10488670_20260421_085427.pdf similarity index 100% rename from Beispiele/Tour_10488670_20260421_085427.pdf rename to Beispiele/transportauftraege/Tour_10488670_20260421_085427.pdf diff --git a/Beispiele/Tour_124826_20260511_074915.pdf b/Beispiele/transportauftraege/Tour_124826_20260511_074915.pdf similarity index 100% rename from Beispiele/Tour_124826_20260511_074915.pdf rename to Beispiele/transportauftraege/Tour_124826_20260511_074915.pdf diff --git a/Beispiele/Tour_143070_20260421_111410.pdf b/Beispiele/transportauftraege/Tour_143070_20260421_111410.pdf similarity index 100% rename from Beispiele/Tour_143070_20260421_111410.pdf rename to Beispiele/transportauftraege/Tour_143070_20260421_111410.pdf diff --git a/Beispiele/Tour_69690_20260204_141117.pdf b/Beispiele/transportauftraege/Tour_69690_20260204_141117.pdf similarity index 100% rename from Beispiele/Tour_69690_20260204_141117.pdf rename to Beispiele/transportauftraege/Tour_69690_20260204_141117.pdf diff --git a/Beispiele/Transportauftrag 90947865.pdf b/Beispiele/transportauftraege/Transportauftrag 90947865.pdf similarity index 100% rename from Beispiele/Transportauftrag 90947865.pdf rename to Beispiele/transportauftraege/Transportauftrag 90947865.pdf diff --git a/Beispiele/Transportauftrag Frigomed 11.05.26.pdf b/Beispiele/transportauftraege/Transportauftrag Frigomed 11.05.26.pdf similarity index 100% rename from Beispiele/Transportauftrag Frigomed 11.05.26.pdf rename to Beispiele/transportauftraege/Transportauftrag Frigomed 11.05.26.pdf diff --git a/Beispiele/Transportauftrag Frigomed 12.05.26.pdf b/Beispiele/transportauftraege/Transportauftrag Frigomed 12.05.26.pdf similarity index 100% rename from Beispiele/Transportauftrag Frigomed 12.05.26.pdf rename to Beispiele/transportauftraege/Transportauftrag Frigomed 12.05.26.pdf diff --git a/Beispiele/Transportauftrag Frigomed 13.05.26.pdf b/Beispiele/transportauftraege/Transportauftrag Frigomed 13.05.26.pdf similarity index 100% rename from Beispiele/Transportauftrag Frigomed 13.05.26.pdf rename to Beispiele/transportauftraege/Transportauftrag Frigomed 13.05.26.pdf diff --git a/Beispiele/Transportauftrag Frigomed 15.05.26.pdf b/Beispiele/transportauftraege/Transportauftrag Frigomed 15.05.26.pdf similarity index 100% rename from Beispiele/Transportauftrag Frigomed 15.05.26.pdf rename to Beispiele/transportauftraege/Transportauftrag Frigomed 15.05.26.pdf diff --git a/Beispiele/doc25641820260508123727.pdf b/Beispiele/transportauftraege/doc25641820260508123727.pdf similarity index 100% rename from Beispiele/doc25641820260508123727.pdf rename to Beispiele/transportauftraege/doc25641820260508123727.pdf diff --git a/Beispiele/4586_001.pdf b/Beispiele/zeitfenster/4586_001.pdf similarity index 100% rename from Beispiele/4586_001.pdf rename to Beispiele/zeitfenster/4586_001.pdf diff --git a/CLAUDE.md b/CLAUDE.md index f9401ac..d1fdf70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,10 +8,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Database Setup +> **Tool:** Always use `sql` (SQLcl), never `sqlplus`. + ```bash # Connect to Oracle DB and run scripts in order: -sqlplus user/pass@db @"Scripts/Datenmodell DocumentCheck.sql" -sqlplus user/pass@db @"Scripts/Fragenkatalog-AGB anlegen.sql" +sql user/pass@db @"Scripts/Datenmodell DocumentCheck.sql" +sql user/pass@db @"Scripts/Fragenkatalog-AGB anlegen.sql" ``` No build/compilation step — APEX applications deploy directly to Oracle instances. diff --git a/Scripts/DC_BACKEND_PKG.sql b/Scripts/DC_BACKEND_PKG.sql index f300516..e737fda 100644 --- a/Scripts/DC_BACKEND_PKG.sql +++ b/Scripts/DC_BACKEND_PKG.sql @@ -52,12 +52,28 @@ CREATE OR REPLACE PACKAGE DC_BACKEND_PKG AS -- ------------------------------------------------------------------------- FUNCTION health_check RETURN BOOLEAN; + -- ------------------------------------------------------------------------- + -- Setzt Projekt auf PENDING zurück – eigene Transaktion (AUTONOMOUS COMMIT). + -- dc_projects.status → PENDING, report_markdown → NULL + -- dc_project_documents.original_text/translated_text → NULL, progress → 0 + -- dc_results löschen + -- ------------------------------------------------------------------------- + PROCEDURE reset_project( + p_project_id IN NUMBER + ); + -- ------------------------------------------------------------------------- -- Startet die Prüfung und wirft eine Exception wenn nicht 202 zurückkommt. -- Convenience-Wrapper für direkte APEX-Button-Aufrufe. + -- + -- p_reset TRUE = Projekt vor dem Start zurücksetzen (eigener COMMIT): + -- dc_projects.status → PENDING, report_markdown → NULL + -- dc_project_documents.original_text/translated_text → NULL, progress → 0 + -- dc_results löschen -- ------------------------------------------------------------------------- PROCEDURE start_check_or_raise( - p_project_id IN NUMBER + p_project_id IN NUMBER, + p_reset IN BOOLEAN DEFAULT FALSE ); -- ------------------------------------------------------------------------- @@ -200,6 +216,31 @@ END DC_BACKEND_PKG; -- ============================================================================= CREATE OR REPLACE PACKAGE BODY DC_BACKEND_PKG AS + -- ------------------------------------------------------------------------- + -- Setzt Projekt auf PENDING zurück – eigene Transaktion (AUTONOMOUS). + -- ------------------------------------------------------------------------- + PROCEDURE reset_project(p_project_id IN NUMBER) IS + PRAGMA AUTONOMOUS_TRANSACTION; + BEGIN + UPDATE dc_projects + SET status = 'PENDING', + report_markdown = NULL + WHERE id = p_project_id; + + UPDATE dc_project_documents + SET original_text = NULL, + translated_text = NULL, + progress = 0, + status = 'PENDING' + WHERE project_id = p_project_id; + + DELETE FROM dc_results + WHERE project_id = p_project_id; + + COMMIT; + END reset_project; + + -- ------------------------------------------------------------------------- -- Setzt gemeinsame Request-Header (Content-Type + optional API-Key) -- ------------------------------------------------------------------------- @@ -274,11 +315,16 @@ CREATE OR REPLACE PACKAGE BODY DC_BACKEND_PKG AS -- ------------------------------------------------------------------------- PROCEDURE start_check_or_raise( - p_project_id IN NUMBER + p_project_id IN NUMBER, + p_reset IN BOOLEAN DEFAULT FALSE ) IS l_status NUMBER; l_response VARCHAR2(4000); BEGIN + IF p_reset THEN + reset_project(p_project_id); + END IF; + start_check( p_project_id => p_project_id, p_status_code => l_status, diff --git a/Scripts/DC_DOCUMENT_PROGRESS_ALTER.sql b/Scripts/DC_DOCUMENT_PROGRESS_ALTER.sql new file mode 100644 index 0000000..51e4ecc --- /dev/null +++ b/Scripts/DC_DOCUMENT_PROGRESS_ALTER.sql @@ -0,0 +1,50 @@ +-- ============================================================================= +-- DC_DOCUMENT_PROGRESS_ALTER.sql +-- Ergänzt dc_project_documents um status und progress für Dokument-Fortschritt. +-- Idempotent: Fehler "Spalte existiert bereits" wird ignoriert. +-- ============================================================================= + +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_project_documents ADD ( + status VARCHAR2(20) DEFAULT ''PENDING'', + progress NUMBER DEFAULT 0 + ) + '; + DBMS_OUTPUT.PUT_LINE('Spalten status/progress angelegt.'); +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -1430 THEN + DBMS_OUTPUT.PUT_LINE('Spalten existieren bereits – kein Änderungsbedarf.'); + ELSE + RAISE; + END IF; +END; +/ + +-- Bestehende Zeilen initialisieren +UPDATE dc_project_documents +SET status = 'PENDING', + progress = 0 +WHERE status IS NULL; + +COMMIT; + +-- Check-Constraint (idempotent) +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_project_documents + ADD CONSTRAINT dc_project_documents_status_ck + CHECK (status IN (''PENDING'', ''IN_PROGRESS'', ''COMPLETED'', ''ERROR'')) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE IN (-2264, -2261) THEN NULL; -- Constraint existiert bereits + ELSE RAISE; + END IF; +END; +/ + +COMMIT; + +EXIT; diff --git a/Scripts/DC_DOCUMENT_PROGRESS_ORDS.sql b/Scripts/DC_DOCUMENT_PROGRESS_ORDS.sql new file mode 100644 index 0000000..91ab9cf --- /dev/null +++ b/Scripts/DC_DOCUMENT_PROGRESS_ORDS.sql @@ -0,0 +1,68 @@ +-- ============================================================================= +-- DC_DOCUMENT_PROGRESS_ORDS.sql +-- Registriert den ORDS-Endpunkt PUT /api/dc/documents/:doc_id/progress. +-- Idempotent: bereits vorhandene Templates/Handler werden überschrieben. +-- ============================================================================= + +BEGIN + -- Template (idempotent) + ORDS.DEFINE_TEMPLATE( + p_module_name => 'frigosped.dc', + p_pattern => 'documents/:doc_id/progress', + p_priority => 0, + p_comments => 'Dokument-Fortschritt und -Status schreiben' + ); + + -- PUT /api/dc/documents/:doc_id/progress + -- Body: {"progress":45,"status":"IN_PROGRESS"} + ORDS.DEFINE_HANDLER( + p_module_name => 'frigosped.dc', + p_pattern => 'documents/:doc_id/progress', + p_method => 'PUT', + p_source_type => ORDS.SOURCE_TYPE_PLSQL, + p_comments => 'Setzt progress (0-100) und/oder status des Dokuments', + p_source => + 'DECLARE + v_body CLOB; + v_progress NUMBER; + v_status VARCHAR2(20); + v_rows INTEGER; + BEGIN + v_body := :body_text; + v_progress := TO_NUMBER(JSON_VALUE(v_body, ''$.progress'')); + v_status := JSON_VALUE(v_body, ''$.status''); + + IF v_progress IS NOT NULL AND (v_progress < 0 OR v_progress > 100) THEN + :status_code := 400; + HTP.P(''{"error":"progress muss zwischen 0 und 100 liegen"}''); + RETURN; + END IF; + + UPDATE dc_project_documents + SET progress = CASE WHEN JSON_EXISTS(v_body, ''$.progress'') THEN v_progress ELSE progress END, + status = CASE WHEN JSON_EXISTS(v_body, ''$.status'') THEN v_status ELSE status END + WHERE id = :doc_id; + + v_rows := SQL%ROWCOUNT; + IF v_rows = 0 THEN + :status_code := 404; + HTP.P(''{"error":"Dokument nicht gefunden","doc_id":'' || :doc_id || ''}''); + RETURN; + END IF; + + :status_code := 200; + HTP.P(''{"doc_id":'' || :doc_id || '',"updated":true}''); + + EXCEPTION + WHEN OTHERS THEN + ROLLBACK; + :status_code := 500; + HTP.P(''{"error":"'' || REPLACE(SQLERRM, ''"'', '''''''') || ''"}''); + END;' + ); + + COMMIT; +END; +/ + +EXIT; diff --git a/Scripts/DC_PROJECTS_NOTIFY_ALTER.sql b/Scripts/DC_PROJECTS_NOTIFY_ALTER.sql new file mode 100644 index 0000000..c8a5a98 --- /dev/null +++ b/Scripts/DC_PROJECTS_NOTIFY_ALTER.sql @@ -0,0 +1,71 @@ +-- ============================================================================= +-- DC_PROJECTS_NOTIFY_ALTER.sql +-- Ergänzt dc_projects um drei Berichtstyp-Flags für die E-Mail-Benachrichtigung. +-- Idempotent: bereits vorhandene Spalten werden ignoriert. +-- ============================================================================= + +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_projects ADD ( + notify_ok CHAR(1) DEFAULT ''Y'' NOT NULL, + notify_hinweis CHAR(1) DEFAULT ''Y'' NOT NULL, + notify_abweichend CHAR(1) DEFAULT ''Y'' NOT NULL + ) + '; + DBMS_OUTPUT.PUT_LINE('Spalten notify_ok/notify_hinweis/notify_abweichend angelegt.'); +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -1430 THEN + DBMS_OUTPUT.PUT_LINE('Spalten existieren bereits – kein Änderungsbedarf.'); + ELSE + RAISE; + END IF; +END; +/ + +-- Check-Constraints (idempotent) +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_projects + ADD CONSTRAINT dc_projects_notify_ok_ck + CHECK (notify_ok IN (''Y'',''N'')) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE IN (-2264, -2261) THEN NULL; + ELSE RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_projects + ADD CONSTRAINT dc_projects_notify_hinweis_ck + CHECK (notify_hinweis IN (''Y'',''N'')) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE IN (-2264, -2261) THEN NULL; + ELSE RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_projects + ADD CONSTRAINT dc_projects_notify_abweichend_ck + CHECK (notify_abweichend IN (''Y'',''N'')) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE IN (-2264, -2261) THEN NULL; + ELSE RAISE; + END IF; +END; +/ + +COMMIT; + +EXIT; \ No newline at end of file diff --git a/Scripts/DC_REFERENCES_ALTER.sql b/Scripts/DC_REFERENCES_ALTER.sql new file mode 100644 index 0000000..16abcf9 --- /dev/null +++ b/Scripts/DC_REFERENCES_ALTER.sql @@ -0,0 +1,81 @@ +-- ============================================================================= +-- DC_REFERENCES_ALTER.sql +-- Ergänzt dc_project_documents um Felder für heruntergeladene externe Referenzen. +-- parent_document_id → Self-FK auf das Originaldokument (NULL bei Originalen) +-- is_reference → 0 = Originaldokument, 1 = heruntergeladene Referenz +-- source_url → Ursprungs-URL der heruntergeladenen Referenz +-- Idempotent: Fehler "Spalte/Constraint existiert bereits" wird ignoriert. +-- ============================================================================= + +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_project_documents ADD ( + parent_document_id NUMBER, + is_reference NUMBER DEFAULT 0, + source_url VARCHAR2(2000 CHAR) + ) + '; + DBMS_OUTPUT.PUT_LINE('Spalten parent_document_id/is_reference/source_url angelegt.'); +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -1430 THEN + DBMS_OUTPUT.PUT_LINE('Spalten existieren bereits – kein Änderungsbedarf.'); + ELSE + RAISE; + END IF; +END; +/ + +-- Bestehende Zeilen initialisieren +UPDATE dc_project_documents +SET is_reference = 0 +WHERE is_reference IS NULL; + +COMMIT; + +-- Self-FK (idempotent) +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_project_documents + ADD CONSTRAINT dc_project_documents_parent_fk + FOREIGN KEY (parent_document_id) REFERENCES dc_project_documents (id) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE IN (-2264, -2261, -2275) THEN NULL; -- Constraint existiert bereits + ELSE RAISE; + END IF; +END; +/ + +-- Check-Constraint is_reference (idempotent) +BEGIN + EXECUTE IMMEDIATE ' + ALTER TABLE dc_project_documents + ADD CONSTRAINT dc_project_documents_is_ref_ck + CHECK (is_reference IN (0, 1)) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE IN (-2264, -2261) THEN NULL; -- Constraint existiert bereits + ELSE RAISE; + END IF; +END; +/ + +-- Index auf parent_document_id (idempotent) +BEGIN + EXECUTE IMMEDIATE ' + CREATE INDEX dc_project_documents_parent_i ON dc_project_documents (parent_document_id) + '; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -955 THEN NULL; -- Index existiert bereits + ELSE RAISE; + END IF; +END; +/ + +COMMIT; + +EXIT; diff --git a/Scripts/DC_STRIP_TRANSLATED_FENCE.sql b/Scripts/DC_STRIP_TRANSLATED_FENCE.sql new file mode 100644 index 0000000..276b403 --- /dev/null +++ b/Scripts/DC_STRIP_TRANSLATED_FENCE.sql @@ -0,0 +1,51 @@ +-- ============================================================================= +-- Bereinigt umschließende Markdown-Code-Fences (```markdown ... ```) aus der +-- Spalte TRANSLATED_TEXT in DC_PROJECT_DOCUMENTS. +-- +-- Hintergrund: Das Übersetzungs-Modell hat den Text mal mit, mal ohne Fence +-- gespeichert. Der Backend-Code strippt den Fence ab jetzt beim Schreiben; +-- dieses Skript bereinigt die bereits vorhandenen Altdatensätze. +-- +-- Idempotent: Datensätze ohne führenden ``` bleiben unverändert. +-- ============================================================================= + +SET SERVEROUTPUT ON + +DECLARE + v_count PLS_INTEGER := 0; + v_text CLOB; + v_nl PLS_INTEGER; +BEGIN + FOR rec IN ( + SELECT id, translated_text + FROM dc_project_documents + WHERE translated_text IS NOT NULL + AND LTRIM(translated_text) LIKE '```%' + ) LOOP + v_text := LTRIM(rec.translated_text); + + -- Erste Zeile (z. B. ```markdown) entfernen + v_nl := INSTR(v_text, CHR(10)); + IF v_nl > 0 THEN + v_text := SUBSTR(v_text, v_nl + 1); + END IF; + + -- Schließendes ``` am Ende entfernen + v_text := RTRIM(v_text); + IF SUBSTR(v_text, -3) = '```' THEN + v_text := SUBSTR(v_text, 1, LENGTH(v_text) - 3); + END IF; + + v_text := TRIM(v_text); + + UPDATE dc_project_documents + SET translated_text = v_text + WHERE id = rec.id; + + v_count := v_count + 1; + END LOOP; + + COMMIT; + DBMS_OUTPUT.PUT_LINE('Bereinigte Datensätze: ' || v_count); +END; +/ diff --git a/Scripts/DC_UTILS_PKG.sql b/Scripts/DC_UTILS_PKG.sql index ee0af24..c85e5f8 100644 --- a/Scripts/DC_UTILS_PKG.sql +++ b/Scripts/DC_UTILS_PKG.sql @@ -3,7 +3,7 @@ -- Hilfsprozeduren und -funktionen für den Dokumenten-Check -- -- Deployment: --- sqlplus user/pass@db @"Scripts/DC_UTILS_PKG.sql" +-- sql user/pass@db @"Scripts/DC_UTILS_PKG.sql" -- ============================================================================= @@ -12,13 +12,24 @@ -- ============================================================================= CREATE OR REPLACE PACKAGE DC_UTILS_PKG AS + -- Absender-Adresse für Berichts-Mails (anpassen falls nötig) + c_mail_from CONSTANT VARCHAR2(100) := 'express-o@frigosped.de'; + + -- Basis-URL der APEX-App für Direktlinks in Mails (Friendly URL, ohne + -- Session-ID -> APEX Deep-Linking leitet bei Bedarf über den Login). + -- Die Projekt-ID wird angehängt: ...?p7_project_id= + c_app_link_base CONSTANT VARCHAR2(300) := + 'https://apex-ai.express-o.org/ords/r/ai_dev/dokumentencheck/' || + 'projekt-bearbeiten?p7_project_id='; + -- ------------------------------------------------------------------------- -- Erstellt einen Markdown-Prüfbericht für ein Projekt und speichert ihn - -- in dc_projects.report_markdown. + -- in dc_projects.report_markdown. Anschließend wird der Bericht per Mail + -- an notification_email versendet (sofern gesetzt). -- -- Aufbau des Berichts: -- 1. Kopf (Projektname, Abschlussdatum) - -- 2. Zusammenfassung (OK / Hinweise / Abweichungen) + -- 2. Zusammenfassung (OK / Hinweise / Abweichungen) – gefiltert nach notify_* -- 3. Ergebnisse: je Dokument → Kategorie (order_nr) → Frage (order_nr) -- Icons: ✅ OK | ⚠️ Hinweis | ❌ Abweichung | ❓ Unklar -- @@ -27,6 +38,17 @@ CREATE OR REPLACE PACKAGE DC_UTILS_PKG AS -- ------------------------------------------------------------------------- PROCEDURE generate_report(p_project_id IN NUMBER); + -- ------------------------------------------------------------------------- + -- Versendet den gespeicherten Markdown-Bericht per E-Mail als PDF-Anhang. + -- Wird automatisch von generate_report aufgerufen; kann aber auch + -- manuell ausgelöst werden (z.B. für erneuten Versand). + -- Tut nichts wenn notification_email oder report_markdown leer ist. + -- p_skip_pdf = TRUE: kein HTTP-Rückruf ans Java-Backend (nur Text-Mail). + -- ------------------------------------------------------------------------- + PROCEDURE send_report_email(p_project_id IN NUMBER, + p_skip_pdf IN BOOLEAN DEFAULT FALSE, + p_pdf IN BLOB DEFAULT NULL); + END DC_UTILS_PKG; / @@ -44,11 +66,24 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS v_status dc_projects.status%TYPE; v_done dc_projects.completed_at%TYPE; - v_ok_cnt NUMBER := 0; - v_warn_cnt NUMBER := 0; - v_dev_cnt NUMBER := 0; - v_unkl_cnt NUMBER := 0; - v_total NUMBER := 0; + v_ok_cnt NUMBER := 0; + v_warn_cnt NUMBER := 0; + v_dev_cnt NUMBER := 0; + v_unkl_cnt NUMBER := 0; + v_total NUMBER := 0; + v_total_all NUMBER := 0; + + -- Zähler pro Dokument + v_doc_ok_cnt NUMBER := 0; + v_doc_warn_cnt NUMBER := 0; + v_doc_dev_cnt NUMBER := 0; + v_doc_unkl_cnt NUMBER := 0; + v_doc_total NUMBER := 0; -- gefiltert (für ok_cnt-Berechnung) + v_doc_total_all NUMBER := 0; -- ungefiltert (für Gesamt-Zeile) + + v_notify_ok dc_projects.notify_ok%TYPE; + v_notify_hinweis dc_projects.notify_hinweis%TYPE; + v_notify_abweichend dc_projects.notify_abweichend%TYPE; -- Emoji-Konstanten via UNISTR (vermeidet Quellcode-Encoding-Probleme) C_OK CONSTANT VARCHAR2(10 CHAR) := UNISTR('\2705'); -- ✅ @@ -70,6 +105,10 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS JOIN dc_results r ON r.question_id = q.id WHERE r.project_id = p_project_id AND r.doc_id = p_doc_id + AND ( (r.deviation = 1 AND v_notify_abweichend = 'Y') + OR (r.warning = 1 AND v_notify_hinweis = 'Y') + OR (r.deviation = 0 AND r.warning = 0 AND v_notify_ok = 'Y') + ) ORDER BY qc.order_nr, qc.name; CURSOR c_qs(p_doc_id NUMBER, p_cat_id NUMBER) IS @@ -85,6 +124,10 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS WHERE r.project_id = p_project_id AND r.doc_id = p_doc_id AND q.category_id = p_cat_id + AND ( (r.deviation = 1 AND v_notify_abweichend = 'Y') + OR (r.warning = 1 AND v_notify_hinweis = 'Y') + OR (r.deviation = 0 AND r.warning = 0 AND v_notify_ok = 'Y') + ) ORDER BY q.order_nr, q.id; v_icon VARCHAR2(20 CHAR); @@ -102,11 +145,18 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS BEGIN DBMS_LOB.CREATETEMPORARY(v_md, TRUE); - SELECT name, status, completed_at - INTO v_name, v_status, v_done + SELECT name, status, completed_at, + notify_ok, notify_hinweis, notify_abweichend + INTO v_name, v_status, v_done, + v_notify_ok, v_notify_hinweis, v_notify_abweichend FROM dc_projects WHERE id = p_project_id; + SELECT COUNT(*) + INTO v_total_all + FROM dc_results + WHERE project_id = p_project_id; + SELECT SUM(CASE WHEN deviation = 1 THEN 1 ELSE 0 END), SUM(CASE WHEN warning = 1 THEN 1 ELSE 0 END), SUM(CASE WHEN result_type = 'UNKLAR' @@ -115,7 +165,11 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS COUNT(*) INTO v_dev_cnt, v_warn_cnt, v_unkl_cnt, v_total FROM dc_results - WHERE project_id = p_project_id; + WHERE project_id = p_project_id + AND ( (deviation = 1 AND v_notify_abweichend = 'Y') + OR (warning = 1 AND v_notify_hinweis = 'Y') + OR (deviation = 0 AND warning = 0 AND v_notify_ok = 'Y') + ); v_ok_cnt := v_total - v_dev_cnt - v_warn_cnt - v_unkl_cnt; @@ -138,13 +192,19 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS -- ===================================================================== ln('## Zusammenfassung'); ln(); - ln(C_OK || ' OK: ' || v_ok_cnt); - ln(C_WARN || ' Hinweise: ' || v_warn_cnt); - ln(C_NOK || ' Abweichungen: ' || v_dev_cnt); - IF v_unkl_cnt > 0 THEN - ln(C_UNKL || ' Unklar: ' || v_unkl_cnt); + IF v_notify_ok = 'Y' THEN + ln(C_OK || ' OK: ' || v_ok_cnt); + IF v_unkl_cnt > 0 THEN + ln(C_UNKL || ' Unklar: ' || v_unkl_cnt); + END IF; END IF; - ln('**Gesamt: ' || v_total || '**'); + IF v_notify_hinweis = 'Y' THEN + ln(C_WARN || ' Hinweise: ' || v_warn_cnt); + END IF; + IF v_notify_abweichend = 'Y' THEN + ln(C_NOK || ' Abweichungen: ' || v_dev_cnt); + END IF; + ln('**Gesamt: ' || v_total_all || '**'); ln(); ln('---'); ln(); @@ -159,6 +219,42 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS ln('## ' || C_DOC || ' ' || doc.filename); ln(); + -- Zusammenfassung je Dokument (gefiltert nach notify_*, analog zur Gesamt-Zusammenfassung) + SELECT SUM(CASE WHEN deviation = 1 + AND v_notify_abweichend = 'Y' THEN 1 ELSE 0 END), + SUM(CASE WHEN warning = 1 AND deviation = 0 + AND v_notify_hinweis = 'Y' THEN 1 ELSE 0 END), + SUM(CASE WHEN result_type = 'UNKLAR' AND deviation = 0 AND warning = 0 + AND v_notify_ok = 'Y' THEN 1 ELSE 0 END), + SUM(CASE WHEN (deviation = 1 AND v_notify_abweichend = 'Y') + OR (warning = 1 AND v_notify_hinweis = 'Y') + OR (deviation = 0 AND warning = 0 + AND v_notify_ok = 'Y') THEN 1 ELSE 0 END), + COUNT(*) + INTO v_doc_dev_cnt, v_doc_warn_cnt, v_doc_unkl_cnt, v_doc_total, v_doc_total_all + FROM dc_results + WHERE project_id = p_project_id + AND doc_id = doc.id; + + v_doc_ok_cnt := v_doc_total - v_doc_dev_cnt - v_doc_warn_cnt - v_doc_unkl_cnt; + + IF v_notify_ok = 'Y' THEN + ln(C_OK || ' OK: ' || v_doc_ok_cnt); + IF v_doc_unkl_cnt > 0 THEN + ln(C_UNKL || ' Unklar: ' || v_doc_unkl_cnt); + END IF; + END IF; + IF v_notify_hinweis = 'Y' THEN + ln(C_WARN || ' Hinweise: ' || v_doc_warn_cnt); + END IF; + IF v_notify_abweichend = 'Y' THEN + ln(C_NOK || ' Abweichungen: ' || v_doc_dev_cnt); + END IF; + ln('**Gesamt: ' || v_doc_total_all || '**'); + ln(); + ln('---'); + ln(); + FOR cat IN c_cats(doc.id) LOOP ln(); ln('### ' || cat.name); @@ -172,7 +268,7 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS ELSE v_icon := C_OK; END IF; - + ln(); ln(); ln(v_icon || ' **' || q.question_text || '**'); @@ -208,12 +304,137 @@ CREATE OR REPLACE PACKAGE BODY DC_UTILS_PKG AS DBMS_LOB.FREETEMPORARY(v_md); + -- Mail-Versand (Fehler blockieren nie den Report-Build) + BEGIN + send_report_email(p_project_id); + EXCEPTION WHEN OTHERS THEN NULL; + END; + EXCEPTION WHEN OTHERS THEN DBMS_LOB.FREETEMPORARY(v_md); RAISE; END generate_report; + + -- ------------------------------------------------------------------------- + PROCEDURE send_report_email(p_project_id IN NUMBER, + p_skip_pdf IN BOOLEAN DEFAULT FALSE, + p_pdf IN BLOB DEFAULT NULL) IS + + v_email dc_projects.notification_email%TYPE; + v_name dc_projects.name%TYPE; + v_markdown dc_projects.report_markdown%TYPE; + v_done dc_projects.completed_at%TYPE; + v_mail_id NUMBER; + v_pdf BLOB; + v_body VARCHAR2(4000); + + v_ok_cnt NUMBER; + v_warn_cnt NUMBER; + v_dev_cnt NUMBER; + v_total_all NUMBER; + + C_OK CONSTANT VARCHAR2(10 CHAR) := UNISTR('\2705'); -- ✅ + C_NOK CONSTANT VARCHAR2(10 CHAR) := UNISTR('\274C'); -- ❌ + C_WARN CONSTANT VARCHAR2(10 CHAR) := UNISTR('\26A0\FE0F'); -- ⚠️ + + CRLF CONSTANT VARCHAR2(2) := CHR(13) || CHR(10); + + BEGIN + SELECT notification_email, name, report_markdown, completed_at + INTO v_email, v_name, v_markdown, v_done + FROM dc_projects + WHERE id = p_project_id; + + IF v_email IS NULL OR TRIM(v_email) IS NULL THEN RETURN; END IF; + IF v_markdown IS NULL OR DBMS_LOB.GETLENGTH(v_markdown) = 0 THEN RETURN; END IF; + + -- Zusammenfassung aus dc_results (alle Ergebnisse, ungefiltert) + SELECT COUNT(*), + SUM(CASE WHEN deviation = 1 THEN 1 ELSE 0 END), + SUM(CASE WHEN warning = 1 AND deviation = 0 THEN 1 ELSE 0 END) + INTO v_total_all, v_dev_cnt, v_warn_cnt + FROM dc_results + WHERE project_id = p_project_id; + + v_ok_cnt := v_total_all - v_dev_cnt - v_warn_cnt; + + -- ===================================================================== + -- E-Mail-Beschreibungstext + -- ===================================================================== + v_body := + 'Sehr geehrte Damen und Herren,' || CRLF || + CRLF || + 'die automatische Dokumentenprüfung für das Projekt' || CRLF || + '„' || v_name || '"' || CRLF || + 'wurde am ' || TO_CHAR(NVL(v_done, SYSDATE), 'DD.MM.YYYY') || + ' erfolgreich abgeschlossen.' || CRLF || + CRLF || + 'Zusammenfassung der Prüfergebnisse:' || CRLF || + C_OK || ' OK: ' || v_ok_cnt || CRLF || + C_WARN || ' Hinweise: ' || v_warn_cnt || CRLF || + C_NOK || ' Abweichungen: ' || v_dev_cnt || CRLF || + ' Gesamt: ' || v_total_all || ' Prüfpunkte' || CRLF || + CRLF || + 'Den vollständigen Prüfbericht finden Sie als PDF im Anhang.' || CRLF || + CRLF || + 'Direkt zur Prüfung in der Anwendung:' || CRLF || + c_app_link_base || p_project_id || CRLF || + CRLF || + 'Mit freundlichen Grüßen' || CRLF || + 'Ihr Dokumenten-Check-System' || CRLF || + 'Frigosped Express GmbH'; + + -- ===================================================================== + -- PDF bestimmen: Priorität p_pdf (von Java mitgeliefert) > + -- convert_markdown HTTP-Callback > kein PDF (skip_pdf oder Fehler) + -- ===================================================================== + IF p_pdf IS NOT NULL AND DBMS_LOB.GETLENGTH(p_pdf) > 0 THEN + -- Java hat das PDF schon fertig mitgeschickt – direkt verwenden + v_pdf := p_pdf; + ELSIF NOT p_skip_pdf THEN + BEGIN + v_pdf := DC_BACKEND_PKG.convert_markdown(v_markdown, 'PDF'); + EXCEPTION WHEN OTHERS THEN + DBMS_OUTPUT.PUT_LINE('PDF-Generierung fehlgeschlagen: ' || SQLERRM); + v_pdf := NULL; + END; + END IF; + + IF v_pdf IS NOT NULL THEN + -- Mail mit PDF-Anhang + v_mail_id := APEX_MAIL.SEND( + p_to => v_email, + p_from => c_mail_from, + p_subj => 'Prüfbericht: ' || v_name, + p_body => v_body + ); + + APEX_MAIL.ADD_ATTACHMENT( + p_mail_id => v_mail_id, + p_attachment => v_pdf, + p_filename => 'Pruefbericht_' || + REGEXP_REPLACE(v_name, '[^A-Za-z0-9_äöüÄÖÜß-]', '_') || + '.pdf', + p_mime_type => 'application/pdf' + ); + ELSE + -- Fallback: Backend nicht erreichbar → Markdown direkt im Body + APEX_MAIL.SEND( + p_to => v_email, + p_from => c_mail_from, + p_subj => 'Prüfbericht: ' || v_name, + p_body => v_body || CRLF || CRLF || + '--- Detaillierter Bericht ---' || CRLF || CRLF || + v_markdown + ); + END IF; + + EXCEPTION + WHEN NO_DATA_FOUND THEN NULL; + END send_report_email; + END DC_UTILS_PKG; / diff --git a/Scripts/Datenmodell DocumentCheck.sql b/Scripts/Datenmodell DocumentCheck.sql index e266acc..e6fea8b 100644 --- a/Scripts/Datenmodell DocumentCheck.sql +++ b/Scripts/Datenmodell DocumentCheck.sql @@ -169,6 +169,15 @@ create table dc_projects ( progress number default on null 0, completed_at date, notification_email varchar2(100 char), + notify_ok char(1 char) default 'Y' not null + constraint dc_projects_notify_ok_ck + check (notify_ok in ('Y','N')), + notify_hinweis char(1 char) default 'Y' not null + constraint dc_projects_notify_hinweis_ck + check (notify_hinweis in ('Y','N')), + notify_abweichend char(1 char) default 'Y' not null + constraint dc_projects_notify_abweichend_ck + check (notify_abweichend in ('Y','N')), row_version integer not null, created date not null, created_by varchar2(255 char) not null, @@ -192,6 +201,10 @@ create table dc_project_documents ( references dc_document_types, catalog_id number constraint dc_project_documents_catalog_id_fk references dc_question_catalogs, + status varchar2(20 char) default on null 'PENDING' + constraint dc_project_documents_status_ck + check (status in ('PENDING','IN_PROGRESS','COMPLETED','ERROR')), + progress number default on null 0, original_file blob, original_text clob, translated_text clob, diff --git a/Scripts/ORDS REST Services.sql b/Scripts/ORDS REST Services.sql index c1016eb..24cfb93 100644 --- a/Scripts/ORDS REST Services.sql +++ b/Scripts/ORDS REST Services.sql @@ -170,7 +170,10 @@ BEGIN p.current_phase, p.current_doc_id, p.current_question_id, - p.estimated_completion_at + p.estimated_completion_at, + p.notify_ok, + p.notify_hinweis, + p.notify_abweichend FROM dc_projects p WHERE p.id = :project_id ]' @@ -335,12 +338,67 @@ BEGIN p_pattern => 'projects/:project_id/complete', p_method => 'POST', p_source_type => ORDS.SOURCE_TYPE_PLSQL, - p_comments => 'Setzt status=COMPLETED und erstellt Markdown-Bericht', + p_comments => 'Setzt status=COMPLETED, speichert Java-Markdown, sendet pro Dokument ein PDF per E-Mail', p_source => q'[ DECLARE - v_status dc_projects.status%TYPE; - v_now DATE := SYSDATE; + v_status dc_projects.status%TYPE; + v_now DATE := SYSDATE; + v_report_md CLOB; + v_doc_pdfs JSON_ARRAY_T; + + -- Konvertiert den BLOB-Request-Body (UTF-8 JSON) in einen CLOB + FUNCTION body_to_clob(p_blob IN BLOB) RETURN CLOB IS + v_clob CLOB; + v_dest INTEGER := 1; + v_src INTEGER := 1; + v_lang INTEGER := DBMS_LOB.DEFAULT_LANG_CTX; + v_warning INTEGER; + BEGIN + IF p_blob IS NULL OR DBMS_LOB.GETLENGTH(p_blob) = 0 THEN RETURN NULL; END IF; + DBMS_LOB.CREATETEMPORARY(v_clob, TRUE); + DBMS_LOB.CONVERTTOCLOB(v_clob, p_blob, DBMS_LOB.LOBMAXSIZE, + v_dest, v_src, NLS_CHARSET_ID('AL32UTF8'), v_lang, v_warning); + RETURN v_clob; + END body_to_clob; + + -- Dekodiert einen Base64-CLOB in einen BLOB (chunk-weise, 24000 Zeichen pro Schritt) + FUNCTION base64_to_blob(p_b64 IN CLOB) RETURN BLOB IS + v_blob BLOB; + v_raw RAW(32767); + v_len INTEGER; + v_chunk INTEGER := 24000; + v_offset INTEGER := 1; + v_sub VARCHAR2(32767); + BEGIN + DBMS_LOB.CREATETEMPORARY(v_blob, TRUE); + v_len := DBMS_LOB.GETLENGTH(p_b64); + WHILE v_offset <= v_len LOOP + v_sub := DBMS_LOB.SUBSTR(p_b64, v_chunk, v_offset); + v_raw := UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW(v_sub)); + DBMS_LOB.WRITEAPPEND(v_blob, UTL_RAW.LENGTH(v_raw), v_raw); + v_offset := v_offset + v_chunk; + END LOOP; + RETURN v_blob; + END base64_to_blob; + BEGIN + -- JSON-Body parsen: report_markdown + document_pdfs (Array) + DECLARE + v_body_clob CLOB; + v_json JSON_OBJECT_T; + BEGIN + v_body_clob := body_to_clob(:body); + IF v_body_clob IS NOT NULL THEN + v_json := JSON_OBJECT_T.parse(v_body_clob); + v_report_md := v_json.get_clob('report_markdown'); + BEGIN + v_doc_pdfs := v_json.get_array('document_pdfs'); + EXCEPTION WHEN OTHERS THEN v_doc_pdfs := NULL; END; + DBMS_LOB.FREETEMPORARY(v_body_clob); + END IF; + EXCEPTION WHEN OTHERS THEN NULL; + END; + SELECT status INTO v_status FROM dc_projects @@ -354,20 +412,114 @@ BEGIN END IF; UPDATE dc_projects - SET status = 'COMPLETED', - completed_at = v_now, - progress = 100, - current_phase = 'COMPLETED', - current_doc_id = NULL, - current_question_id = NULL, + SET status = 'COMPLETED', + completed_at = v_now, + progress = 100, + current_phase = 'COMPLETED', + current_doc_id = NULL, + current_question_id = NULL, estimated_completion_at = NULL WHERE id = :project_id; - -- Markdown-Bericht generieren (Fehler ignoriert, Projekt bleibt COMPLETED) - BEGIN - DC_UTILS_PKG.generate_report(:project_id); - EXCEPTION WHEN OTHERS THEN NULL; - END; + IF v_report_md IS NOT NULL AND DBMS_LOB.GETLENGTH(v_report_md) > 0 THEN + -- Kombinierten Markdown-Bericht speichern + UPDATE dc_projects SET report_markdown = v_report_md WHERE id = :project_id; + + -- E-Mail mit je einem PDF-Anhang pro Dokument versenden + BEGIN + DECLARE + v_email dc_projects.notification_email%TYPE; + v_name dc_projects.name%TYPE; + v_done dc_projects.completed_at%TYPE; + v_mail_id NUMBER; + v_ok_cnt NUMBER := 0; + v_warn_cnt NUMBER := 0; + v_dev_cnt NUMBER := 0; + v_total NUMBER := 0; + C_OK CONSTANT VARCHAR2(10 CHAR) := UNISTR('\2705'); + C_NOK CONSTANT VARCHAR2(10 CHAR) := UNISTR('\274C'); + C_WARN CONSTANT VARCHAR2(10 CHAR) := UNISTR('\26A0\FE0F'); + CRLF CONSTANT VARCHAR2(2) := CHR(13)||CHR(10); + v_body VARCHAR2(4000); + v_doc_obj JSON_OBJECT_T; + v_pdf_b64 CLOB; + v_pdf_blob BLOB; + v_fname VARCHAR2(500); + BEGIN + SELECT notification_email, name, completed_at + INTO v_email, v_name, v_done + FROM dc_projects WHERE id = :project_id; + + IF v_email IS NULL OR TRIM(v_email) IS NULL THEN + NULL; -- keine Empfängeradresse → abbrechen + ELSE + SELECT COUNT(*), + SUM(CASE WHEN deviation = 1 THEN 1 ELSE 0 END), + SUM(CASE WHEN warning = 1 AND deviation = 0 THEN 1 ELSE 0 END) + INTO v_total, v_dev_cnt, v_warn_cnt + FROM dc_results WHERE project_id = :project_id; + v_ok_cnt := v_total - v_dev_cnt - v_warn_cnt; + + v_body := + 'Sehr geehrte Damen und Herren,' || CRLF || + CRLF || + 'die automatische Dokumentenprüfung für das Projekt' || CRLF || + '„' || v_name || '"' || CRLF || + 'wurde am ' || TO_CHAR(NVL(v_done, SYSDATE), 'DD.MM.YYYY') || + ' erfolgreich abgeschlossen.' || CRLF || + CRLF || + 'Zusammenfassung der Prüfergebnisse:' || CRLF || + C_OK || ' OK: ' || v_ok_cnt || CRLF || + C_WARN || ' Hinweise: ' || v_warn_cnt || CRLF || + C_NOK || ' Abweichungen: ' || v_dev_cnt || CRLF || + ' Gesamt: ' || v_total || ' Prüfpunkte' || CRLF || + CRLF || + 'Die Prüfberichte der einzelnen Dokumente finden Sie als PDF-Anhänge.' || CRLF || + CRLF || + 'Mit freundlichen Grüßen' || CRLF || + 'Ihr Dokumenten-Check-System' || CRLF || + 'Frigosped Express GmbH'; + + v_mail_id := APEX_MAIL.SEND( + p_to => v_email, + p_from => DC_UTILS_PKG.c_mail_from, + p_subj => 'Prüfbericht: ' || v_name, + p_body => v_body + ); + + -- Einen PDF-Anhang pro Dokument hinzufügen + IF v_doc_pdfs IS NOT NULL AND v_doc_pdfs.get_size > 0 THEN + FOR i IN 0 .. v_doc_pdfs.get_size - 1 LOOP + BEGIN + v_doc_obj := TREAT(v_doc_pdfs.get(i) AS JSON_OBJECT_T); + v_fname := v_doc_obj.get_string('filename'); + v_pdf_b64 := v_doc_obj.get_clob('pdf_base64'); + IF v_pdf_b64 IS NOT NULL AND DBMS_LOB.GETLENGTH(v_pdf_b64) > 0 THEN + v_pdf_blob := base64_to_blob(v_pdf_b64); + APEX_MAIL.ADD_ATTACHMENT( + p_mail_id => v_mail_id, + p_attachment => v_pdf_blob, + p_filename => NVL(v_fname, 'Pruefbericht.pdf'), + p_mime_type => 'application/pdf' + ); + DBMS_LOB.FREETEMPORARY(v_pdf_blob); + DBMS_LOB.FREETEMPORARY(v_pdf_b64); + END IF; + EXCEPTION WHEN OTHERS THEN NULL; + END; + END LOOP; + END IF; + END IF; + EXCEPTION WHEN OTHERS THEN NULL; + END; + END; + ELSE + -- Fallback: Bericht in DB generieren (z.B. bei leerem Body) + BEGIN + DC_UTILS_PKG.generate_report(:project_id); + EXCEPTION WHEN OTHERS THEN NULL; + END; + END IF; :status_code := 200; HTP.P('{"project_id":' || :project_id @@ -446,6 +598,11 @@ BEGIN d.document_type_id, d.catalog_id, d.uploaded_at, + d.status, + d.progress, + d.parent_document_id, + NVL(d.is_reference, 0) AS is_reference, + d.source_url, CASE WHEN d.original_text IS NOT NULL THEN 1 ELSE 0 END AS has_original_text, CASE WHEN d.translated_text IS NOT NULL THEN 1 ELSE 0 END AS has_translated_text, d.row_version, @@ -594,6 +751,67 @@ BEGIN ); + -- =========================================================================== + -- TEMPLATE 9c: documents/:doc_id/progress (PUT) + -- =========================================================================== + ORDS.DEFINE_TEMPLATE( + p_module_name => 'frigosped.dc', + p_pattern => 'documents/:doc_id/progress', + p_priority => 0, + p_comments => 'Dokument-Fortschritt und -Status schreiben' + ); + + -- 9c. PUT /api/dc/documents/:doc_id/progress + -- Body: {"progress":45,"status":"IN_PROGRESS"} + ORDS.DEFINE_HANDLER( + p_module_name => 'frigosped.dc', + p_pattern => 'documents/:doc_id/progress', + p_method => 'PUT', + p_source_type => ORDS.SOURCE_TYPE_PLSQL, + p_comments => 'Setzt progress (0-100) und/oder status des Dokuments', + p_source => q'[ + DECLARE + v_body CLOB; + v_progress NUMBER; + v_status VARCHAR2(20); + v_rows INTEGER; + BEGIN + v_body := :body_text; + v_progress := TO_NUMBER(JSON_VALUE(v_body, '$.progress')); + v_status := JSON_VALUE(v_body, '$.status'); + + IF v_progress IS NOT NULL AND (v_progress < 0 OR v_progress > 100) THEN + :status_code := 400; + HTP.P('{"error":"progress muss zwischen 0 und 100 liegen"}'); + RETURN; + END IF; + + UPDATE dc_project_documents + SET progress = CASE WHEN JSON_EXISTS(v_body, '$.progress') THEN v_progress ELSE progress END, + status = CASE WHEN JSON_EXISTS(v_body, '$.status') THEN v_status ELSE status END + WHERE id = :doc_id; + + v_rows := SQL%ROWCOUNT; + IF v_rows = 0 THEN + :status_code := 404; + HTP.P('{"error":"Dokument nicht gefunden","doc_id":' || :doc_id || '}'); + RETURN; + END IF; + + :status_code := 200; + HTP.P('{"doc_id":' || :doc_id || ',"progress":' || NVL(v_progress, -1) + || ',"status":"' || NVL(v_status, '') || '"}'); + + EXCEPTION + WHEN OTHERS THEN + ROLLBACK; + :status_code := 500; + HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}'); + END; + ]' + ); + + -- =========================================================================== -- TEMPLATE 10+11: projects/:project_id/results -- (POST = Einfuegen, DELETE = Alle loeschen) @@ -1143,7 +1361,7 @@ BEGIN || ',"cost_eur":' || ROUND(NVL(v_month_eur, 0), 6) || ',"total_tokens":'|| NVL(v_month_tokens, 0) || ',"call_count":' || NVL(v_month_calls, 0) - || '},"monthly":' || NVL(v_monthly_json, '[]') + || '},"monthly":' || NVL(v_monthly_json, '[' || CHR(93)) || '}' ); @@ -1156,6 +1374,196 @@ BEGIN ); + -- =========================================================================== + -- TEMPLATE 18: documents/:parent_id/references (POST) + -- Legt eine heruntergeladene externe Referenz als neue dc_project_documents- + -- Zeile an (is_reference=1, verknuepft mit dem Originaldokument). + -- =========================================================================== + ORDS.DEFINE_TEMPLATE( + p_module_name => 'frigosped.dc', + p_pattern => 'documents/:parent_id/references', + p_priority => 0, + p_comments => 'Heruntergeladene externe Referenz zu einem Dokument speichern' + ); + + -- 18. POST /api/dc/documents/:parent_id/references + -- Body: {"source_url":"https://...","filename":"AGB.pdf", + -- "mime_type":"application/pdf","file_base64":"JVBERi0x..."} + ORDS.DEFINE_HANDLER( + p_module_name => 'frigosped.dc', + p_pattern => 'documents/:parent_id/references', + p_method => 'POST', + p_source_type => ORDS.SOURCE_TYPE_PLSQL, + p_comments => '201 Created mit neuer doc-id; legt is_reference=1-Zeile mit BLOB an', + p_source => q'[ + DECLARE + v_body_clob CLOB; + v_json JSON_OBJECT_T; + v_source_url VARCHAR2(2000); + v_filename VARCHAR2(255); + v_mime VARCHAR2(100); + v_file_b64 CLOB; + v_blob BLOB; + v_project_id NUMBER; + v_doc_type_id NUMBER; + v_new_id NUMBER; + + -- Wandelt den (ggf. grossen) Request-BLOB in einen CLOB + FUNCTION body_to_clob(p_body IN BLOB) RETURN CLOB IS + v_clob CLOB; + v_dest INTEGER := 1; + v_src INTEGER := 1; + v_lang INTEGER := 0; + v_warning INTEGER := 0; + BEGIN + IF p_body IS NULL OR DBMS_LOB.GETLENGTH(p_body) = 0 THEN + RETURN NULL; + END IF; + DBMS_LOB.CREATETEMPORARY(v_clob, TRUE); + DBMS_LOB.CONVERTTOCLOB(v_clob, p_body, DBMS_LOB.LOBMAXSIZE, + v_dest, v_src, NLS_CHARSET_ID('AL32UTF8'), v_lang, v_warning); + RETURN v_clob; + END body_to_clob; + + -- Dekodiert einen Base64-CLOB in einen BLOB (chunk-weise) + FUNCTION base64_to_blob(p_b64 IN CLOB) RETURN BLOB IS + v_blob BLOB; + v_raw RAW(32767); + v_chunk INTEGER := 24000; + v_offset INTEGER := 1; + v_len INTEGER; + v_sub VARCHAR2(32767); + BEGIN + DBMS_LOB.CREATETEMPORARY(v_blob, TRUE); + v_len := DBMS_LOB.GETLENGTH(p_b64); + WHILE v_offset <= v_len LOOP + v_sub := DBMS_LOB.SUBSTR(p_b64, v_chunk, v_offset); + v_raw := UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW(v_sub)); + DBMS_LOB.WRITEAPPEND(v_blob, UTL_RAW.LENGTH(v_raw), v_raw); + v_offset := v_offset + v_chunk; + END LOOP; + RETURN v_blob; + END base64_to_blob; + + BEGIN + v_body_clob := body_to_clob(:body); + IF v_body_clob IS NULL THEN + :status_code := 400; + HTP.P('{"error":"Leerer Request-Body"}'); + RETURN; + END IF; + + v_json := JSON_OBJECT_T.parse(v_body_clob); + v_source_url := v_json.get_string('source_url'); + v_filename := v_json.get_string('filename'); + v_mime := v_json.get_string('mime_type'); + BEGIN + v_file_b64 := v_json.get_clob('file_base64'); + EXCEPTION WHEN OTHERS THEN v_file_b64 := NULL; END; + DBMS_LOB.FREETEMPORARY(v_body_clob); + + IF v_file_b64 IS NULL OR DBMS_LOB.GETLENGTH(v_file_b64) = 0 THEN + :status_code := 400; + HTP.P('{"error":"file_base64 ist Pflichtfeld"}'); + RETURN; + END IF; + + -- Projekt + Dokumenttyp vom Originaldokument uebernehmen + SELECT project_id, document_type_id + INTO v_project_id, v_doc_type_id + FROM dc_project_documents + WHERE id = :parent_id; + + v_blob := base64_to_blob(v_file_b64); + + INSERT INTO dc_project_documents ( + project_id, + document_type_id, + parent_document_id, + is_reference, + source_url, + original_file, + mime_type, + filename, + status, + progress, + uploaded_at + ) VALUES ( + v_project_id, + v_doc_type_id, + :parent_id, + 1, + v_source_url, + v_blob, + NVL(v_mime, 'application/octet-stream'), + NVL(v_filename, 'referenz'), + 'IN_PROGRESS', + 0, + SYSDATE + ) + RETURNING id INTO v_new_id; + + :status_code := 201; + HTP.P('{"id":' || v_new_id + || ',"parent_document_id":' || :parent_id + || ',"project_id":' || v_project_id || '}'); + + EXCEPTION + WHEN NO_DATA_FOUND THEN + ROLLBACK; + :status_code := 404; + HTP.P('{"error":"Originaldokument nicht gefunden","parent_id":' || :parent_id || '}'); + WHEN OTHERS THEN + ROLLBACK; + :status_code := 500; + HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}'); + END; + ]' + ); + + + -- =========================================================================== + -- TEMPLATE 19: projects/:project_id/references (DELETE) + -- Loescht alle heruntergeladenen Referenzen eines Projekts (Re-Processing). + -- =========================================================================== + ORDS.DEFINE_TEMPLATE( + p_module_name => 'frigosped.dc', + p_pattern => 'projects/:project_id/references', + p_priority => 0, + p_comments => 'Heruntergeladene Referenzen eines Projekts verwalten' + ); + + -- 19. DELETE /api/dc/projects/:project_id/references + ORDS.DEFINE_HANDLER( + p_module_name => 'frigosped.dc', + p_pattern => 'projects/:project_id/references', + p_method => 'DELETE', + p_source_type => ORDS.SOURCE_TYPE_PLSQL, + p_comments => 'Loescht alle is_reference=1-Zeilen eines Projekts (fuer Re-Processing)', + p_source => q'[ + DECLARE + v_deleted INTEGER; + BEGIN + DELETE FROM dc_project_documents + WHERE project_id = :project_id + AND is_reference = 1; + + v_deleted := SQL%ROWCOUNT; + + :status_code := 200; + HTP.P('{"project_id":' || :project_id + || ',"deleted_count":' || v_deleted || '}'); + + EXCEPTION + WHEN OTHERS THEN + ROLLBACK; + :status_code := 500; + HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}'); + END; + ]' + ); + + COMMIT; END; / diff --git a/Scripts/deploy_complete_handler.sql b/Scripts/deploy_complete_handler.sql new file mode 100644 index 0000000..a55c422 --- /dev/null +++ b/Scripts/deploy_complete_handler.sql @@ -0,0 +1,217 @@ +-- ============================================================================= +-- Deploys only the POST /api/dc/projects/:project_id/complete handler. +-- Uses CLOB construction to avoid SQLcl bind-variable pre-processing of +-- :status_code / :body / :project_id inside the handler source string. +-- ============================================================================= +DECLARE + v_src CLOB; + + PROCEDURE a(p IN VARCHAR2) IS + BEGIN DBMS_LOB.APPEND(v_src, TO_CLOB(p)); END; + +BEGIN + DBMS_LOB.CREATETEMPORARY(v_src, TRUE); + + a(' DECLARE'); + a(CHR(10)||' v_status dc_projects.status%TYPE;'); + a(CHR(10)||' v_now DATE := SYSDATE;'); + a(CHR(10)||' v_report_md CLOB;'); + a(CHR(10)||' v_doc_pdfs JSON_ARRAY_T;'); + a(CHR(10)); + a(CHR(10)||' FUNCTION body_to_clob(p_blob IN BLOB) RETURN CLOB IS'); + a(CHR(10)||' v_clob CLOB;'); + a(CHR(10)||' v_dest INTEGER := 1;'); + a(CHR(10)||' v_src INTEGER := 1;'); + a(CHR(10)||' v_lang INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;'); + a(CHR(10)||' v_warning INTEGER;'); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' IF p_blob IS NULL OR DBMS_LOB.GETLENGTH(p_blob) = 0 THEN RETURN NULL; END IF;'); + a(CHR(10)||' DBMS_LOB.CREATETEMPORARY(v_clob, TRUE);'); + a(CHR(10)||' DBMS_LOB.CONVERTTOCLOB(v_clob, p_blob, DBMS_LOB.LOBMAXSIZE,'); + a(CHR(10)||' v_dest, v_src, NLS_CHARSET_ID(''AL32UTF8''), v_lang, v_warning);'); + a(CHR(10)||' RETURN v_clob;'); + a(CHR(10)||' END body_to_clob;'); + a(CHR(10)); + a(CHR(10)||' FUNCTION base64_to_blob(p_b64 IN CLOB) RETURN BLOB IS'); + a(CHR(10)||' v_blob BLOB;'); + a(CHR(10)||' v_raw RAW(32767);'); + a(CHR(10)||' v_len INTEGER;'); + a(CHR(10)||' v_chunk INTEGER := 24000;'); + a(CHR(10)||' v_offset INTEGER := 1;'); + a(CHR(10)||' v_sub VARCHAR2(32767);'); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' DBMS_LOB.CREATETEMPORARY(v_blob, TRUE);'); + a(CHR(10)||' v_len := DBMS_LOB.GETLENGTH(p_b64);'); + a(CHR(10)||' WHILE v_offset <= v_len LOOP'); + a(CHR(10)||' v_sub := DBMS_LOB.SUBSTR(p_b64, v_chunk, v_offset);'); + a(CHR(10)||' v_raw := UTL_ENCODE.BASE64_DECODE(UTL_RAW.CAST_TO_RAW(v_sub));'); + a(CHR(10)||' DBMS_LOB.WRITEAPPEND(v_blob, UTL_RAW.LENGTH(v_raw), v_raw);'); + a(CHR(10)||' v_offset := v_offset + v_chunk;'); + a(CHR(10)||' END LOOP;'); + a(CHR(10)||' RETURN v_blob;'); + a(CHR(10)||' END base64_to_blob;'); + a(CHR(10)); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' DECLARE'); + a(CHR(10)||' v_body_clob CLOB;'); + a(CHR(10)||' v_json JSON_OBJECT_T;'); + a(CHR(10)||' BEGIN'); + -- Use concatenation to embed the bind variable names so SQLcl doesn't pre-process them + a(CHR(10)||' v_body_clob := body_to_clob(' || CHR(58) || 'body);'); + a(CHR(10)||' IF v_body_clob IS NOT NULL THEN'); + a(CHR(10)||' v_json := JSON_OBJECT_T.parse(v_body_clob);'); + a(CHR(10)||' v_report_md := v_json.get_clob(''report_markdown'');'); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' v_doc_pdfs := v_json.get_array(''document_pdfs'');'); + a(CHR(10)||' EXCEPTION WHEN OTHERS THEN v_doc_pdfs := NULL; END;'); + a(CHR(10)||' DBMS_LOB.FREETEMPORARY(v_body_clob);'); + a(CHR(10)||' END IF;'); + a(CHR(10)||' EXCEPTION WHEN OTHERS THEN NULL;'); + a(CHR(10)||' END;'); + a(CHR(10)); + a(CHR(10)||' SELECT status'); + a(CHR(10)||' INTO v_status'); + a(CHR(10)||' FROM dc_projects'); + a(CHR(10)||' WHERE id = ' || CHR(58) || 'project_id'); + a(CHR(10)||' FOR UPDATE NOWAIT;'); + a(CHR(10)); + a(CHR(10)||' IF v_status = ''COMPLETED'' THEN'); + a(CHR(10)||' ' || CHR(58) || 'status_code := 409;'); + a(CHR(10)||' HTP.P(''{"error":"Projekt ist bereits COMPLETED","project_id":'' || ' || CHR(58) || 'project_id || ''}'');'); + a(CHR(10)||' RETURN;'); + a(CHR(10)||' END IF;'); + a(CHR(10)); + a(CHR(10)||' UPDATE dc_projects'); + a(CHR(10)||' SET status = ''COMPLETED'','); + a(CHR(10)||' completed_at = v_now,'); + a(CHR(10)||' progress = 100,'); + a(CHR(10)||' current_phase = ''COMPLETED'','); + a(CHR(10)||' current_doc_id = NULL,'); + a(CHR(10)||' current_question_id = NULL,'); + a(CHR(10)||' estimated_completion_at = NULL'); + a(CHR(10)||' WHERE id = ' || CHR(58) || 'project_id;'); + a(CHR(10)); + a(CHR(10)||' IF v_report_md IS NOT NULL AND DBMS_LOB.GETLENGTH(v_report_md) > 0 THEN'); + a(CHR(10)||' UPDATE dc_projects SET report_markdown = v_report_md WHERE id = ' || CHR(58) || 'project_id;'); + a(CHR(10)); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' DECLARE'); + a(CHR(10)||' v_email dc_projects.notification_email%TYPE;'); + a(CHR(10)||' v_name dc_projects.name%TYPE;'); + a(CHR(10)||' v_done dc_projects.completed_at%TYPE;'); + a(CHR(10)||' v_mail_id NUMBER;'); + a(CHR(10)||' v_ok_cnt NUMBER := 0;'); + a(CHR(10)||' v_warn_cnt NUMBER := 0;'); + a(CHR(10)||' v_dev_cnt NUMBER := 0;'); + a(CHR(10)||' v_total NUMBER := 0;'); + a(CHR(10)||' C_OK CONSTANT VARCHAR2(10 CHAR) := UNISTR(''\2705'');'); + a(CHR(10)||' C_NOK CONSTANT VARCHAR2(10 CHAR) := UNISTR(''\274C'');'); + a(CHR(10)||' C_WARN CONSTANT VARCHAR2(10 CHAR) := UNISTR(''\26A0\FE0F'');'); + a(CHR(10)||' CRLF CONSTANT VARCHAR2(2) := CHR(13)||CHR(10);'); + a(CHR(10)||' v_body VARCHAR2(4000);'); + a(CHR(10)||' v_doc_obj JSON_OBJECT_T;'); + a(CHR(10)||' v_pdf_b64 CLOB;'); + a(CHR(10)||' v_pdf_blob BLOB;'); + a(CHR(10)||' v_fname VARCHAR2(500);'); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' SELECT notification_email, name, completed_at'); + a(CHR(10)||' INTO v_email, v_name, v_done'); + a(CHR(10)||' FROM dc_projects WHERE id = ' || CHR(58) || 'project_id;'); + a(CHR(10)); + a(CHR(10)||' IF v_email IS NOT NULL AND TRIM(v_email) IS NOT NULL THEN'); + a(CHR(10)||' SELECT COUNT(*),'); + a(CHR(10)||' SUM(CASE WHEN deviation = 1 THEN 1 ELSE 0 END),'); + a(CHR(10)||' SUM(CASE WHEN warning = 1 AND deviation = 0 THEN 1 ELSE 0 END)'); + a(CHR(10)||' INTO v_total, v_dev_cnt, v_warn_cnt'); + a(CHR(10)||' FROM dc_results WHERE project_id = ' || CHR(58) || 'project_id;'); + a(CHR(10)||' v_ok_cnt := v_total - v_dev_cnt - v_warn_cnt;'); + a(CHR(10)); + a(CHR(10)||' v_body :='); + a(CHR(10)||' ''Sehr geehrte Damen und Herren,'' || CRLF ||'); + a(CHR(10)||' CRLF ||'); + a(CHR(10)||' ''die automatische Dokumentenprüfung für das Projekt'' || CRLF ||'); + a(CHR(10)||' ''„'' || v_name || ''"'' || CRLF ||'); + a(CHR(10)||' ''wurde am '' || TO_CHAR(NVL(v_done, SYSDATE), ''DD.MM.YYYY'') ||'); + a(CHR(10)||' '' erfolgreich abgeschlossen.'' || CRLF ||'); + a(CHR(10)||' CRLF ||'); + a(CHR(10)||' ''Zusammenfassung der Prüfergebnisse:'' || CRLF ||'); + a(CHR(10)||' C_OK || '' OK: '' || v_ok_cnt || CRLF ||'); + a(CHR(10)||' C_WARN || '' Hinweise: '' || v_warn_cnt || CRLF ||'); + a(CHR(10)||' C_NOK || '' Abweichungen: '' || v_dev_cnt || CRLF ||'); + a(CHR(10)||' '' Gesamt: '' || v_total || '' Prüfpunkte'' || CRLF ||'); + a(CHR(10)||' CRLF ||'); + a(CHR(10)||' ''Die Prüfberichte der einzelnen Dokumente finden Sie als PDF-Anhänge.'' || CRLF ||'); + a(CHR(10)||' CRLF ||'); + a(CHR(10)||' ''Mit freundlichen Grüßen'' || CRLF ||'); + a(CHR(10)||' ''Ihr Dokumenten-Check-System'' || CRLF ||'); + a(CHR(10)||' ''Frigosped Express GmbH'';'); + a(CHR(10)); + a(CHR(10)||' v_mail_id := APEX_MAIL.SEND('); + a(CHR(10)||' p_to => v_email,'); + a(CHR(10)||' p_from => DC_UTILS_PKG.c_mail_from,'); + a(CHR(10)||' p_subj => ''Prüfbericht: '' || v_name,'); + a(CHR(10)||' p_body => v_body'); + a(CHR(10)||' );'); + a(CHR(10)); + a(CHR(10)||' IF v_doc_pdfs IS NOT NULL AND v_doc_pdfs.get_size > 0 THEN'); + a(CHR(10)||' FOR i IN 0 .. v_doc_pdfs.get_size - 1 LOOP'); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' v_doc_obj := TREAT(v_doc_pdfs.get(i) AS JSON_OBJECT_T);'); + a(CHR(10)||' v_fname := v_doc_obj.get_string(''filename'');'); + a(CHR(10)||' v_pdf_b64 := v_doc_obj.get_clob(''pdf_base64'');'); + a(CHR(10)||' IF v_pdf_b64 IS NOT NULL AND DBMS_LOB.GETLENGTH(v_pdf_b64) > 0 THEN'); + a(CHR(10)||' v_pdf_blob := base64_to_blob(v_pdf_b64);'); + a(CHR(10)||' APEX_MAIL.ADD_ATTACHMENT('); + a(CHR(10)||' p_mail_id => v_mail_id,'); + a(CHR(10)||' p_attachment => v_pdf_blob,'); + a(CHR(10)||' p_filename => NVL(v_fname, ''Pruefbericht.pdf''),'); + a(CHR(10)||' p_mime_type => ''application/pdf'''); + a(CHR(10)||' );'); + a(CHR(10)||' DBMS_LOB.FREETEMPORARY(v_pdf_blob);'); + a(CHR(10)||' DBMS_LOB.FREETEMPORARY(v_pdf_b64);'); + a(CHR(10)||' END IF;'); + a(CHR(10)||' EXCEPTION WHEN OTHERS THEN NULL;'); + a(CHR(10)||' END;'); + a(CHR(10)||' END LOOP;'); + a(CHR(10)||' END IF;'); + a(CHR(10)||' END IF;'); + a(CHR(10)||' EXCEPTION WHEN OTHERS THEN NULL;'); + a(CHR(10)||' END;'); + a(CHR(10)||' END;'); + a(CHR(10)||' ELSE'); + a(CHR(10)||' BEGIN'); + a(CHR(10)||' DC_UTILS_PKG.generate_report(' || CHR(58) || 'project_id);'); + a(CHR(10)||' EXCEPTION WHEN OTHERS THEN NULL;'); + a(CHR(10)||' END;'); + a(CHR(10)||' END IF;'); + a(CHR(10)); + a(CHR(10)||' ' || CHR(58) || 'status_code := 200;'); + a(CHR(10)||' HTP.P(''{"project_id":'' || ' || CHR(58) || 'project_id'); + a(CHR(10)||' || '',\"status\":\"COMPLETED\"'''); + a(CHR(10)||' || '',\"completed_at\":\"'' || TO_CHAR(v_now, ''YYYY-MM-DD"T"HH24:MI:SS'') || ''\"'''); + a(CHR(10)||' || ''}'');'); + a(CHR(10)); + a(CHR(10)||' EXCEPTION'); + a(CHR(10)||' WHEN NO_DATA_FOUND THEN'); + a(CHR(10)||' ' || CHR(58) || 'status_code := 404;'); + a(CHR(10)||' HTP.P(''{"error":"Projekt nicht gefunden","project_id":'' || ' || CHR(58) || 'project_id || ''}'');'); + a(CHR(10)||' WHEN OTHERS THEN'); + a(CHR(10)||' ROLLBACK;'); + a(CHR(10)||' ' || CHR(58) || 'status_code := 500;'); + a(CHR(10)||' HTP.P(''{"error":"'' || REPLACE(SQLERRM, ''"'', '''''''') || ''"}'');'); + a(CHR(10)||' END;'); + + ORDS.DEFINE_HANDLER( + p_module_name => 'frigosped.dc', + p_pattern => 'projects/:project_id/complete', + p_method => 'POST', + p_source_type => ORDS.SOURCE_TYPE_PLSQL, + p_comments => 'Setzt status=COMPLETED, sendet pro Dokument ein PDF per E-Mail', + p_source => v_src + ); + + DBMS_LOB.FREETEMPORARY(v_src); + COMMIT; + DBMS_OUTPUT.PUT_LINE('Handler erfolgreich deployed.'); +END; +/ diff --git a/dc-backend/Dockerfile b/dc-backend/Dockerfile index e8777a0..7084f97 100644 --- a/dc-backend/Dockerfile +++ b/dc-backend/Dockerfile @@ -1,7 +1,7 @@ # ============================================================================= # Stage 1: Build # ============================================================================= -FROM maven:3.9-eclipse-temurin-21 AS build +FROM docker.io/library/maven:3.9-eclipse-temurin-21 AS build WORKDIR /build @@ -15,7 +15,7 @@ RUN mvn package -DskipTests -q # ============================================================================= # Stage 2: Runtime # ============================================================================= -FROM eclipse-temurin:21-jre +FROM docker.io/library/eclipse-temurin:21-jre WORKDIR /app diff --git a/dc-backend/build-and-push.sh b/dc-backend/build-and-push.sh old mode 100644 new mode 100755 diff --git a/dc-backend/follow_logs.sh b/dc-backend/follow_logs.sh new file mode 100755 index 0000000..1aed5a2 --- /dev/null +++ b/dc-backend/follow_logs.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(pwd)" +export KUBECONFIG="$SCRIPT_DIR/../env/frigo-dev.yaml" + +POD=$(kubectl get pod -n ai-env -l app=dc-backend \ + --field-selector=status.phase=Running \ + -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) + +if [ -z "$POD" ]; then + echo "Kein laufender dc-backend Pod gefunden. Alle Pods:" + kubectl get pods -n ai-env -l app=dc-backend + exit 1 +fi + +echo "=== Logs: $POD ===" +kubectl logs "$POD" -n ai-env --tail $1 +while true; do kubectl logs -f "$POD" -n ai-env --tail 0 && break || sleep 2; done +kubectl logs -f "$POD" -n ai-env --tail $1 diff --git a/dc-backend/getPods.sh b/dc-backend/getPods.sh old mode 100644 new mode 100755 diff --git a/dc-backend/k8s/deployment.yaml b/dc-backend/k8s/deployment.yaml index c09ff3e..e5c1c69 100644 --- a/dc-backend/k8s/deployment.yaml +++ b/dc-backend/k8s/deployment.yaml @@ -66,10 +66,10 @@ spec: periodSeconds: 10 resources: requests: - memory: "256Mi" + memory: "512Mi" cpu: "100m" limits: - memory: "512Mi" + memory: "2Gi" cpu: "500m" --- apiVersion: v1 diff --git a/dc-backend/logs.sh b/dc-backend/logs.sh old mode 100644 new mode 100755 diff --git a/dc-backend/pod-ram-usage.sh b/dc-backend/pod-ram-usage.sh new file mode 100755 index 0000000..a299b74 --- /dev/null +++ b/dc-backend/pod-ram-usage.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# pod-ram-usage.sh — show RAM usage by pod, with optional namespace / sorting flags +# +# Usage: +# ./pod-ram-usage.sh # all namespaces +# ./pod-ram-usage.sh -n my-namespace # specific namespace +# ./pod-ram-usage.sh -s # sort by usage (highest first) +# ./pod-ram-usage.sh -n kube-system -s # combine flags +# ./pod-ram-usage.sh -w 5 # watch mode, refresh every 5 seconds +# ./pod-ram-usage.sh -h # show help + +set -euo pipefail + +# ── defaults ──────────────────────────────────────────────────────────────── +NAMESPACE="" +SORT=false +WATCH_INTERVAL=0 # 0 = run once + +# ── helpers ───────────────────────────────────────────────────────────────── +usage() { + grep '^#' "$0" | grep -v '#!/' | sed 's/^# \{0,2\}//' + exit 0 +} + +die() { echo "ERROR: $*" >&2; exit 1; } + +check_deps() { + command -v kubectl >/dev/null 2>&1 || die "kubectl not found in PATH" + # metrics-server check (soft): warn rather than abort + if ! kubectl top pod --all-namespaces >/dev/null 2>&1; then + echo "WARNING: 'kubectl top' failed — is metrics-server installed and ready?" >&2 + echo " Install it with:" >&2 + echo " kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml" >&2 + exit 1 + fi +} + +# Convert Mi/Ki/Gi strings to MiB float for sorting +to_mib() { + local val="$1" + if [[ "$val" =~ ^([0-9]+(\.[0-9]+)?)Gi$ ]]; then echo "${BASH_REMATCH[1]} * 1024" | bc + elif [[ "$val" =~ ^([0-9]+(\.[0-9]+)?)Mi$ ]]; then echo "${BASH_REMATCH[1]}" + elif [[ "$val" =~ ^([0-9]+(\.[0-9]+)?)Ki$ ]]; then echo "scale=2; ${BASH_REMATCH[1]} / 1024" | bc + elif [[ "$val" =~ ^([0-9]+(\.[0-9]+)?)m$ ]]; then echo "0" # millicores — shouldn't appear for RAM + else echo "0" + fi +} + +# ── parse args ─────────────────────────────────────────────────────────────── +while getopts ":n:sw:h" opt; do + case $opt in + n) NAMESPACE="$OPTARG" ;; + s) SORT=true ;; + w) WATCH_INTERVAL="$OPTARG" ;; + h) usage ;; + :) die "Option -$OPTARG requires an argument." ;; + \?) die "Unknown option -$OPTARG" ;; + esac +done + +# ── main logic ─────────────────────────────────────────────────────────────── +run_once() { + # build kubectl top args + local top_args=("top" "pod") + if [[ -n "$NAMESPACE" ]]; then + top_args+=("-n" "$NAMESPACE") + else + top_args+=("--all-namespaces") + fi + top_args+=("--no-headers") + + # fetch raw data + local raw + raw=$(kubectl "${top_args[@]}" 2>/dev/null) || { + echo "ERROR: kubectl top pod failed." >&2; return 1 + } + + # also get resource requests/limits for context + local req_args=("get" "pods") + if [[ -n "$NAMESPACE" ]]; then + req_args+=("-n" "$NAMESPACE") + else + req_args+=("--all-namespaces") + fi + req_args+=("-o" "custom-columns=\ +NS:.metadata.namespace,\ +NAME:.metadata.name,\ +MEM_REQ:.spec.containers[*].resources.requests.memory,\ +MEM_LIM:.spec.containers[*].resources.limits.memory" \ + "--no-headers") + + local limits + limits=$(kubectl "${req_args[@]}" 2>/dev/null) || limits="" + + # ── header ────────────────────────────────────────────────────────────── + local ns_label="ALL NAMESPACES" + [[ -n "$NAMESPACE" ]] && ns_label="$NAMESPACE" + echo "" + echo "═══════════════════════════════════════════════════════════════════════" + printf " RAM Usage by Pod — namespace: %-35s\n" "$ns_label" + printf " %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" + echo "═══════════════════════════════════════════════════════════════════════" + printf "%-20s %-42s %10s %10s %10s\n" \ + "NAMESPACE" "POD" "MEMORY" "REQUEST" "LIMIT" + echo "───────────────────────────────────────────────────────────────────────" + + # ── build output rows ──────────────────────────────────────────────────── + declare -a rows + while IFS= read -r line; do + [[ -z "$line" ]] && continue + + # kubectl top --all-namespaces: NAMESPACE POD CPU MEM + # kubectl top -n NS: POD CPU MEM + if [[ -n "$NAMESPACE" ]]; then + local pod cpu mem + read -r pod cpu mem <<< "$line" + local ns="$NAMESPACE" + else + local ns pod cpu mem + read -r ns pod cpu mem <<< "$line" + fi + + # look up request / limit from the limits table + local req lim + req=$(echo "$limits" | awk -v n="$ns" -v p="$pod" '$1==n && $2==p {print $3}' | head -1) + lim=$(echo "$limits" | awk -v n="$ns" -v p="$pod" '$1==n && $2==p {print $4}' | head -1) + [[ -z "$req" || "$req" == "" ]] && req="—" + [[ -z "$lim" || "$lim" == "" ]] && lim="—" + + # for sort key convert mem to MiB numeric + local sort_key + sort_key=$(to_mib "$mem") + + rows+=("$(printf '%020.4f\t%-20s\t%-42s\t%10s\t%10s\t%10s' \ + "$sort_key" "$ns" "$pod" "$mem" "$req" "$lim")") + done <<< "$raw" + + # ── sort if requested ──────────────────────────────────────────────────── + if $SORT; then + IFS=$'\n' sorted=($(printf '%s\n' "${rows[@]}" | sort -t$'\t' -k1 -rn)) + else + IFS=$'\n' sorted=("${rows[@]}") + fi + + # ── print rows ─────────────────────────────────────────────────────────── + local total_mib=0 + local count=0 + for row in "${sorted[@]}"; do + local sort_key ns pod mem req lim + IFS=$'\t' read -r sort_key ns pod mem req lim <<< "$row" + printf "%-20s %-42s %10s %10s %10s\n" "$ns" "$pod" "$mem" "$req" "$lim" + total_mib=$(echo "$total_mib + $sort_key" | bc) + (( count++ )) || true + done + + echo "───────────────────────────────────────────────────────────────────────" + printf " %d pod(s) Total usage: %.0f MiB\n" "$count" "$total_mib" + echo "═══════════════════════════════════════════════════════════════════════" + echo "" +} + +# ── entry point ────────────────────────────────────────────────────────────── +check_deps + +if [[ "$WATCH_INTERVAL" -gt 0 ]]; then + echo "Watch mode — refreshing every ${WATCH_INTERVAL}s (Ctrl-C to stop)" + while true; do + clear + run_once + sleep "$WATCH_INTERVAL" + done +else + run_once +fi \ No newline at end of file diff --git a/dc-backend/pom.xml b/dc-backend/pom.xml index 92c5416..1b9abbf 100644 --- a/dc-backend/pom.xml +++ b/dc-backend/pom.xml @@ -90,6 +90,13 @@ 3.0.3 + + + com.github.librepdf + openpdf + 1.3.43 + + org.apache.poi @@ -104,6 +111,13 @@ 0.12.0 + + + org.jsoup + jsoup + 1.18.1 + + diff --git a/dc-backend/src/main/java/de/frigosped/dc/client/MistralOcrClient.java b/dc-backend/src/main/java/de/frigosped/dc/client/MistralOcrClient.java new file mode 100644 index 0000000..6014374 --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/client/MistralOcrClient.java @@ -0,0 +1,19 @@ +package de.frigosped.dc.client; + +import de.frigosped.dc.model.MistralOcrRequest; +import de.frigosped.dc.model.MistralOcrResponse; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "mistral-ocr") +@Path("/v1") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public interface MistralOcrClient { + + @POST + @Path("/ocr") + MistralOcrResponse ocr(@HeaderParam("Authorization") String authorization, + MistralOcrRequest request); +} \ No newline at end of file diff --git a/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java b/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java index f7b5550..9dae259 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java +++ b/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java @@ -116,10 +116,12 @@ public interface OrdsClient { /** * POST /api/dc/projects/:projectId/complete * Setzt Status auf COMPLETED + completed_at = SYSDATE. + * Body enthält optional das vorberechnete report_markdown. */ @POST @Path("/projects/{projectId}/complete") - Response completeProject(@PathParam("projectId") long projectId); + Response completeProject(@PathParam("projectId") long projectId, + CompleteProjectRequest body); // ========================================================================= // Dokumente @@ -159,6 +161,31 @@ public interface OrdsClient { @Path("/documents/{docId}/texts") Response updateTexts(@PathParam("docId") long docId, TextsRequest body); + /** + * PUT /api/dc/documents/:docId/progress + * Setzt status und/oder progress (0-100) eines Dokuments. + */ + @PUT + @Path("/documents/{docId}/progress") + Response updateDocumentProgress(@PathParam("docId") long docId, DocumentProgressRequest body); + + /** + * POST /api/dc/documents/:parentId/references + * Legt eine heruntergeladene externe Referenz als neue dc_project_documents-Zeile an. + * Antwort: 201 Created, Body {"id": , ...} + */ + @POST + @Path("/documents/{parentId}/references") + Response insertReference(@PathParam("parentId") long parentId, ReferenceCreateRequest body); + + /** + * DELETE /api/dc/projects/:projectId/references + * Löscht alle heruntergeladenen Referenzen eines Projekts (vor Re-Processing). + */ + @DELETE + @Path("/projects/{projectId}/references") + Response deleteReferences(@PathParam("projectId") long projectId); + // ========================================================================= // Ergebnisse // ========================================================================= diff --git a/dc-backend/src/main/java/de/frigosped/dc/client/OrdsLoggingFilter.java b/dc-backend/src/main/java/de/frigosped/dc/client/OrdsLoggingFilter.java index 2969cd1..9247316 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/client/OrdsLoggingFilter.java +++ b/dc-backend/src/main/java/de/frigosped/dc/client/OrdsLoggingFilter.java @@ -47,6 +47,8 @@ public class OrdsLoggingFilter implements ClientRequestFilter, ClientResponseFil LOG.infof("=== ORDS %s %s END ===", requestCtx.getMethod(), requestCtx.getUri()); } + private static final int MAX_BODY_LOG = 500; + private String readRequest(ClientRequestContext ctx) { Object entity = ctx.getEntity(); if (entity == null) return null; @@ -63,7 +65,11 @@ public class OrdsLoggingFilter implements ClientRequestFilter, ClientResponseFil } else { body = entity.toString(); } - return body.isBlank() ? null : body; + if (body.isBlank()) return null; + if (body.length() > MAX_BODY_LOG) { + return body.substring(0, MAX_BODY_LOG) + String.format(" ... [%d Zeichen gesamt]", body.length()); + } + return body; } private String readResponse(ClientResponseContext ctx) throws IOException { diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/CompleteProjectRequest.java b/dc-backend/src/main/java/de/frigosped/dc/model/CompleteProjectRequest.java new file mode 100644 index 0000000..559040d --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/model/CompleteProjectRequest.java @@ -0,0 +1,22 @@ +package de.frigosped.dc.model; + +import java.util.List; + +public class CompleteProjectRequest { + + private String reportMarkdown; + private List documentPdfs; + + public CompleteProjectRequest() {} + + public CompleteProjectRequest(String reportMarkdown, List documentPdfs) { + this.reportMarkdown = reportMarkdown; + this.documentPdfs = documentPdfs; + } + + public String getReportMarkdown() { return reportMarkdown; } + public void setReportMarkdown(String v) { this.reportMarkdown = v; } + + public List getDocumentPdfs() { return documentPdfs; } + public void setDocumentPdfs(List v) { this.documentPdfs = v; } +} \ No newline at end of file diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/DocumentPdfEntry.java b/dc-backend/src/main/java/de/frigosped/dc/model/DocumentPdfEntry.java new file mode 100644 index 0000000..be45c62 --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/model/DocumentPdfEntry.java @@ -0,0 +1,20 @@ +package de.frigosped.dc.model; + +public class DocumentPdfEntry { + + private String filename; + private String pdfBase64; + + public DocumentPdfEntry() {} + + public DocumentPdfEntry(String filename, String pdfBase64) { + this.filename = filename; + this.pdfBase64 = pdfBase64; + } + + public String getFilename() { return filename; } + public void setFilename(String v) { this.filename = v; } + + public String getPdfBase64() { return pdfBase64; } + public void setPdfBase64(String v) { this.pdfBase64 = v; } +} diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/DocumentProgressRequest.java b/dc-backend/src/main/java/de/frigosped/dc/model/DocumentProgressRequest.java new file mode 100644 index 0000000..78c7446 --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/model/DocumentProgressRequest.java @@ -0,0 +1,18 @@ +package de.frigosped.dc.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DocumentProgressRequest { + + private Integer progress; + private String status; + + public DocumentProgressRequest(Integer progress, String status) { + this.progress = progress; + this.status = status; + } + + public Integer getProgress() { return progress; } + public String getStatus() { return status; } +} \ No newline at end of file diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrRequest.java b/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrRequest.java new file mode 100644 index 0000000..3795c8c --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrRequest.java @@ -0,0 +1,28 @@ +package de.frigosped.dc.model; + +public class MistralOcrRequest { + + private String model; + private Document document; + + public MistralOcrRequest(String model, Document document) { + this.model = model; + this.document = document; + } + + public String getModel() { return model; } + public Document getDocument() { return document; } + + public static class Document { + private String type; + private String documentUrl; + + public Document(String type, String documentUrl) { + this.type = type; + this.documentUrl = documentUrl; + } + + public String getType() { return type; } + public String getDocumentUrl() { return documentUrl; } + } +} \ No newline at end of file diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrResponse.java b/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrResponse.java new file mode 100644 index 0000000..5c405f9 --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrResponse.java @@ -0,0 +1,37 @@ +package de.frigosped.dc.model; + +import java.util.List; + +public class MistralOcrResponse { + + private List pages; + private UsageInfo usageInfo; + + public List getPages() { return pages; } + public void setPages(List pages) { this.pages = pages; } + + public UsageInfo getUsageInfo() { return usageInfo; } + public void setUsageInfo(UsageInfo usageInfo) { this.usageInfo = usageInfo; } + + public static class Page { + private int index; + private String markdown; + + public int getIndex() { return index; } + public void setIndex(int index) { this.index = index; } + + public String getMarkdown() { return markdown; } + public void setMarkdown(String markdown) { this.markdown = markdown; } + } + + public static class UsageInfo { + private int pagesProcessed; + private Long docSizeBytes; + + public int getPagesProcessed() { return pagesProcessed; } + public void setPagesProcessed(int pagesProcessed) { this.pagesProcessed = pagesProcessed; } + + public Long getDocSizeBytes() { return docSizeBytes; } + public void setDocSizeBytes(Long docSizeBytes) { this.docSizeBytes = docSizeBytes; } + } +} \ No newline at end of file diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java b/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java index b191ef7..f2a4d29 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java +++ b/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java @@ -14,8 +14,13 @@ public class OrdsDocument { private Long documentTypeId; private Long catalogId; private LocalDateTime uploadedAt; + private String status; + private Integer progress; private Integer hasOriginalText; // 0 oder 1 private Integer hasTranslatedText; // 0 oder 1 + private Long parentDocumentId; // gesetzt bei heruntergeladenen Referenzen + private Integer isReference; // 0 = Originaldokument, 1 = Referenz + private String sourceUrl; // Ursprungs-URL der Referenz private Integer rowVersion; private LocalDateTime created; private LocalDateTime updated; @@ -40,12 +45,30 @@ public class OrdsDocument { public LocalDateTime getUploadedAt() { return uploadedAt; } public void setUploadedAt(LocalDateTime v) { this.uploadedAt = v; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + + public Integer getProgress() { return progress; } + public void setProgress(Integer progress) { this.progress = progress; } + public Integer getHasOriginalText() { return hasOriginalText; } public void setHasOriginalText(Integer v) { this.hasOriginalText = v; } public Integer getHasTranslatedText() { return hasTranslatedText; } public void setHasTranslatedText(Integer v) { this.hasTranslatedText = v; } + public Long getParentDocumentId() { return parentDocumentId; } + public void setParentDocumentId(Long v) { this.parentDocumentId = v; } + + public Integer getIsReference() { return isReference; } + public void setIsReference(Integer v) { this.isReference = v; } + + /** true, wenn dieses Dokument eine heruntergeladene Referenz ist. */ + public boolean isReference() { return Integer.valueOf(1).equals(isReference); } + + public String getSourceUrl() { return sourceUrl; } + public void setSourceUrl(String v) { this.sourceUrl = v; } + public Integer getRowVersion() { return rowVersion; } public void setRowVersion(Integer v) { this.rowVersion = v; } diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java b/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java index 64d1ba6..1cc8c96 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java +++ b/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java @@ -17,6 +17,9 @@ public class OrdsProject { private Integer progress; // 0–100 private LocalDateTime completedAt; private String notificationEmail; + private String notifyOk; // 'Y' | 'N' + private String notifyHinweis; // 'Y' | 'N' + private String notifyAbweichend; // 'Y' | 'N' private Integer rowVersion; private LocalDateTime created; private LocalDateTime updated; @@ -50,6 +53,15 @@ public class OrdsProject { public String getNotificationEmail() { return notificationEmail; } public void setNotificationEmail(String v) { this.notificationEmail = v; } + public String getNotifyOk() { return notifyOk; } + public void setNotifyOk(String v) { this.notifyOk = v; } + + public String getNotifyHinweis() { return notifyHinweis; } + public void setNotifyHinweis(String v) { this.notifyHinweis = v; } + + public String getNotifyAbweichend() { return notifyAbweichend; } + public void setNotifyAbweichend(String v) { this.notifyAbweichend = v; } + public Integer getRowVersion() { return rowVersion; } public void setRowVersion(Integer v) { this.rowVersion = v; } diff --git a/dc-backend/src/main/java/de/frigosped/dc/model/ReferenceCreateRequest.java b/dc-backend/src/main/java/de/frigosped/dc/model/ReferenceCreateRequest.java new file mode 100644 index 0000000..a07216d --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/model/ReferenceCreateRequest.java @@ -0,0 +1,38 @@ +package de.frigosped.dc.model; + +/** + * Request-Body für POST /api/dc/documents/:parent_id/references + * {"source_url":"https://...","filename":"AGB.pdf", + * "mime_type":"application/pdf","file_base64":"JVBERi0x..."} + * + * Legt eine heruntergeladene externe Referenz als neue dc_project_documents-Zeile an. + */ +public class ReferenceCreateRequest { + + private String sourceUrl; + private String filename; + private String mimeType; + private String fileBase64; + + public ReferenceCreateRequest() {} + + public ReferenceCreateRequest(String sourceUrl, String filename, + String mimeType, String fileBase64) { + this.sourceUrl = sourceUrl; + this.filename = filename; + this.mimeType = mimeType; + this.fileBase64 = fileBase64; + } + + public String getSourceUrl() { return sourceUrl; } + public void setSourceUrl(String v) { this.sourceUrl = v; } + + public String getFilename() { return filename; } + public void setFilename(String v) { this.filename = v; } + + public String getMimeType() { return mimeType; } + public void setMimeType(String v) { this.mimeType = v; } + + public String getFileBase64() { return fileBase64; } + public void setFileBase64(String v) { this.fileBase64 = v; } +} diff --git a/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java b/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java index 04a55ed..7d5d0ae 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java +++ b/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java @@ -14,6 +14,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Base64; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -48,6 +49,22 @@ public class CheckOrchestrationService { @Inject EvaluationService evaluationService; + @Inject + MarkdownReportBuilder markdownReportBuilder; + + @Inject + MarkdownExportService markdownExportService; + + @Inject + ReferenceDownloadService referenceDownloadService; + + @Inject + com.fasterxml.jackson.databind.ObjectMapper objectMapper; + + @org.eclipse.microprofile.config.inject.ConfigProperty( + name = "dc.references.max-per-document", defaultValue = "5") + int maxReferencesPerDocument; + // ========================================================================= // Haupt-Einstiegspunkt // ========================================================================= @@ -82,7 +99,7 @@ public class CheckOrchestrationService { if (documents.isEmpty()) { LOG.warnf("Projekt %d hat keine Dokumente – markiere als abgeschlossen", projectId); - ordsClient.completeProject(projectId); + ordsClient.completeProject(projectId, new CompleteProjectRequest("", java.util.Collections.emptyList())); return; } @@ -115,28 +132,39 @@ public class CheckOrchestrationService { } int[] stepHolder = {0}; - // 6. Alte Ergebnisse löschen (ermöglicht Re-Processing) + // 6. Alte Ergebnisse + heruntergeladene Referenzen löschen (ermöglicht Re-Processing) Response delResp = ordsClient.deleteResults(projectId); LOG.debugf("Alte Ergebnisse gelöscht: HTTP %d", delResp.getStatus()); + try { + Response delRefResp = ordsClient.deleteReferences(projectId); + LOG.debugf("Alte Referenzen gelöscht: HTTP %d", delRefResp.getStatus()); + } catch (Exception e) { + LOG.debugf("Referenzen löschen fehlgeschlagen: %s", e.getMessage()); + } - // Texte sammeln (in-memory: vermeidet Re-Fetch aus ORDS) + // Texte und Ergebnisse sammeln (in-memory für Markdown-Report) Map originalTexts = new HashMap<>(); + Map> resultsByDoc = new HashMap<>(); + // Original-Texte heruntergeladener Referenzen je Hauptdokument-ID + Map> referenceTextsByDoc = new HashMap<>(); // ─── PHASE A: OCR + Übersetzung (wird übersprungen wenn Text bereits vorhanden) ── for (OrdsDocument doc : documents) { - // Vor dem Schritt: Status auf "OCR für dieses Dokument" setzen + if (doc.isReference()) continue; // Referenzen werden während Phase A erzeugt + pushProgress(projectId, stepHolder[0], totalSteps, "OCR", doc.getId(), null, startedAt); + pushDocumentProgress(doc.getId(), 0, "IN_PROGRESS"); String originalText = ""; if (Integer.valueOf(1).equals(doc.getHasOriginalText())) { - // Resume: Text aus vorherigem Lauf wiederverwenden LOG.infof("Dokument %d: Text bereits vorhanden – überspringe OCR/Übersetzung", doc.getId()); try { OrdsDocumentTexts texts = ordsClient.getTexts(doc.getId()); originalText = texts.getOriginalText() != null ? texts.getOriginalText() : ""; + pushDocumentProgress(doc.getId(), 50, null); } catch (Exception e) { LOG.warnf("Text-Laden für Dokument %d fehlgeschlagen: %s", doc.getId(), e.getMessage()); } @@ -148,6 +176,7 @@ public class CheckOrchestrationService { if (fileResp.getStatus() != 200) { LOG.warnf("Download Dokument %d fehlgeschlagen (HTTP %d)", doc.getId(), fileResp.getStatus()); + pushDocumentProgress(doc.getId(), null, "ERROR"); } else { byte[] fileBytes = fileResp.readEntity(byte[].class); @@ -155,22 +184,34 @@ public class CheckOrchestrationService { fileBytes, doc.getMimeType(), doc.getFilename(), projectId); LOG.debugf("Dokument %d: %d Zeichen extrahiert", (long) doc.getId(), (long) originalText.length()); + pushDocumentProgress(doc.getId(), 25, null); translatedText = evaluationService.translate(originalText, projectId); LOG.debugf("Dokument %d: Übersetzung fertig (%d Zeichen)", (long) doc.getId(), (long) translatedText.length()); + pushDocumentProgress(doc.getId(), 50, null); ordsClient.updateTexts(doc.getId(), new TextsRequest(originalText, translatedText)); } } catch (Exception e) { LOG.errorf(e, "Fehler bei Dokumentenverarbeitung %d", doc.getId()); + pushDocumentProgress(doc.getId(), null, "ERROR"); } } originalTexts.put(doc.getId(), originalText); - // Nach dem Schritt: Fortschritt hochzählen + ETA aktualisieren + // Externe Referenzen erkennen, herunterladen und als eigene Zeilen speichern + if (!originalText.isBlank() && referenceDownloadService.isEnabled()) { + try { + processReferences(doc, originalText, projectId, referenceTextsByDoc); + } catch (Exception e) { + LOG.warnf("Referenz-Verarbeitung für Dokument %d fehlgeschlagen: %s", + doc.getId(), e.getMessage()); + } + } + stepHolder[0]++; pushProgress(projectId, stepHolder[0], totalSteps, "OCR", doc.getId(), null, startedAt); @@ -178,7 +219,12 @@ public class CheckOrchestrationService { // ─── PHASE B: Fragen auswerten ──────────────────────────────── for (OrdsDocument doc : documents) { - String originalText = originalTexts.getOrDefault(doc.getId(), ""); + if (doc.isReference()) continue; // Referenzen werden nur „in Summe" mitbewertet + + // Auswertung über Haupt- + Referenztext (Option D: „Summe") + String originalText = buildCombinedText( + originalTexts.getOrDefault(doc.getId(), ""), + referenceTextsByDoc.get(doc.getId())); Long catId = doc.getCatalogId(); if (catId == null || !questionsByCatalog.containsKey(catId)) { @@ -197,6 +243,7 @@ public class CheckOrchestrationService { } List> batches = partition(questions, 4); + int batchesDone = 0; for (List batch : batches) { OrdsQuestion first = batch.get(0); @@ -217,17 +264,64 @@ public class CheckOrchestrationService { for (OrdsQuestion question : batch) { EvaluationResult result = batchResults.get(question.getQuestionId()); if (result == null) continue; - saveResult(projectId, doc.getId(), question, result); + ResultRequest rr = saveResult(projectId, doc.getId(), question, result); + if (rr != null) { + resultsByDoc.computeIfAbsent(doc.getId(), k -> new ArrayList<>()).add(rr); + } } stepHolder[0] += batch.size(); + batchesDone += batch.size(); + int docProgress = 50 + (int) Math.min(batchesDone * 50L / questions.size(), 49); + pushDocumentProgress(doc.getId(), docProgress, null); pushProgress(projectId, stepHolder[0], totalSteps, "QUESTIONS", doc.getId(), batch.get(batch.size() - 1).getQuestionId(), startedAt); } + + pushDocumentProgress(doc.getId(), 100, "COMPLETED"); } - // 7. Abschließen - ordsClient.completeProject(projectId); + // 7. Kombinierten Markdown-Bericht für die DB generieren + String reportMarkdown = ""; + try { + reportMarkdown = markdownReportBuilder.build( + project, documents, questionsByCatalog, resultsByDoc); + LOG.infof("Projekt %d: Markdown-Bericht generiert (%d Zeichen)", + projectId, reportMarkdown.length()); + } catch (Exception e) { + LOG.warnf(e, "Markdown-Generierung fehlgeschlagen – sende leeren Body"); + } + + // 8. Pro Dokument ein eigenes PDF generieren + Map questionById = new HashMap<>(); + for (List qs : questionsByCatalog.values()) { + for (OrdsQuestion q : qs) questionById.put(q.getQuestionId(), q); + } + + List documentPdfs = new ArrayList<>(); + for (OrdsDocument doc : documents) { + List docResults = + resultsByDoc.getOrDefault(doc.getId(), java.util.Collections.emptyList()); + if (docResults.isEmpty()) continue; + try { + String docMd = markdownReportBuilder.buildForDocument( + project, doc, questionById, docResults); + byte[] pdfBytes = markdownExportService.toPdf(docMd); + String baseName = doc.getFilename().replaceAll("\\.[^.]+$", ""); + String filename = "Pruefbericht_" + + baseName.replaceAll("[^A-Za-z0-9_äöüÄÖÜß.-]", "_") + + ".pdf"; + documentPdfs.add(new DocumentPdfEntry(filename, + Base64.getEncoder().encodeToString(pdfBytes))); + LOG.infof("Projekt %d: Dok %d – PDF generiert (%d Bytes)", + projectId, doc.getId(), pdfBytes.length); + } catch (Exception e) { + LOG.warnf(e, "PDF-Generierung für Dok %d fehlgeschlagen", doc.getId()); + } + } + + ordsClient.completeProject(projectId, + new CompleteProjectRequest(reportMarkdown, documentPdfs)); LOG.infof("=== Projekt %d erfolgreich abgeschlossen ===", projectId); } catch (Exception e) { @@ -257,6 +351,98 @@ public class CheckOrchestrationService { } } + /** Setzt progress und/oder status eines Dokuments. null-Werte werden ignoriert. */ + private void pushDocumentProgress(long docId, Integer progress, String status) { + try { + jakarta.ws.rs.core.Response resp = + ordsClient.updateDocumentProgress(docId, new DocumentProgressRequest(progress, status)); + if (resp.getStatus() < 200 || resp.getStatus() >= 300) { + LOG.warnf("Dokument-Fortschritt: HTTP %d (Dok %d, progress=%s, status=%s)", + resp.getStatus(), docId, progress, status); + } + } catch (Exception e) { + LOG.warnf("Dokument-Fortschritt konnte nicht gesetzt werden (Dok %d): %s", docId, e.getMessage()); + } + } + + /** + * Erkennt externe Referenzen im Dokumenttext, lädt sie sicher herunter, + * legt sie als eigene dc_project_documents-Zeilen an (is_reference=1) und + * sammelt ihren Original-Text für die spätere „Summen"-Auswertung. + */ + private void processReferences(OrdsDocument doc, String originalText, long projectId, + Map> referenceTextsByDoc) { + List urls = evaluationService.extractReferenceUrls(originalText, projectId); + if (urls.isEmpty()) return; + + LOG.infof("Dokument %d: %d Referenz-URL(s) vom Modell erkannt", doc.getId(), urls.size()); + int processed = 0; + + for (String url : urls) { + if (processed >= maxReferencesPerDocument) { + LOG.infof("Dokument %d: Referenz-Limit (%d) erreicht – Rest ignoriert", + doc.getId(), maxReferencesPerDocument); + break; + } + + var fetched = referenceDownloadService.fetch(url, projectId); + if (fetched.isEmpty()) continue; + + ReferenceDownloadService.DownloadedReference ref = fetched.get(); + try { + // 1. Referenz als neue Dokument-Zeile (mit BLOB) anlegen + String base64 = Base64.getEncoder().encodeToString(ref.bytes()); + Response insResp = ordsClient.insertReference(doc.getId(), + new ReferenceCreateRequest(ref.sourceUrl(), ref.filename(), + ref.mimeType(), base64)); + String body = insResp.readEntity(String.class); + if (insResp.getStatus() != 201) { + LOG.warnf("Referenz-Insert fehlgeschlagen (HTTP %d): %s", insResp.getStatus(), body); + continue; + } + long refId = extractId(body); + + // 2. Texte der Referenz speichern (Original + Übersetzung) – für separate Ansicht + String refTranslated = evaluationService.translate(ref.text(), projectId); + ordsClient.updateTexts(refId, new TextsRequest(ref.text(), refTranslated)); + pushDocumentProgress(refId, 100, "COMPLETED"); + + // 3. Original-Text für die „Summen"-Auswertung des Hauptdokuments merken + referenceTextsByDoc.computeIfAbsent(doc.getId(), k -> new ArrayList<>()) + .add("\n" + ref.text()); + + processed++; + LOG.infof("Dokument %d: Referenz gespeichert (id=%d, %s)", + doc.getId(), refId, ref.sourceUrl()); + } catch (Exception e) { + LOG.warnf("Referenz '%s' konnte nicht gespeichert werden: %s", url, e.getMessage()); + } + } + } + + /** Liest das Feld "id" aus einer ORDS-Insert-Antwort. */ + private long extractId(String responseBody) throws Exception { + Map map = objectMapper.readValue(responseBody, Map.class); + Object id = map.get("id"); + if (id instanceof Number n) return n.longValue(); + throw new IllegalStateException("Kein 'id'-Feld in ORDS-Antwort: " + responseBody); + } + + /** + * Fügt Haupttext und Referenztexte zu einem Bewertungstext zusammen (Option D). + * Die Referenzen werden klar abgegrenzt angehängt. + */ + private String buildCombinedText(String mainText, List referenceTexts) { + if (referenceTexts == null || referenceTexts.isEmpty()) { + return mainText != null ? mainText : ""; + } + StringBuilder sb = new StringBuilder(mainText != null ? mainText : ""); + for (String refText : referenceTexts) { + sb.append("\n\n--- Referenzdokument ---\n\n").append(refText); + } + return sb.toString(); + } + /** Teilt eine Liste in Untergruppen der gewünschten Größe auf. */ private static List> partition(List list, int size) { List> parts = new ArrayList<>(); @@ -282,14 +468,20 @@ public class CheckOrchestrationService { return map; } - private void saveResult(long projectId, long docId, - OrdsQuestion question, EvaluationResult result) { + private ResultRequest saveResult(long projectId, long docId, + OrdsQuestion question, EvaluationResult result) { try { int deviation = 0, warning = 0; if (result.getScore() != null && question.getThreshold() != null && result.getScore() < question.getThreshold()) { - if ("ABWEICHUNG".equalsIgnoreCase(question.getResultHandling())) deviation = 1; - else if ("HINWEIS".equalsIgnoreCase(question.getResultHandling())) warning = 1; + if ("UNKLAR".equalsIgnoreCase(result.getResultType())) { + // Teilweise Erfüllung (falscher Paragraph, gleiches Ziel, o.ä.) → immer Hinweis + warning = 1; + } else if ("ABWEICHUNG".equalsIgnoreCase(question.getResultHandling())) { + deviation = 1; + } else if ("HINWEIS".equalsIgnoreCase(question.getResultHandling())) { + warning = 1; + } } ResultRequest rr = new ResultRequest( question.getQuestionId(), docId, @@ -301,9 +493,11 @@ public class CheckOrchestrationService { LOG.warnf("Ergebnis speichern: HTTP %d (Frage %d, Dok %d)", saveResp.getStatus(), question.getQuestionId(), docId); } + return rr; } catch (Exception e) { LOG.errorf(e, "Ergebnis-Speichern fehlgeschlagen: Frage %d / Dokument %d", question.getQuestionId(), docId); + return null; } } diff --git a/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java b/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java index a743a73..0cc3f16 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java +++ b/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java @@ -1,5 +1,8 @@ package de.frigosped.dc.service; +import de.frigosped.dc.client.MistralOcrClient; +import de.frigosped.dc.model.MistralOcrRequest; +import de.frigosped.dc.model.MistralOcrResponse; import dev.langchain4j.data.message.ImageContent; import dev.langchain4j.data.message.TextContent; import dev.langchain4j.data.message.UserMessage; @@ -11,7 +14,10 @@ import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; +import org.apache.pdfbox.text.PDFTextStripper; import org.apache.poi.xwpf.usermodel.*; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; import org.jboss.logging.Logger; import org.odftoolkit.odfdom.doc.OdfTextDocument; import org.w3c.dom.Node; @@ -42,13 +48,26 @@ public class DocumentProcessingService { /** 200 DPI: gutes Gleichgewicht zwischen Qualität und Dateigröße für OCR */ private static final float OCR_DPI = 200f; + /** Mindest-Zeichen pro Seite – darunter gilt das PDF als gescannt */ + private static final int MIN_CHARS_PER_PAGE = 50; + @Inject @Named("ocr") ChatLanguageModel ocrModel; + @Inject + @RestClient + MistralOcrClient mistralOcrClient; + @Inject AiCostTrackerService costTracker; + @ConfigProperty(name = "dc.ai.provider", defaultValue = "ollama") + String aiProvider; + + @ConfigProperty(name = "dc.ai.mistral.api-key", defaultValue = "") + String mistralApiKey; + // ========================================================================= // Öffentliche API // ========================================================================= @@ -96,28 +115,32 @@ public class DocumentProcessingService { // ========================================================================= private String extractFromPdf(byte[] fileBytes, Long projectId) throws Exception { - LOG.debug("Starte PDF-OCR..."); - StringBuilder result = new StringBuilder(); - try (PDDocument pdf = Loader.loadPDF(fileBytes)) { - PDFRenderer renderer = new PDFRenderer(pdf); int pageCount = pdf.getNumberOfPages(); LOG.debugf("PDF hat %d Seite(n)", pageCount); + String extracted = new PDFTextStripper().getText(pdf).trim(); + if (extracted.length() >= pageCount * MIN_CHARS_PER_PAGE) { + LOG.debugf("PDF-Textextraktion: %d Zeichen", extracted.length()); + return extracted; + } + + LOG.debugf("PDF hat wenig eingebetteten Text (%d Zeichen) – verwende Vision-OCR", extracted.length()); + if ("mistral".equalsIgnoreCase(aiProvider)) { + return mistralOcr(fileBytes); + } + PDFRenderer renderer = new PDFRenderer(pdf); + StringBuilder result = new StringBuilder(); for (int i = 0; i < pageCount; i++) { LOG.debugf("OCR Seite %d/%d", i + 1, pageCount); BufferedImage image = renderer.renderImageWithDPI(i, OCR_DPI, ImageType.RGB); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, "PNG", baos); String base64Image = Base64.getEncoder().encodeToString(baos.toByteArray()); - - String pageText = ocrPage(base64Image, i + 1, pageCount, projectId); - result.append(pageText).append("\n\n"); + result.append(ocrPage(base64Image, i + 1, pageCount, projectId)).append("\n\n"); } + return result.toString().trim(); } - - return result.toString().trim(); } private String ocrPage(String base64Image, int pageNum, int totalPages, Long projectId) { @@ -143,6 +166,31 @@ public class DocumentProcessingService { return response.content().text(); } + private String mistralOcr(byte[] fileBytes) { + LOG.debug("Mistral OCR API – sende PDF direkt..."); + String base64 = Base64.getEncoder().encodeToString(fileBytes); + String dataUrl = "data:application/pdf;base64," + base64; + + MistralOcrRequest request = new MistralOcrRequest( + "mistral-ocr-latest", + new MistralOcrRequest.Document("document_url", dataUrl) + ); + + MistralOcrResponse response = mistralOcrClient.ocr("Bearer " + mistralApiKey, request); + + StringBuilder result = new StringBuilder(); + for (MistralOcrResponse.Page page : response.getPages().stream() + .sorted(java.util.Comparator.comparingInt(MistralOcrResponse.Page::getIndex)) + .toList()) { + if (page.getMarkdown() != null && !page.getMarkdown().isBlank()) { + result.append(page.getMarkdown()).append("\n\n"); + } + } + LOG.debugf("Mistral OCR abgeschlossen: %d Seite(n), %d Zeichen", + response.getPages().size(), result.length()); + return result.toString().trim(); + } + // ========================================================================= // DOCX → Markdown (Apache POI) // ========================================================================= diff --git a/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java b/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java index 4c949a2..f49db01 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java +++ b/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * KI-gestützte Auswertung: @@ -78,7 +79,79 @@ public class EvaluationService { Response response = mainModel.generate(messages); costTracker.track("main", "TRANSLATE", projectId, null, response.tokenUsage()); - return response.content().text().trim(); + return stripCodeFence(response.content().text().trim()); + } + + /** + * Entfernt eine umschließende ```-Markierung (z. B. ```markdown), die das + * Modell der Übersetzung manchmal trotz Anweisung hinzufügt. So wird der + * Markdown-Text einheitlich ohne Code-Fence gespeichert. + */ + private String stripCodeFence(String text) { + if (text == null) return ""; + String t = text.trim(); + if (t.startsWith("```")) { + int firstNewline = t.indexOf('\n'); + if (firstNewline > 0) { + t = t.substring(firstNewline + 1); // erste Zeile (```markdown) entfernen + } + if (t.endsWith("```")) { + t = t.substring(0, t.length() - 3); // schließendes ``` entfernen + } + } + return t.trim(); + } + + // ========================================================================= + // Referenz-Erkennung + // ========================================================================= + + /** + * Lässt das Hauptmodell die relevanten externen Dokument-Referenz-URLs direkt + * aus dem Dokumenttext ausgeben (kein Regex). Login-, Video-, Impressum- und + * Portalseiten werden anhand des Kontexts herausgefiltert. + * + * @return Liste der vom Modell gelieferten URLs (kann leer sein) + */ + public List extractReferenceUrls(String documentText, long projectId) { + if (documentText == null || documentText.isBlank()) return List.of(); + + List messages = List.of( + SystemMessage.from( + "Du analysierst Transport- und Speditionsdokumente und findest Verweise auf\n" + + "EXTERNE Dokumente, die per Link erreichbar sind (z. B. AGB, Allgemeine\n" + + "Geschäftsbedingungen, Verhaltenskodex/Code of Conduct, Vertragsbedingungen,\n" + + "Sicherheits- oder Compliance-Richtlinien).\n\n" + + "Gib NUR Links zurück, die tatsächlich im Text stehen und auf ein solches\n" + + "Dokument verweisen. Schließe ausdrücklich AUS: Login-/Portalseiten,\n" + + "Video-Links (z. B. YouTube), Impressum, Datenschutz-Übersichtsseiten,\n" + + "Hinweisgeber-/Whistleblower-Portale, Social-Media- und reine Startseiten.\n\n" + + "Verändere die URLs nicht – gib sie exakt so zurück, wie sie im Text stehen.\n\n" + + "Antworte AUSSCHLIESSLICH mit einem gültigen JSON-Array von Strings (URLs).\n" + + "Wenn es keine passenden Links gibt, antworte mit []." + ), + UserMessage.from("## Dokument\n\n" + documentText + "\n\n---\n\nAntworte mit dem JSON-Array der relevanten URLs:") + ); + + try { + Response response = mainModel.generate(messages); + costTracker.track("main", "REF_EXTRACT", projectId, null, response.tokenUsage()); + + String json = extractJsonArray(response.content().text()); + List urls = objectMapper.readValue( + json, + objectMapper.getTypeFactory().constructCollectionType(List.class, String.class) + ); + // Duplikate entfernen, leere Einträge filtern, Reihenfolge erhalten + return urls.stream() + .filter(u -> u != null && !u.isBlank()) + .map(String::trim) + .distinct() + .collect(Collectors.toList()); + } catch (Exception e) { + LOG.warnf("Referenz-URL-Extraktion fehlgeschlagen: %s", e.getMessage()); + return List.of(); + } } // ========================================================================= @@ -163,6 +236,11 @@ public class EvaluationService { Du bist ein Experte für Transportrecht, AGB-Recht und Vertragsanalyse. Du prüfst Transport- und Speditionsdokumente gegen einen Fragenkatalog. + Wichtige Suchregel: Suche im Dokument ZUERST nach expliziten, wörtlichen Formulierungen, + die die Anforderung direkt bestätigen oder verneinen. Zitiere immer die stärkste, + direkteste Textstelle. Nutze indirekte oder implizite Belege NUR dann, wenn keine + direkte Formulierung im Dokument existiert. + Antworte AUSSCHLIESSLICH mit einem gültigen JSON-Array – kein Text davor oder danach. Das Array enthält für JEDE Frage genau ein Objekt: @@ -275,6 +353,11 @@ public class EvaluationService { Du bist ein Experte für Transportrecht, AGB-Recht und Vertragsanalyse. Du prüfst Transport- und Speditionsdokumente gegen einen Fragenkatalog. + Wichtige Suchregel: Suche im Dokument ZUERST nach expliziten, wörtlichen Formulierungen, + die die Anforderung direkt bestätigen oder verneinen. Zitiere immer die stärkste, + direkteste Textstelle. Nutze indirekte oder implizite Belege NUR dann, wenn keine + direkte Formulierung im Dokument existiert. + Antworte AUSSCHLIESSLICH mit einem gültigen JSON-Objekt – kein Text davor oder danach. Das JSON-Objekt muss folgende Felder enthalten: diff --git a/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownExportService.java b/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownExportService.java index 1b99d28..1e3d32b 100644 --- a/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownExportService.java +++ b/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownExportService.java @@ -20,6 +20,12 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Konvertiert Markdown-Text zu DOCX (Apache POI) oder PDF (PDFBox). @@ -66,41 +72,90 @@ public class MarkdownExportService { || cp == 0x1F4C4; // 📄 } + /** Findet das schließende einzelne * ab Position from; überspringt ** -Paare. */ + private static int findItalicClose(String text, int from) { + int end = text.indexOf('*', from); + while (end >= 0) { + if (end + 1 >= text.length() || text.charAt(end + 1) != '*') return end; + end = text.indexOf('*', end + 2); + } + return -1; + } + /** - * Zerlegt eine Zeile in typisierte Inline-Segmente. + * Zerlegt text in typisierte Inline-Segmente mit Zeilenübergreifender Format-Fortsetzung. * - * Erkennungsreihenfolge pro Position: - * 1. **text** → BOLD - * 2. *text* → ITALIC (nur wenn nicht Teil von **) - * 3. isReportEmoji() → EMOJI (inkl. nachfolgende Variation-Selektoren U+FE0F / ZWJ U+200D) - * 4. alles andere → PLAIN (gesammelt bis zum nächsten Marker oder Emoji) + * openFormatRef[0] gibt das ggf. bereits offene Format vom vorherigen Aufruf an + * (null = keines, BOLD = innerhalb **, ITALIC = innerhalb *). + * Nach dem Aufruf enthält openFormatRef[0] das weiterhin offene Format. * - * Beispiel: "✅ **Frage** ist *kursiv*" - * → [EMOJI "✅"], [PLAIN " "], [BOLD "Frage"], [PLAIN " ist "], [ITALIC "kursiv"] + * Wird ** oder * geöffnet aber auf dieser Zeile nicht geschlossen, erstreckt sich + * die Formatierung bis ans Zeilenende und openFormatRef[0] wird entsprechend gesetzt, + * damit der nächste Aufruf die Formatierung nahtlos fortsetzt. */ - static List parseSegments(String text) { + static List parseSegments(String text, SegType[] openFormatRef) { List out = new ArrayList<>(); if (text == null || text.isEmpty()) return out; + + SegType open = openFormatRef[0]; int pos = 0; + + // Format-Fortsetzung von der Vorgängerzeile aufnehmen + if (open == SegType.BOLD) { + int end = text.indexOf("**"); + if (end >= 0) { + if (end > 0) out.add(new Seg(SegType.BOLD, text.substring(0, end))); + pos = end + 2; + open = null; + } else { + out.add(new Seg(SegType.BOLD, text)); + openFormatRef[0] = SegType.BOLD; + return out; + } + } else if (open == SegType.ITALIC) { + int end = findItalicClose(text, 0); + if (end >= 0) { + if (end > 0) out.add(new Seg(SegType.ITALIC, text.substring(0, end))); + pos = end + 1; + open = null; + } else { + out.add(new Seg(SegType.ITALIC, text)); + openFormatRef[0] = SegType.ITALIC; + return out; + } + } + while (pos < text.length()) { // **bold** if (text.startsWith("**", pos)) { int end = text.indexOf("**", pos + 2); - if (end > pos + 1) { + if (end >= 0) { out.add(new Seg(SegType.BOLD, text.substring(pos + 2, end))); pos = end + 2; - continue; + } else { + // kein schließendes ** auf dieser Zeile → bis Zeilenende + Folgezeile + String boldText = text.substring(pos + 2); + if (!boldText.isEmpty()) out.add(new Seg(SegType.BOLD, boldText)); + open = SegType.BOLD; + pos = text.length(); } + continue; } // *italic* (not part of **) if (text.charAt(pos) == '*' && (pos + 1 >= text.length() || text.charAt(pos + 1) != '*')) { - int end = text.indexOf('*', pos + 1); - if (end > pos && (end + 1 >= text.length() || text.charAt(end + 1) != '*')) { + int end = findItalicClose(text, pos + 1); + if (end >= 0) { out.add(new Seg(SegType.ITALIC, text.substring(pos + 1, end))); pos = end + 1; - continue; + } else { + // kein schließendes * auf dieser Zeile → bis Zeilenende + Folgezeile + String italicText = text.substring(pos + 1); + if (!italicText.isEmpty()) out.add(new Seg(SegType.ITALIC, italicText)); + open = SegType.ITALIC; + pos = text.length(); } + continue; } // Bekanntes Report-Emoji int cp = text.codePointAt(pos); @@ -124,9 +179,16 @@ public class MarkdownExportService { } if (pos > start) out.add(new Seg(SegType.PLAIN, text.substring(start, pos))); } + + openFormatRef[0] = open; return out; } + /** Überladung ohne Zustandsverfolgung (für Tabellenzellen und isolierte Aufrufe). */ + static List parseSegments(String text) { + return parseSegments(text, new SegType[]{null}); + } + // ========================================================================= // DOCX-Export via Apache POI // ========================================================================= @@ -154,22 +216,30 @@ public class MarkdownExportService { try (XWPFDocument doc = new XWPFDocument()) { String[] lines = markdown.replace("\r", "").split("\n", -1); int i = 0; + SegType[] inlineState = {null}; // zeilenübergreifendes Bold/Italic-State while (i < lines.length) { String line = lines[i]; if (line.startsWith("#### ")) { + inlineState[0] = null; addHeading(doc, line.substring(5).trim(), 4); } else if (line.startsWith("### ")) { + inlineState[0] = null; addHeading(doc, line.substring(4).trim(), 3); } else if (line.startsWith("## ")) { + inlineState[0] = null; addHeading(doc, line.substring(3).trim(), 2); } else if (line.startsWith("# ")) { + inlineState[0] = null; addHeading(doc, line.substring(2).trim(), 1); } else if (line.equals("---")) { + inlineState[0] = null; addHorizontalRule(doc); } else if (line.startsWith("> ")) { + inlineState[0] = null; addBlockquote(doc, line.substring(2).trim()); } else if (line.startsWith("|")) { + inlineState[0] = null; // Tabellenzeilen sammeln bis die Pipe-Sequenz endet List rows = new ArrayList<>(); while (i < lines.length && lines[i].startsWith("|")) { @@ -185,9 +255,10 @@ public class MarkdownExportService { if (!rows.isEmpty()) addTable(doc, rows); continue; // i wurde schon inkrementiert } else if (!line.isBlank()) { - addParagraph(doc, line); + addParagraph(doc, line, inlineState); + } else { + inlineState[0] = null; // Leerzeile = Absatzgrenze → Format zurücksetzen } - // Leerzeile erzeugt implizit Abstand durch fehlenden Paragraph-Inhalt i++; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -215,21 +286,21 @@ public class MarkdownExportService { run.setFontSize(switch (level) { case 1 -> 22; case 2 -> 18; case 3 -> 14; default -> 12; }); } - /** - * Fügt einen normalen Absatz mit Inline-Formatierung hinzu. - * Delegiert an renderInline(), das Bold/Italic/Emoji korrekt als separate Runs setzt. - */ + /** Fügt einen normalen Absatz hinzu; state wird für zeilenübergreifende Formatierung mitgeführt. */ + private void addParagraph(XWPFDocument doc, String text, SegType[] state) { + renderInline(doc.createParagraph(), text, state); + } + private void addParagraph(XWPFDocument doc, String text) { renderInline(doc.createParagraph(), text); } /** * Rendert Inline-Formatierung in einen vorhandenen Paragraph. - * parseSegments() liefert die Segmente; jedes bekommt einen eigenen XWPFRun - * mit setBold()/setItalic(). Emoji-Codepoints landen als Rohtext (Word rendert sie nativ). + * state wird an parseSegments() weitergereicht und hält das über Zeilen offene Format. */ - private void renderInline(XWPFParagraph p, String text) { - for (Seg seg : parseSegments(text)) { + private void renderInline(XWPFParagraph p, String text, SegType[] state) { + for (Seg seg : parseSegments(text, state)) { XWPFRun run = p.createRun(); run.setText(seg.text()); if (seg.type() == SegType.BOLD) run.setBold(true); @@ -237,6 +308,11 @@ public class MarkdownExportService { } } + /** Überladung ohne Zustandsverfolgung (für Tabellenzellen). */ + private void renderInline(XWPFParagraph p, String text) { + renderInline(p, text, new SegType[]{null}); + } + /** * Fügt eine visuelle Trennlinie ein (Dashes-Zeichen, kein echtes OOXML-Border). */ @@ -317,13 +393,14 @@ public class MarkdownExportService { private static final float TEXT_WIDTH = PAGE_WIDTH - 2 * MARGIN; /** - * Erzeugt ein PDF aus dem Markdown-String. - * \r wird vorab entfernt (Oracle CLOBs liefern \r\n; PDType1Font akzeptiert - * nur Latin-1 0x20–0xFF, \r würde eine Exception werfen). + * Erzeugt ein PDF aus dem Markdown-String via PDFBox. + * Emoji werden als farbige Java2D-Icons eingebettet (via PdfWriter.emojiImage()). + * \r wird vorab entfernt (Oracle CLOBs liefern \r\n-Zeilenenden). */ public byte[] toPdf(String markdown) throws Exception { try (PDDocument doc = new PDDocument()) { - new PdfWriter(doc).write(markdown.replace("\r", "")); + PdfWriter writer = new PdfWriter(doc); + writer.write(markdown.replace("\r", "")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); doc.save(baos); return baos.toByteArray(); @@ -337,6 +414,12 @@ public class MarkdownExportService { PDPageContentStream cs; // aktiver Content-Stream der aktuellen Seite float y; // aktuelle y-Schreibposition (oben nach unten) final PDType1Font fNorm, fBold, fItalic; + // Statischer Cache: "codepoint@px" → BufferedImage (JVM-weit, überlebt mehrere PDFs) + private static final java.util.concurrent.ConcurrentHashMap + BUFFERED_IMAGE_CACHE = new java.util.concurrent.ConcurrentHashMap<>(); + // Pro-Dokument-Cache: "codepoint@px" → PDImageXObject (PDDocument-gebunden) + private final java.util.Map emojiCache = new java.util.HashMap<>(); + int emojiCacheMisses = 0; PdfWriter(PDDocument doc) throws Exception { this.doc = doc; @@ -356,6 +439,7 @@ public class MarkdownExportService { doc.addPage(p); cs = new PDPageContentStream(doc, p); y = PAGE_HEIGHT - MARGIN; + LOG.infof("PDF: neue Seite %d", doc.getPages().getCount()); } /** @@ -390,88 +474,122 @@ public class MarkdownExportService { PDImageXObject emojiImage(String emoji, float sizePt) throws Exception { int px = Math.max(24, (int)(sizePt * 3)); int pad = Math.max(1, px / 16); - BufferedImage img = new BufferedImage(px + pad * 2, px + pad * 2, - BufferedImage.TYPE_INT_ARGB); - Graphics2D g = img.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_PURE); + String cacheKey = emoji.codePointAt(0) + "@" + px; - float cx = pad + px / 2f; - float cy = pad + px / 2f; - float r = px / 2f - 0.5f; + // Level 1: per-document PDImageXObject cache (PDDocument-gebunden) + PDImageXObject cached = emojiCache.get(cacheKey); + if (cached != null) return cached; + + long tEmoji = System.currentTimeMillis(); + emojiCacheMisses++; + + // Level 2: JVM-weiter BufferedImage-Cache (überlebt mehrere Dokumente) + // Lambda darf keine checked Exceptions werfen → alles per Java2D-Shapes, kein AWT-Font int cp = emoji.isEmpty() ? 0 : emoji.codePointAt(0); + BufferedImage img = BUFFERED_IMAGE_CACHE.computeIfAbsent(cacheKey, k -> { + BufferedImage bi = new BufferedImage(px + pad * 2, px + pad * 2, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); - switch (cp) { - case 0x2705 -> { // ✅ grüner Kreis + weißes Häkchen - g.setColor(new Color(52, 168, 83)); - g.fill(new Ellipse2D.Float(pad, pad, px, px)); - g.setColor(Color.WHITE); - g.setStroke(new BasicStroke(px / 6f, - BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - g.drawPolyline( - new int[]{(int)(cx - r*.42f), (int)(cx - r*.08f), (int)(cx + r*.42f)}, - new int[]{(int)(cy + r*.05f), (int)(cy + r*.42f), (int)(cy - r*.32f)}, - 3); + float cx = pad + px / 2f; + float cy = pad + px / 2f; + float r = px / 2f - 0.5f; + + switch (cp) { + case 0x2705 -> { // ✅ grüner Kreis + weißes Häkchen + g.setColor(new Color(52, 168, 83)); + g.fill(new Ellipse2D.Float(pad, pad, px, px)); + g.setColor(Color.WHITE); + g.setStroke(new BasicStroke(px / 6f, + BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g.drawPolyline( + new int[]{(int)(cx - r*.42f), (int)(cx - r*.08f), (int)(cx + r*.42f)}, + new int[]{(int)(cy + r*.05f), (int)(cy + r*.42f), (int)(cy - r*.32f)}, + 3); + } + case 0x274C -> { // ❌ roter Kreis + weißes X + g.setColor(new Color(234, 67, 53)); + g.fill(new Ellipse2D.Float(pad, pad, px, px)); + g.setColor(Color.WHITE); + g.setStroke(new BasicStroke(px / 5.5f, + BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + float d = r * 0.34f; + g.drawLine((int)(cx-d),(int)(cy-d),(int)(cx+d),(int)(cy+d)); + g.drawLine((int)(cx+d),(int)(cy-d),(int)(cx-d),(int)(cy+d)); + } + case 0x26A0 -> { // ⚠️ gelbes Dreieck + ! (Shape, kein AWT-Font) + g.setColor(new Color(251, 188, 4)); + g.fillPolygon( + new int[]{(int)cx, pad + px, pad}, + new int[]{pad, pad + px, pad + px}, 3); + g.setColor(new Color(50, 50, 50)); + // "!" als Shape: Balken (fillRect) + Punkt (Ellipse) + float barW = Math.max(2f, px * 0.10f); + float barH = px * 0.32f; + float dotD = Math.max(2f, px * 0.11f); + g.fill(new java.awt.geom.RoundRectangle2D.Float( + cx - barW / 2f, pad + px * 0.37f, barW, barH, barW / 2, barW / 2)); + g.fill(new Ellipse2D.Float( + cx - dotD / 2f, pad + px * 0.77f, dotD, dotD)); + } + case 0x2753 -> { // ❓ roter Kreis + weißes ? (Shape, kein AWT-Font) + g.setColor(new Color(234, 67, 53)); + g.fill(new Ellipse2D.Float(pad, pad, px, px)); + g.setColor(Color.WHITE); + float sw = Math.max(1.5f, px / 7.5f); + g.setStroke(new BasicStroke(sw, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + // "?" Haken via quadratische Bézierkurven + float x0 = pad, y0 = pad; + java.awt.geom.Path2D.Float path = new java.awt.geom.Path2D.Float(); + path.moveTo(x0 + px * 0.28f, y0 + px * 0.26f); // linkes offenes Ende + path.quadTo(x0 + px * 0.26f, y0 + px * 0.12f, + x0 + px * 0.50f, y0 + px * 0.12f); // nach oben-mitte + path.quadTo(x0 + px * 0.74f, y0 + px * 0.12f, + x0 + px * 0.74f, y0 + px * 0.30f); // über die Spitze rechts + path.quadTo(x0 + px * 0.74f, y0 + px * 0.46f, + x0 + px * 0.56f, y0 + px * 0.50f); // rechte Seite einwärts + path.quadTo(x0 + px * 0.50f, y0 + px * 0.52f, + x0 + px * 0.50f, y0 + px * 0.62f); // Schaft nach unten + g.draw(path); + float dotD2 = Math.max(2f, px * 0.10f); + g.fill(new Ellipse2D.Float( + x0 + px * 0.50f - dotD2 / 2f, y0 + px * 0.78f, dotD2, dotD2)); + } + case 0x1F4C4 -> { // 📄 blaues Dokument + int dw = (int)(px * .70f), dh = (int)(px * .90f); + int dx = (int)(cx - dw / 2f), dy = pad + (int)(px * .05f); + int fold = (int)(px * .22f); + g.setColor(new Color(66, 133, 244)); + g.fillPolygon( + new int[]{dx, dx + dw - fold, dx + dw, dx + dw, dx}, + new int[]{dy, dy, dy + fold, dy + dh, dy + dh}, 5); + g.setColor(new Color(30, 90, 200)); + g.fillPolygon( + new int[]{dx + dw - fold, dx + dw - fold, dx + dw}, + new int[]{dy, dy + fold, dy + fold}, 3); + g.setColor(Color.WHITE); + int lw = (int)(dw * .56f), lx = dx + (int)(dw * .18f); + int lh = Math.max(1, (int)(dh * .08f)), gap = (int)(dh * .13f); + int ly = dy + (int)(dh * .38f); + for (int l = 0; l < 3; l++) g.fillRect(lx, ly + l * gap, lw, lh); + } + default -> { // Unbekannter Codepoint: grauer Kreis (Fallback) + g.setColor(new Color(158, 158, 158)); + g.fill(new Ellipse2D.Float(pad, pad, px, px)); + } } - case 0x274C -> { // ❌ roter Kreis + weißes X - g.setColor(new Color(234, 67, 53)); - g.fill(new Ellipse2D.Float(pad, pad, px, px)); - g.setColor(Color.WHITE); - g.setStroke(new BasicStroke(px / 5.5f, - BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); - float d = r * 0.34f; - g.drawLine((int)(cx-d),(int)(cy-d),(int)(cx+d),(int)(cy+d)); - g.drawLine((int)(cx+d),(int)(cy-d),(int)(cx-d),(int)(cy+d)); - } - case 0x26A0 -> { // ⚠️ gelbes Dreieck + ! - g.setColor(new Color(251, 188, 4)); - g.fillPolygon( - new int[]{(int)cx, pad + px, pad}, - new int[]{pad, pad + px, pad + px}, 3); - g.setColor(new Color(50, 50, 50)); - g.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, (int)(px * .42f))); - FontMetrics fm = g.getFontMetrics(); - g.drawString("!", (int)(cx - fm.stringWidth("!") / 2f), - (int)(pad + px * .82f)); - } - case 0x2753 -> { // ❓ roter Kreis + weißes ? - g.setColor(new Color(234, 67, 53)); - g.fill(new Ellipse2D.Float(pad, pad, px, px)); - g.setColor(Color.WHITE); - g.setFont(new java.awt.Font("Dialog", java.awt.Font.BOLD, (int)(px * .55f))); - FontMetrics fm = g.getFontMetrics(); - g.drawString("?", (int)(cx - fm.stringWidth("?") / 2f), - (int)(cy + px * .20f)); - } - case 0x1F4C4 -> { // 📄 blaues Dokument - int dw = (int)(px * .70f), dh = (int)(px * .90f); - int dx = (int)(cx - dw / 2f), dy = pad + (int)(px * .05f); - int fold = (int)(px * .22f); - g.setColor(new Color(66, 133, 244)); - g.fillPolygon( - new int[]{dx, dx + dw - fold, dx + dw, dx + dw, dx}, - new int[]{dy, dy, dy + fold, dy + dh, dy + dh}, 5); - g.setColor(new Color(30, 90, 200)); // gefaltete Ecke dunkler - g.fillPolygon( - new int[]{dx + dw - fold, dx + dw - fold, dx + dw}, - new int[]{dy, dy + fold, dy + fold}, 3); - g.setColor(Color.WHITE); // drei weiße Textzeilen - int lw = (int)(dw * .56f), lx = dx + (int)(dw * .18f); - int lh = Math.max(1, (int)(dh * .08f)), gap = (int)(dh * .13f); - int ly = dy + (int)(dh * .38f); - for (int l = 0; l < 3; l++) g.fillRect(lx, ly + l * gap, lw, lh); - } - default -> { // Unbekannter Codepoint: grauer Kreis (Fallback) - g.setColor(new Color(158, 158, 158)); - g.fill(new Ellipse2D.Float(pad, pad, px, px)); - } - } - g.dispose(); - // LosslessFactory = verlustfreie PNG-Einbettung ins PDF - return LosslessFactory.createFromImage(doc, img); + g.dispose(); + return bi; + }); + + // BufferedImage → PDImageXObject (verlustfreie PNG-Einbettung) + PDImageXObject xobj = LosslessFactory.createFromImage(doc, img); + emojiCache.put(cacheKey, xobj); + LOG.infof("PDF: emojiImage('%s' U+%04X, %dpx) neu gerendert in %dms", + cacheKey, emoji.codePointAt(0), px, System.currentTimeMillis() - tEmoji); + return xobj; } /** @@ -551,9 +669,9 @@ public class MarkdownExportService { * * indent: horizontaler Einzug in Punkten (z.B. 20 für Blockquotes). */ - void renderLine(String text, PDType1Font defaultFont, float size, float indent) - throws Exception { - List segs = parseSegments(text); + void renderLine(String text, PDType1Font defaultFont, float size, float indent, + SegType[] state) throws Exception { + List segs = parseSegments(text, state); boolean onlyPlain = segs.stream().allMatch(s -> s.type() == SegType.PLAIN); if (onlyPlain) { writeWrappedText(defaultFont, size, indent, text); @@ -585,6 +703,12 @@ public class MarkdownExportService { if (!line.isEmpty()) flushInlineLine(line, size, emojiPt, indent); } + /** Überladung ohne Zustandsverfolgung (für Blockquotes, Tabellenzeilen). */ + void renderLine(String text, PDType1Font defaultFont, float size, float indent) + throws Exception { + renderLine(text, defaultFont, size, indent, new SegType[]{null}); + } + /** * Schreibt eine fertig zusammengestellte Inline-Zeile auf den PDF-Stream. * Berechnet die Zeilenhöhe als Maximum aus LINE_HEIGHT und Emoji-Größe, @@ -728,8 +852,21 @@ public class MarkdownExportService { void write(String markdown) throws Exception { String[] lines = markdown.split("\n", -1); List tableAccum = new ArrayList<>(); + int lineNr = 0; + long tWrite = System.currentTimeMillis(); + SegType[] inlineState = {null}; // zeilenübergreifendes Bold/Italic-State for (String line : lines) { + lineNr++; + if (lineNr % 10 == 0) { + LOG.infof("PDF: write() Zeile %d/%d – %d Seiten – %dms bisher", + lineNr, lines.length, doc.getPages().getCount(), + System.currentTimeMillis() - tWrite); + } + if (lineNr >= lines.length - 20) { + LOG.infof("PDF: Zeile %d: [%s]", lineNr, + line.length() > 120 ? line.substring(0, 120) + "…" : line); + } // Tabellenpuffer flushen sobald eine Nicht-Pipe-Zeile kommt if (!line.startsWith("|") && !tableAccum.isEmpty()) { renderTable(tableAccum); @@ -737,14 +874,19 @@ public class MarkdownExportService { } if (line.startsWith("#### ")) { + inlineState[0] = null; skip(10); renderLine(line.substring(5).trim(), fBold, 11, 0); skip(4); } else if (line.startsWith("### ")) { + inlineState[0] = null; skip(20); renderLine(line.substring(4).trim(), fBold, 13, 0); skip(6); } else if (line.startsWith("## ")) { + inlineState[0] = null; skip(28); renderLine(line.substring(3).trim(), fBold, 15, 0); skip(8); } else if (line.startsWith("# ")) { + inlineState[0] = null; skip(20); renderLine(line.substring(2).trim(), fBold, 18, 0); skip(1); } else if (line.equals("---")) { + inlineState[0] = null; skip(4); ensureSpace(2); cs.setLineWidth(0.5f); @@ -753,8 +895,10 @@ public class MarkdownExportService { cs.stroke(); y -= 6; } else if (line.startsWith("> ")) { + inlineState[0] = null; renderLine(line.substring(2).trim(), fItalic, 10, 20); } else if (line.startsWith("|")) { + inlineState[0] = null; if (!isTableSeparator(line)) { String[] parts = line.split("\\|", -1); List cells = new ArrayList<>(); @@ -763,13 +907,23 @@ public class MarkdownExportService { if (!cells.isEmpty()) tableAccum.add(cells.toArray(new String[0])); } } else if (line.isBlank()) { + inlineState[0] = null; // Leerzeile = Absatzgrenze → Format zurücksetzen skip(4); } else { - renderLine(line, fNorm, 11, 0); + renderLine(line, fNorm, 11, 0, inlineState); } } - if (!tableAccum.isEmpty()) renderTable(tableAccum); + if (!tableAccum.isEmpty()) { + LOG.infof("PDF: renderTable() wird aufgerufen (%d Zeilen)", tableAccum.size()); + renderTable(tableAccum); + LOG.infof("PDF: renderTable() fertig"); + } + LOG.infof("PDF: cs.close() wird aufgerufen (Seite %d)", doc.getPages().getCount()); cs.close(); + LOG.infof("PDF: cs.close() fertig"); + LOG.infof("PDF: write() fertig – %d Zeilen, %d Seiten, %dms", + lines.length, doc.getPages().getCount(), + System.currentTimeMillis() - tWrite); } } } diff --git a/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownReportBuilder.java b/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownReportBuilder.java new file mode 100644 index 0000000..505906c --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownReportBuilder.java @@ -0,0 +1,260 @@ +package de.frigosped.dc.service; + +import de.frigosped.dc.model.*; +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +@ApplicationScoped +public class MarkdownReportBuilder { + + private static final String ICON_OK = "✅"; // ✅ + private static final String ICON_NOK = "❌"; // ❌ + private static final String ICON_WARN = "⚠️"; // ⚠️ + private static final String ICON_UNKL = "❓"; // ❓ + private static final String ICON_DOC = "📄"; // 📄 + + private static final DateTimeFormatter DATE_FMT = + DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + + /** + * Generates a Markdown report equivalent to DC_UTILS_PKG.generate_report(). + * @param project project meta (name, notify flags, completedAt) + * @param documents ordered list of project documents + * @param questionsByCatalog map catalogId → sorted questions + * @param resultsByDoc map docId → list of saved results + */ + public String build(OrdsProject project, + List documents, + Map> questionsByCatalog, + Map> resultsByDoc) { + + boolean showOk = !"N".equals(project.getNotifyOk()); + boolean showWarn = !"N".equals(project.getNotifyHinweis()); + boolean showDev = !"N".equals(project.getNotifyAbweichend()); + + // Flat lookup: questionId → question + Map questionById = new HashMap<>(); + for (List qs : questionsByCatalog.values()) { + for (OrdsQuestion q : qs) { + questionById.put(q.getQuestionId(), q); + } + } + + // Global counts (total_all unfiltered, filtered for summary section) + int totalAll = 0, devCnt = 0, warnCnt = 0, unklarCnt = 0; + for (List list : resultsByDoc.values()) { + for (ResultRequest r : list) { + totalAll++; + if (isDeviation(r)) devCnt++; + else if (isWarning(r)) warnCnt++; + else if (isUnklar(r)) unklarCnt++; + } + } + int okCnt = totalAll - devCnt - warnCnt - unklarCnt; + + // Filtered counts matching notify flags (same logic as PL/SQL) + int filtTotal = (showDev ? devCnt : 0) + + (showWarn ? warnCnt : 0) + + (showOk ? (okCnt + unklarCnt) : 0); + int filtDevCnt = showDev ? devCnt : 0; + int filtWarnCnt = showWarn ? warnCnt : 0; + int filtUnklCnt = showOk ? unklarCnt : 0; + int filtOkCnt = filtTotal - filtDevCnt - filtWarnCnt - filtUnklCnt; + + StringBuilder sb = new StringBuilder(); + + // Header + sb.append("# Prüfbericht: ").append(project.getName()).append("\n\n"); + LocalDateTime ts = project.getCompletedAt() != null + ? project.getCompletedAt() : LocalDateTime.now(); + sb.append("**Abgeschlossen:** ").append(ts.format(DATE_FMT)).append("\n\n"); + sb.append("---\n\n"); + + // Summary + sb.append("## Zusammenfassung\n\n"); + if (showOk) { + sb.append(ICON_OK).append(" OK: ").append(filtOkCnt).append("\n"); + if (filtUnklCnt > 0) { + sb.append(ICON_UNKL).append(" Unklar: ").append(filtUnklCnt).append("\n"); + } + } + if (showWarn) sb.append(ICON_WARN).append(" Hinweise: ").append(filtWarnCnt).append("\n"); + if (showDev) sb.append(ICON_NOK).append(" Abweichungen: ").append(filtDevCnt).append("\n"); + sb.append("**Gesamt: ").append(totalAll).append("**\n\n"); + sb.append("---\n\n"); + + // Results per document + sb.append("## Ergebnisse\n\n"); + + for (OrdsDocument doc : documents) { + sb.append("## ").append(ICON_DOC).append(" ").append(doc.getFilename()).append("\n\n"); + + List docResults = resultsByDoc.getOrDefault(doc.getId(), Collections.emptyList()); + + // Per-doc counts + int dDev = 0, dWarn = 0, dUnkl = 0; + for (ResultRequest r : docResults) { + if (isDeviation(r)) dDev++; + else if (isWarning(r)) dWarn++; + else if (isUnklar(r)) dUnkl++; + } + int dAll = docResults.size(); + int dOk = dAll - dDev - dWarn - dUnkl; + + if (showOk) { + sb.append(ICON_OK).append(" OK: ").append(dOk).append("\n"); + if (dUnkl > 0) sb.append(ICON_UNKL).append(" Unklar: ").append(dUnkl).append("\n"); + } + if (showWarn) sb.append(ICON_WARN).append(" Hinweise: ").append(dWarn).append("\n"); + if (showDev) sb.append(ICON_NOK).append(" Abweichungen: ").append(dDev).append("\n"); + sb.append("**Gesamt: ").append(dAll).append("**\n\n"); + sb.append("---\n\n"); + + // Results filtered + sorted by category order, then question order + List visible = docResults.stream() + .filter(r -> isVisible(r, showOk, showWarn, showDev)) + .sorted(Comparator + .comparingInt((ResultRequest r) -> catOrder(r, questionById)) + .thenComparingInt(r -> qOrder(r, questionById))) + .collect(Collectors.toList()); + + Long currentCatId = null; + for (ResultRequest r : visible) { + OrdsQuestion q = questionById.get(r.getQuestionId()); + if (q == null) continue; + + if (!Objects.equals(q.getCategoryId(), currentCatId)) { + currentCatId = q.getCategoryId(); + String catName = q.getCategoryName() != null ? q.getCategoryName() : "Allgemein"; + sb.append("\n### ").append(catName).append("\n\n"); + } + + String icon = icon(r); + sb.append("\n\n").append(icon).append(" **").append(q.getQuestionText()).append("**\n\n"); + + if (r.getAnswer() != null && !r.getAnswer().isBlank()) { + sb.append(r.getAnswer()).append("\n\n"); + } + if (r.getScore() != null) { + sb.append("*Score: ").append(r.getScore()).append("%*\n"); + } + if (r.getTheComment() != null && !r.getTheComment().isBlank()) { + sb.append("\n> ").append(r.getTheComment()).append("\n"); + } + sb.append("\n"); + } + + sb.append("---\n\n"); + } + + return sb.toString(); + } + + /** + * Generates a Markdown report for a single document. + */ + public String buildForDocument(OrdsProject project, + OrdsDocument doc, + Map questionById, + List docResults) { + + boolean showOk = !"N".equals(project.getNotifyOk()); + boolean showWarn = !"N".equals(project.getNotifyHinweis()); + boolean showDev = !"N".equals(project.getNotifyAbweichend()); + + int dDev = 0, dWarn = 0, dUnkl = 0; + for (ResultRequest r : docResults) { + if (isDeviation(r)) dDev++; + else if (isWarning(r)) dWarn++; + else if (isUnklar(r)) dUnkl++; + } + int dAll = docResults.size(); + int dOk = dAll - dDev - dWarn - dUnkl; + + StringBuilder sb = new StringBuilder(); + + sb.append("# Prüfbericht: ").append(project.getName()).append("\n\n"); + LocalDateTime ts = project.getCompletedAt() != null + ? project.getCompletedAt() : LocalDateTime.now(); + sb.append("**Abgeschlossen:** ").append(ts.format(DATE_FMT)).append("\n\n"); + sb.append("---\n\n"); + + sb.append("## ").append(ICON_DOC).append(" ").append(doc.getFilename()).append("\n\n"); + + if (showOk) { + sb.append(ICON_OK).append(" OK: ").append(dOk).append("\n"); + if (dUnkl > 0) sb.append(ICON_UNKL).append(" Unklar: ").append(dUnkl).append("\n"); + } + if (showWarn) sb.append(ICON_WARN).append(" Hinweise: ").append(dWarn).append("\n"); + if (showDev) sb.append(ICON_NOK).append(" Abweichungen: ").append(dDev).append("\n"); + sb.append("**Gesamt: ").append(dAll).append("**\n\n"); + sb.append("---\n\n"); + + List visible = docResults.stream() + .filter(r -> isVisible(r, showOk, showWarn, showDev)) + .sorted(Comparator + .comparingInt((ResultRequest r) -> catOrder(r, questionById)) + .thenComparingInt(r -> qOrder(r, questionById))) + .collect(Collectors.toList()); + + Long currentCatId = null; + for (ResultRequest r : visible) { + OrdsQuestion q = questionById.get(r.getQuestionId()); + if (q == null) continue; + + if (!Objects.equals(q.getCategoryId(), currentCatId)) { + currentCatId = q.getCategoryId(); + String catName = q.getCategoryName() != null ? q.getCategoryName() : "Allgemein"; + sb.append("\n### ").append(catName).append("\n\n"); + } + + String icon = icon(r); + sb.append("\n\n").append(icon).append(" **").append(q.getQuestionText()).append("**\n\n"); + + if (r.getAnswer() != null && !r.getAnswer().isBlank()) { + sb.append(r.getAnswer()).append("\n\n"); + } + if (r.getScore() != null) { + sb.append("*Score: ").append(r.getScore()).append("%*\n"); + } + if (r.getTheComment() != null && !r.getTheComment().isBlank()) { + sb.append("\n> ").append(r.getTheComment()).append("\n"); + } + sb.append("\n"); + } + + sb.append("---\n\n"); + return sb.toString(); + } + + private boolean isDeviation(ResultRequest r) { return Integer.valueOf(1).equals(r.getDeviation()); } + private boolean isWarning(ResultRequest r) { return Integer.valueOf(1).equals(r.getWarning()); } + private boolean isUnklar(ResultRequest r) { return "UNKLAR".equals(r.getResultType()) && !isDeviation(r) && !isWarning(r); } + + private boolean isVisible(ResultRequest r, boolean showOk, boolean showWarn, boolean showDev) { + if (isDeviation(r)) return showDev; + if (isWarning(r)) return showWarn; + return showOk; + } + + private String icon(ResultRequest r) { + if (isDeviation(r)) return ICON_NOK; + if (isWarning(r)) return ICON_WARN; + if (isUnklar(r)) return ICON_UNKL; + return ICON_OK; + } + + private int catOrder(ResultRequest r, Map byId) { + OrdsQuestion q = byId.get(r.getQuestionId()); + return (q != null && q.getCategoryOrderNr() != null) ? q.getCategoryOrderNr() : Integer.MAX_VALUE; + } + + private int qOrder(ResultRequest r, Map byId) { + OrdsQuestion q = byId.get(r.getQuestionId()); + return (q != null && q.getOrderNr() != null) ? q.getOrderNr() : Integer.MAX_VALUE; + } +} diff --git a/dc-backend/src/main/java/de/frigosped/dc/service/ReferenceDownloadService.java b/dc-backend/src/main/java/de/frigosped/dc/service/ReferenceDownloadService.java new file mode 100644 index 0000000..0eb2696 --- /dev/null +++ b/dc-backend/src/main/java/de/frigosped/dc/service/ReferenceDownloadService.java @@ -0,0 +1,281 @@ +package de.frigosped.dc.service; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; +import org.jsoup.Jsoup; + +import java.io.InputStream; +import java.net.InetAddress; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Lädt externe Referenz-Dokumente sicher herunter und extrahiert deren Text. + * + * Sicherheit (SSRF): Es werden ausschließlich öffentlich auflösbare Domains + * akzeptiert. Der Hostname wird per DNS aufgelöst und jede resultierende IP + * gegen private/Loopback/Link-Local/CGNAT/ULA-Bereiche geprüft. Redirects + * werden manuell verfolgt und bei jedem Hop neu validiert. Größe und Timeout + * sind begrenzt. + * + * Text-Extraktion: + * - PDF/DOCX/ODT/Plain → {@link DocumentProcessingService#extractText} + * - text/html → jsoup → Text + */ +@ApplicationScoped +public class ReferenceDownloadService { + + private static final Logger LOG = Logger.getLogger(ReferenceDownloadService.class); + private static final int MAX_REDIRECTS = 5; + + @Inject + DocumentProcessingService documentProcessingService; + + @ConfigProperty(name = "dc.references.enabled", defaultValue = "true") + boolean enabled; + + @ConfigProperty(name = "dc.references.max-size-bytes", defaultValue = "15000000") + long maxSizeBytes; + + @ConfigProperty(name = "dc.references.timeout", defaultValue = "30s") + Duration timeout; + + @ConfigProperty(name = "dc.references.allowed-content-types", + defaultValue = "application/pdf," + + "application/vnd.openxmlformats-officedocument.wordprocessingml.document," + + "application/msword," + + "application/vnd.oasis.opendocument.text," + + "text/plain,text/markdown,text/html") + String allowedContentTypes; + + /** Ergebnis eines erfolgreichen Referenz-Downloads. */ + public record DownloadedReference( + byte[] bytes, String mimeType, String text, String filename, String sourceUrl) {} + + public boolean isEnabled() { + return enabled; + } + + /** + * Lädt eine URL herunter und extrahiert den Text. Bei jedem Fehler (ungültige + * URL, SSRF-Blockade, Timeout, zu groß, nicht erlaubter Typ, leerer Text) wird + * {@link Optional#empty()} zurückgegeben – die Verarbeitung läuft weiter. + */ + public Optional fetch(String rawUrl, Long projectId) { + if (!enabled) return Optional.empty(); + + String url = normalize(rawUrl); + if (url == null) { + LOG.debugf("Referenz übersprungen – ungültige URL: %s", rawUrl); + return Optional.empty(); + } + + try { + HttpClient client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NEVER) + .connectTimeout(timeout) + .build(); + + URI current = URI.create(url); + HttpResponse response = null; + + for (int hop = 0; hop <= MAX_REDIRECTS; hop++) { + if (!isPublicHost(current.getHost())) { + LOG.warnf("Referenz blockiert (SSRF-Schutz): %s", current); + return Optional.empty(); + } + + HttpRequest request = HttpRequest.newBuilder(current) + .timeout(timeout) + .header("User-Agent", "Frigosped-DocumentCheck/1.0") + .GET() + .build(); + + response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + int status = response.statusCode(); + + if (status >= 300 && status < 400) { + Optional loc = response.headers().firstValue("location"); + if (loc.isEmpty()) break; + current = current.resolve(loc.get()); // relativ oder absolut + response.body().close(); + continue; + } + break; + } + + if (response == null || response.statusCode() != 200) { + LOG.debugf("Referenz übersprungen – HTTP %s: %s", + response == null ? "?" : response.statusCode(), url); + return Optional.empty(); + } + + String contentType = response.headers().firstValue("content-type") + .map(ct -> ct.split(";")[0].trim().toLowerCase()) + .orElse("application/octet-stream"); + + if (!isAllowedType(contentType)) { + LOG.debugf("Referenz übersprungen – Content-Type %s nicht erlaubt: %s", + contentType, url); + response.body().close(); + return Optional.empty(); + } + + byte[] bytes = readLimited(response.body(), maxSizeBytes); + if (bytes == null) { + LOG.warnf("Referenz übersprungen – größer als %d Bytes: %s", maxSizeBytes, url); + return Optional.empty(); + } + + String filename = deriveFilename(current, contentType); + String text = extract(bytes, contentType, filename, current.toString(), projectId); + + if (text == null || text.isBlank()) { + LOG.debugf("Referenz übersprungen – kein Text extrahierbar: %s", url); + return Optional.empty(); + } + + LOG.infof("Referenz geladen: %s (%s, %d Bytes, %d Zeichen)", + url, contentType, bytes.length, text.length()); + return Optional.of(new DownloadedReference(bytes, contentType, text, filename, url)); + + } catch (Exception e) { + LOG.debugf("Referenz-Download fehlgeschlagen (%s): %s", url, e.getMessage()); + return Optional.empty(); + } + } + + // ========================================================================= + // Text-Extraktion + // ========================================================================= + + private String extract(byte[] bytes, String contentType, String filename, + String url, Long projectId) { + if (contentType.equals("text/html")) { + String html = new String(bytes, StandardCharsets.UTF_8); + return Jsoup.parse(html, url).text(); + } + return documentProcessingService.extractText(bytes, contentType, filename, projectId); + } + + // ========================================================================= + // SSRF-Schutz + // ========================================================================= + + /** + * true, wenn der Host öffentlich auflösbar ist und KEINE der aufgelösten + * IP-Adressen in einem privaten/internen Bereich liegt. + */ + boolean isPublicHost(String host) { + if (host == null || host.isBlank()) return false; + try { + InetAddress[] addresses = InetAddress.getAllByName(host); + if (addresses.length == 0) return false; + for (InetAddress addr : addresses) { + if (!isPublicAddress(addr)) return false; + } + return true; + } catch (Exception e) { + return false; // nicht auflösbar → blockieren + } + } + + private boolean isPublicAddress(InetAddress addr) { + if (addr.isLoopbackAddress() || addr.isAnyLocalAddress() + || addr.isLinkLocalAddress() || addr.isSiteLocalAddress() + || addr.isMulticastAddress()) { + return false; + } + byte[] b = addr.getAddress(); + if (b.length == 4) { + int o0 = b[0] & 0xFF, o1 = b[1] & 0xFF; + if (o0 == 0) return false; // 0.0.0.0/8 + if (o0 == 127) return false; // Loopback (Sicherheit) + if (o0 == 100 && o1 >= 64 && o1 <= 127) return false; // CGNAT 100.64.0.0/10 + if (o0 == 169 && o1 == 254) return false; // Link-Local + } else if (b.length == 16) { + if ((b[0] & 0xFE) == 0xFC) return false; // IPv6 ULA fc00::/7 + } + return true; + } + + // ========================================================================= + // Hilfsmethoden + // ========================================================================= + + /** Normalisiert eine vom LLM gelieferte URL (trimmt Markdown/Satzzeichen). */ + private String normalize(String raw) { + if (raw == null) return null; + String u = raw.trim(); + if (u.startsWith("<") && u.endsWith(">")) u = u.substring(1, u.length() - 1).trim(); + // typische nachgestellte Satzzeichen entfernen + while (!u.isEmpty() && ").,;\"'".indexOf(u.charAt(u.length() - 1)) >= 0) { + u = u.substring(0, u.length() - 1); + } + if (!u.regionMatches(true, 0, "http://", 0, 7) + && !u.regionMatches(true, 0, "https://", 0, 8)) { + return null; + } + return u; + } + + /** Liest den Stream bis zum Limit; gibt null zurück wenn das Limit überschritten wird. */ + private byte[] readLimited(InputStream in, long limit) throws Exception { + try (in) { + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + long total = 0; + int n; + while ((n = in.read(buf)) != -1) { + total += n; + if (total > limit) return null; + out.write(buf, 0, n); + } + return out.toByteArray(); + } + } + + private boolean isAllowedType(String contentType) { + List allowed = Arrays.stream(allowedContentTypes.split(",")) + .map(String::trim).map(String::toLowerCase).toList(); + return allowed.contains(contentType); + } + + private String deriveFilename(URI uri, String contentType) { + String path = uri.getPath(); + String name = null; + if (path != null && !path.isBlank()) { + int slash = path.lastIndexOf('/'); + String last = slash >= 0 ? path.substring(slash + 1) : path; + if (!last.isBlank()) name = last; + } + if (name == null || name.isBlank()) { + name = (uri.getHost() != null ? uri.getHost() : "referenz") + extensionFor(contentType); + } else if (!name.contains(".")) { + name = name + extensionFor(contentType); + } + // Dateinamen säubern + return name.replaceAll("[^A-Za-z0-9_äöüÄÖÜß.-]", "_"); + } + + private String extensionFor(String contentType) { + return switch (contentType) { + case "application/pdf" -> ".pdf"; + case "application/vnd.openxmlformats-officedocument.wordprocessingml.document" -> ".docx"; + case "application/msword" -> ".doc"; + case "application/vnd.oasis.opendocument.text" -> ".odt"; + case "text/html" -> ".html"; + case "text/markdown" -> ".md"; + default -> ".txt"; + }; + } +} diff --git a/dc-backend/src/main/resources/application.properties b/dc-backend/src/main/resources/application.properties index 2baab1e..0bbe228 100644 --- a/dc-backend/src/main/resources/application.properties +++ b/dc-backend/src/main/resources/application.properties @@ -64,6 +64,14 @@ dc.ai.catalog.temperature=0.1 dc.ai.ocr.timeout=120s dc.ai.ocr.max-retries=2 +# ============================================================================= +# Mistral OCR REST Client +# ============================================================================= +quarkus.rest-client.mistral-ocr.url=https://api.mistral.ai +quarkus.rest-client.mistral-ocr.scope=jakarta.inject.Singleton +quarkus.rest-client.mistral-ocr.connect-timeout=10000 +quarkus.rest-client.mistral-ocr.read-timeout=120000 + # ============================================================================= # ORDS REST Client # ============================================================================= @@ -71,11 +79,25 @@ dc.ai.ocr.max-retries=2 quarkus.rest-client.ords-api.url=http://ords:8080/ords/ai_dev quarkus.rest-client.ords-api.scope=jakarta.inject.Singleton quarkus.rest-client.ords-api.connect-timeout=10000 -quarkus.rest-client.ords-api.read-timeout=60000 +quarkus.rest-client.ords-api.read-timeout=600000 # ORDS-Authentifizierung (OAuth2 Bearer Token – leer = kein Auth in Dev) dc.ords.bearer-token= +# ============================================================================= +# Externe Referenz-Dokumente (Links im Dokument → herunterladen & mitbewerten) +# ============================================================================= +# Feature an/aus +dc.references.enabled=true +# Max. Anzahl heruntergeladener Referenzen pro Hauptdokument +dc.references.max-per-document=5 +# Max. Dateigröße pro Referenz (Bytes); größere Downloads werden verworfen +dc.references.max-size-bytes=15000000 +# Verbindungs-/Lese-Timeout pro Download +dc.references.timeout=30s +# Erlaubte Content-Types (alles andere wird verworfen) +dc.references.allowed-content-types=application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/msword,application/vnd.oasis.opendocument.text,text/plain,text/markdown,text/html + # ============================================================================= # Logging # ============================================================================= diff --git a/dc-backend/target/build-metrics.json b/dc-backend/target/build-metrics.json deleted file mode 100644 index fcb5059..0000000 --- a/dc-backend/target/build-metrics.json +++ /dev/null @@ -1 +0,0 @@ -{"duration":1724,"records":[{"duration":299,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#build","started":"13:16:44.350","dependents":[426],"id":371,"thread":"build-6"},{"duration":224,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#getAllExtensions","started":"13:16:44.688","dependents":[413,400,410,389,411],"id":388,"thread":"build-5"},{"duration":222,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#generateConfigClass","started":"13:16:43.904","dependents":[],"id":249,"thread":"build-30"},{"duration":209,"stepId":"io.quarkus.vertx.http.deployment.webjar.WebJarProcessor#processWebJarDevMode","started":"13:16:44.913","dependents":[412,426,411],"id":410,"thread":"build-9"},{"duration":207,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#getSwaggerUiFinalDestination","started":"13:16:44.249","dependents":[410],"id":339,"thread":"build-16"},{"duration":183,"stepId":"io.quarkus.arc.deployment.ArcProcessor#generateResources","started":"13:16:44.650","dependents":[378,425,409],"id":377,"thread":"build-6"},{"duration":181,"stepId":"io.quarkus.deployment.steps.ApplicationIndexBuildStep#build","started":"13:16:43.920","dependents":[338,346,250,390,247,347,407],"id":245,"thread":"build-8"},{"duration":180,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#setupConsole","started":"13:16:43.919","dependents":[248,246,244,253],"id":243,"thread":"build-14"},{"duration":154,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createBuildTimeData","started":"13:16:45.027","dependents":[415,414],"id":413,"thread":"build-33"},{"duration":149,"stepId":"io.quarkus.arc.deployment.ArcProcessor#registerBeans","started":"13:16:44.355","dependents":[346,349,342,345,358,344,341,350,355,397,348,343,354,347,353],"id":340,"thread":"build-23"},{"duration":146,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#build","started":"13:16:45.448","dependents":[],"id":426,"thread":"build-6"},{"duration":121,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createBuildTimeConstJsTemplate","started":"13:16:45.182","dependents":[415,416],"id":414,"thread":"build-16"},{"duration":117,"stepId":"io.quarkus.arc.deployment.ArcProcessor#validate","started":"13:16:44.528","dependents":[365,362,374,377,366,372,409,363,364,367],"id":361,"thread":"build-21"},{"duration":113,"stepId":"io.quarkus.devui.deployment.welcome.WelcomeProcessor#createWelcomePages","started":"13:16:44.913","dependents":[413],"id":400,"thread":"build-39"},{"duration":106,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#setupEndpoints","started":"13:16:44.841","dependents":[393,408,399,425,391,397,426,409],"id":390,"thread":"build-33"},{"duration":105,"stepId":"io.quarkus.deployment.index.ApplicationArchiveBuildStep#build","started":"13:16:44.101","dependents":[324,255,251,252,420,409,292,363,253],"id":250,"thread":"build-6"},{"duration":98,"stepId":"io.quarkus.deployment.steps.CompiledJavaVersionBuildStep#compiledJavaVersion","started":"13:16:43.917","dependents":[390],"id":238,"thread":"build-40"},{"duration":94,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#logConsoleCommand","started":"13:16:43.879","dependents":[395],"id":208,"thread":"build-4"},{"duration":87,"stepId":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor#setupClientProxies","started":"13:16:44.950","dependents":[408,425,426,409],"id":407,"thread":"build-6"},{"duration":87,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#createVertxThreadFactory","started":"13:16:43.889","dependents":[230,426],"id":216,"thread":"build-18"},{"duration":84,"stepId":"io.quarkus.deployment.steps.ConfigDescriptionBuildStep#createConfigDescriptions","started":"13:16:43.904","dependents":[321,311],"id":233,"thread":"build-16"},{"duration":84,"stepId":"io.quarkus.netty.deployment.NettyProcessor#eagerlyInitClass","started":"13:16:43.876","dependents":[426],"id":197,"thread":"build-3"},{"duration":81,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#registerDevUiHandlers","started":"13:16:45.354","dependents":[418,419,426],"id":417,"thread":"build-33"},{"duration":80,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#produceNamedHttpSecurityPolicies","started":"13:16:43.896","dependents":[355,426,354,353],"id":214,"thread":"build-20"},{"duration":79,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#setupAdditionalBeans","started":"13:16:43.896","dependents":[324,338,426],"id":213,"thread":"build-2"},{"duration":79,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#bodyHandler","started":"13:16:43.943","dependents":[421,426],"id":240,"thread":"build-6"},{"duration":77,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupLoggingRuntimeInit","started":"13:16:44.224","dependents":[423,425,426],"id":323,"thread":"build-21"},{"duration":72,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#buildStatic","started":"13:16:43.907","dependents":[426],"id":226,"thread":"build-27"},{"duration":72,"stepId":"io.quarkus.mutiny.deployment.MutinyProcessor#buildTimeInit","started":"13:16:43.888","dependents":[426],"id":196,"thread":"build-19"},{"duration":72,"stepId":"io.quarkus.deployment.dev.HotDeploymentWatchedFileBuildStep#setupWatchedFileHotDeployment","started":"13:16:43.934","dependents":[422],"id":236,"thread":"build-23"},{"duration":72,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#registerMetrics","started":"13:16:43.905","dependents":[323,426],"id":219,"thread":"build-5"},{"duration":64,"stepId":"io.quarkus.deployment.dev.io.NioThreadPoolDevModeProcessor#setupTCCL","started":"13:16:43.910","dependents":[426],"id":209,"thread":"build-26"},{"duration":63,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupLoggingStaticInit","started":"13:16:43.912","dependents":[426],"id":210,"thread":"build-25"},{"duration":63,"stepId":"io.quarkus.deployment.steps.BannerProcessor#recordBanner","started":"13:16:43.945","dependents":[323,426],"id":237,"thread":"build-21"},{"duration":62,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#buildTimeRunTimeConfig","started":"13:16:43.911","dependents":[373,425],"id":205,"thread":"build-11"},{"duration":62,"stepId":"io.quarkus.virtual.threads.VirtualThreadsProcessor#setup","started":"13:16:43.919","dependents":[324,338,355,426,354,353],"id":227,"thread":"build-7"},{"duration":62,"stepId":"io.quarkus.deployment.steps.PreloadClassesBuildStep#preInit","started":"13:16:43.911","dependents":[426],"id":206,"thread":"build-34"},{"duration":60,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initFormAuth","started":"13:16:43.918","dependents":[324,338,418,419,426],"id":222,"thread":"build-17"},{"duration":57,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#generateMappings","started":"13:16:44.233","dependents":[365,342,425,341,367],"id":322,"thread":"build-22"},{"duration":54,"stepId":"io.quarkus.arc.deployment.ArcProcessor#buildCompatibleExtensions","started":"13:16:43.889","dependents":[324,338],"id":178,"thread":"build-9"},{"duration":52,"stepId":"io.quarkus.arc.deployment.BeanArchiveProcessor#build","started":"13:16:44.274","dependents":[338,349,330,325,386,333,331,407,350,326,327,390,332,397,329],"id":324,"thread":"build-42"},{"duration":50,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#ioThreadDetector","started":"13:16:43.927","dependents":[223,426],"id":220,"thread":"build-44"},{"duration":49,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#classLoaderHack","started":"13:16:43.909","dependents":[426],"id":193,"thread":"build-31"},{"duration":49,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createIndexHtmlTemplate","started":"13:16:45.303","dependents":[416],"id":415,"thread":"build-33"},{"duration":49,"stepId":"io.quarkus.deployment.ide.IdeProcessor#detectRunningIdeProcesses","started":"13:16:43.910","dependents":[201],"id":194,"thread":"build-29"},{"duration":49,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#createVertxContextHandlers","started":"13:16:43.930","dependents":[232,230,426],"id":224,"thread":"build-36"},{"duration":46,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#currentContextFactory","started":"13:16:43.929","dependents":[378,426],"id":211,"thread":"build-43"},{"duration":43,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#helpCommand","started":"13:16:43.879","dependents":[395],"id":119,"thread":"build-6"},{"duration":41,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#setupDeployment","started":"13:16:44.984","dependents":[421,404,401,418,425,402,419,405,426,403],"id":399,"thread":"build-33"},{"duration":39,"stepId":"io.quarkus.arc.deployment.devui.ArcDevModeApiProcessor#collectBeanInfo","started":"13:16:44.646","dependents":[375],"id":374,"thread":"build-53"},{"duration":38,"stepId":"io.quarkus.arc.deployment.ArcProcessor#quarkusMain","started":"13:16:43.878","dependents":[324,338,147],"id":102,"thread":"build-8"},{"duration":38,"stepId":"io.quarkus.devui.deployment.menu.ConfigurationProcessor#registerJsonRpcService","started":"13:16:43.938","dependents":[229,355,426,354,353,304,225],"id":218,"thread":"build-53"},{"duration":37,"stepId":"io.quarkus.devui.deployment.menu.ConfigurationProcessor#registerConfigs","started":"13:16:44.248","dependents":[426],"id":321,"thread":"build-5"},{"duration":36,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#setMtlsCertificateRoleProperties","started":"13:16:43.941","dependents":[426],"id":221,"thread":"build-42"},{"duration":33,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#addRestClientBeans","started":"13:16:44.237","dependents":[324,426],"id":318,"thread":"build-43"},{"duration":33,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#serverSerializers","started":"13:16:44.950","dependents":[399,425,426],"id":398,"thread":"build-16"},{"duration":33,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#eventLoopCount","started":"13:16:43.943","dependents":[424,426],"id":215,"thread":"build-41"},{"duration":33,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#releaseConfigOnShutdown","started":"13:16:43.941","dependents":[426],"id":212,"thread":"build-22"},{"duration":31,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerProvidersFromAnnotations","started":"13:16:44.224","dependents":[324,315,425,361,367],"id":314,"thread":"build-6"},{"duration":30,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#build_68c59e5d5fe4deeaa2b750dd2b2f234cee36c063","started":"13:16:43.990","dependents":[421,384,355,424,242,351,422,419,426,354,353,241],"id":239,"thread":"build-27"},{"duration":28,"stepId":"io.quarkus.vertx.http.deployment.console.ConsoleProcessor#setupConsole","started":"13:16:43.922","dependents":[422],"id":188,"thread":"build-45"},{"duration":28,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#quitCommand","started":"13:16:43.894","dependents":[395],"id":120,"thread":"build-10"},{"duration":27,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#handleCustomAnnotatedMethods","started":"13:16:44.242","dependents":[324,338,319,320],"id":317,"thread":"build-23"},{"duration":25,"stepId":"io.quarkus.vertx.http.deployment.ManagementInterfaceSecurityProcessor#createManagementAuthMechHandler","started":"13:16:43.948","dependents":[381,426,217],"id":207,"thread":"build-51"},{"duration":24,"stepId":"io.quarkus.arc.deployment.ArcProcessor#initialize","started":"13:16:44.330","dependents":[350,340,374],"id":338,"thread":"build-44"},{"duration":23,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#findAllJsonRPCMethods","started":"13:16:44.224","dependents":[387,414],"id":304,"thread":"build-40"},{"duration":23,"stepId":"io.quarkus.devui.deployment.build.BuildMetricsDevUIProcessor#create","started":"13:16:43.931","dependents":[426],"id":192,"thread":"build-49"},{"duration":22,"stepId":"io.quarkus.deployment.steps.ClassPathSystemPropBuildStep#set","started":"13:16:43.931","dependents":[426],"id":191,"thread":"build-10"},{"duration":22,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#startTesting","started":"13:16:44.100","dependents":[323,422],"id":248,"thread":"build-40"},{"duration":22,"stepId":"io.quarkus.devui.deployment.ide.IdeProcessor#createOpenInIDEService","started":"13:16:43.963","dependents":[229,418,419,426],"id":228,"thread":"build-45"},{"duration":21,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#filterMultipleVertxInstancesWarning","started":"13:16:43.896","dependents":[172,323],"id":104,"thread":"build-7"},{"duration":21,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#jacksonSupport","started":"13:16:44.236","dependents":[355,426,354,353],"id":316,"thread":"build-42"},{"duration":20,"stepId":"io.quarkus.deployment.steps.RuntimeConfigSetupBuildStep#setupRuntimeConfig","started":"13:16:43.921","dependents":[221,202,424,412,240,357,237,419,356,382,403,199,252,204,239,422,421,362,384,323,381,423,402,426,352,230,242,336,215,212,405],"id":174,"thread":"build-13"},{"duration":20,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#cors","started":"13:16:43.941","dependents":[421,405,426],"id":199,"thread":"build-12"},{"duration":19,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigRootsAsBeans","started":"13:16:43.917","dependents":[355,354,353],"id":159,"thread":"build-37"},{"duration":18,"stepId":"io.quarkus.deployment.steps.ClassTransformingBuildStep#handleClassTransformation","started":"13:16:45.037","dependents":[],"id":409,"thread":"build-16"},{"duration":18,"stepId":"io.quarkus.vertx.http.deployment.GeneratedStaticResourcesProcessor#process","started":"13:16:43.944","dependents":[420,418,419,426],"id":202,"thread":"build-50"},{"duration":18,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#generateBuilders","started":"13:16:44.648","dependents":[425],"id":373,"thread":"build-42"},{"duration":17,"stepId":"io.quarkus.devui.deployment.menu.DependenciesProcessor#createAppDeps","started":"13:16:43.918","dependents":[413],"id":154,"thread":"build-32"},{"duration":17,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#checkForBuildTimeConfigChange","started":"13:16:43.946","dependents":[426],"id":204,"thread":"build-37"},{"duration":16,"stepId":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor#registerInvocationCallbacks","started":"13:16:44.224","dependents":[426],"id":280,"thread":"build-8"},{"duration":16,"stepId":"io.quarkus.deployment.steps.CombinedIndexBuildStep#build","started":"13:16:44.207","dependents":[298,317,314,392,309,275,280,278,304,256,297,323,288,257,261,295,276,279,294,269,296,300,263,318,273,338,267,330,303,277,264,260,274,316,308,265,259,272,281,342,319,315,333,292,341,293,312,262,271,322,408,268,258,299,270],"id":255,"thread":"build-30"},{"duration":16,"stepId":"io.quarkus.jsonp.deployment.JsonpProcessor#build","started":"13:16:43.894","dependents":[425,426],"id":85,"thread":"build-22"},{"duration":16,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#watchConfigFiles","started":"13:16:43.900","dependents":[236],"id":95,"thread":"build-13"},{"duration":15,"stepId":"io.quarkus.devui.deployment.logstream.LogStreamProcessor#createJsonRPCService","started":"13:16:43.927","dependents":[229,304,225],"id":175,"thread":"build-33"},{"duration":15,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#registerAuthMechanismSelectionInterceptor","started":"13:16:44.233","dependents":[346,309,426,313],"id":308,"thread":"build-44"},{"duration":15,"stepId":"io.quarkus.arc.deployment.CommandLineArgumentsProcessor#commandLineArgs","started":"13:16:43.883","dependents":[324,338,355,354,353],"id":55,"thread":"build-11"},{"duration":14,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#registerVerticleClasses","started":"13:16:44.223","dependents":[425],"id":275,"thread":"build-14"},{"duration":14,"stepId":"io.quarkus.rest.client.reactive.deployment.devservices.DevServicesRestClientHttpProxyProcessor#registerDefaultProvider","started":"13:16:43.931","dependents":[305],"id":183,"thread":"build-15"},{"duration":14,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#beanDefiningAnnotations","started":"13:16:43.888","dependents":[324,338,147],"id":68,"thread":"build-16"},{"duration":14,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#addAutoFilters","started":"13:16:44.335","dependents":[371],"id":337,"thread":"build-23"},{"duration":14,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#addRoutingCtxToSecurityEventsForCdiBeans","started":"13:16:43.946","dependents":[426],"id":198,"thread":"build-15"},{"duration":13,"stepId":"io.quarkus.config.yaml.deployment.ConfigYamlProcessor#watchYamlConfig","started":"13:16:43.896","dependents":[236],"id":83,"thread":"build-26"},{"duration":13,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#responseStatusSupport","started":"13:16:43.896","dependents":[390],"id":81,"thread":"build-25"},{"duration":13,"stepId":"io.quarkus.deployment.ide.IdeProcessor#detectIdeFiles","started":"13:16:43.898","dependents":[201],"id":89,"thread":"build-28"},{"duration":12,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#createHttpAuthenticationHandler","started":"13:16:43.947","dependents":[426,382,203],"id":195,"thread":"build-9"},{"duration":12,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#testConsoleCommand","started":"13:16:44.235","dependents":[395],"id":303,"thread":"build-36"},{"duration":12,"stepId":"io.quarkus.smallrye.openapi.deployment.devui.OpenApiDevUIProcessor#pages","started":"13:16:43.917","dependents":[376,388],"id":131,"thread":"build-36"},{"duration":12,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#addMpClientEnricher","started":"13:16:43.899","dependents":[407],"id":88,"thread":"build-14"},{"duration":12,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#customExceptionMappers","started":"13:16:43.932","dependents":[317],"id":179,"thread":"build-35"},{"duration":11,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#setupConfigOverride","started":"13:16:43.926","dependents":[],"id":167,"thread":"build-12"},{"duration":11,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#build_9d6b7122fb368970c50c3a870d1f672392cd8afb","started":"13:16:43.940","dependents":[425,200],"id":189,"thread":"build-47"},{"duration":11,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupStackTraceFormatter","started":"13:16:44.207","dependents":[421,254,323],"id":253,"thread":"build-8"},{"duration":11,"stepId":"io.quarkus.rest.client.reactive.jackson.deployment.RestClientReactiveJacksonProcessor#additionalProviders_d467f0796ff6c3d28e57d6f18c92f27cf1d4298e","started":"13:16:43.886","dependents":[314],"id":48,"thread":"build-12"},{"duration":11,"stepId":"io.quarkus.deployment.steps.CurateOutcomeBuildStep#curateOutcome","started":"13:16:43.904","dependents":[116,413,238,400,154,109,376,180,190,136,414,388,253,169,229,262,250,105,410,245,191,409,304],"id":93,"thread":"build-15"},{"duration":10,"stepId":"io.quarkus.netty.deployment.NettyProcessor#setNettyMachineId","started":"13:16:43.915","dependents":[426],"id":124,"thread":"build-38"},{"duration":10,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#mainClassBuildStep","started":"13:16:44.233","dependents":[409],"id":292,"thread":"build-20"},{"duration":10,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#responseHeaderSupport","started":"13:16:43.900","dependents":[390],"id":86,"thread":"build-11"},{"duration":9,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#unremovableBeans","started":"13:16:43.910","dependents":[361,367],"id":112,"thread":"build-12"},{"duration":9,"stepId":"io.quarkus.rest.client.reactive.jackson.deployment.RestClientReactiveJacksonProcessor#additionalProviders_3f333413be4c0802e30f75e67ce4dd421dc2e40b","started":"13:16:43.895","dependents":[324,338,398,397,407,396],"id":72,"thread":"build-23"},{"duration":9,"stepId":"io.quarkus.vertx.deployment.VertxJsonProcessor#registerJacksonSerDeser","started":"13:16:43.875","dependents":[278],"id":11,"thread":"build-2"},{"duration":9,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#compressionSupport","started":"13:16:43.911","dependents":[390],"id":115,"thread":"build-22"},{"duration":9,"stepId":"io.quarkus.arc.deployment.SyntheticBeansProcessor#initRegular","started":"13:16:44.515","dependents":[358],"id":355,"thread":"build-53"},{"duration":9,"stepId":"io.quarkus.rest.client.reactive.deployment.devconsole.RestClientReactiveDevUIProcessor#createJsonRPCServiceForCache","started":"13:16:43.889","dependents":[304,225],"id":51,"thread":"build-14"},{"duration":9,"stepId":"io.quarkus.deployment.steps.ClassPathSystemPropBuildStep#produce","started":"13:16:43.921","dependents":[191],"id":136,"thread":"build-28"},{"duration":9,"stepId":"io.quarkus.arc.deployment.SyntheticBeansProcessor#initRuntime","started":"13:16:44.515","dependents":[358,422,357,426,356],"id":354,"thread":"build-44"},{"duration":9,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#smallryeOpenApiIndex","started":"13:16:44.326","dependents":[371,337,334,336,335],"id":333,"thread":"build-5"},{"duration":9,"stepId":"io.quarkus.deployment.steps.NativeImageConfigBuildStep#build","started":"13:16:43.952","dependents":[426],"id":200,"thread":"build-45"},{"duration":9,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#unknownConfigFiles","started":"13:16:44.207","dependents":[426],"id":252,"thread":"build-14"},{"duration":8,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#buildExclusions","started":"13:16:44.239","dependents":[333],"id":307,"thread":"build-5"},{"duration":8,"stepId":"io.quarkus.rest.client.reactive.deployment.devservices.DevServicesRestClientHttpProxyProcessor#determineRequiredProxies","started":"13:16:44.238","dependents":[305],"id":300,"thread":"build-45"},{"duration":8,"stepId":"io.quarkus.deployment.pkg.steps.FileSystemResourcesBuildStep#notNormalMode","started":"13:16:43.920","dependents":[],"id":128,"thread":"build-15"},{"duration":8,"stepId":"io.quarkus.arc.deployment.ArcProcessor#setupExecutor","started":"13:16:43.988","dependents":[426],"id":235,"thread":"build-45"},{"duration":8,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#vertxIntegration","started":"13:16:43.877","dependents":[398,397,407,396],"id":15,"thread":"build-7"},{"duration":8,"stepId":"io.quarkus.deployment.steps.ThreadPoolSetup#createExecutor","started":"13:16:43.979","dependents":[232,421,235,239,234,231,426],"id":230,"thread":"build-5"},{"duration":7,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#beans","started":"13:16:43.887","dependents":[324,338],"id":38,"thread":"build-13"},{"duration":7,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#overrideContextInternalInterfaceToAddSafeGuards","started":"13:16:43.926","dependents":[409],"id":144,"thread":"build-23"},{"duration":7,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initBasicAuth","started":"13:16:43.902","dependents":[324,338,337,336],"id":80,"thread":"build-12"},{"duration":7,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#finalizeRouter","started":"13:16:45.437","dependents":[423,422,426],"id":421,"thread":"build-16"},{"duration":6,"stepId":"io.quarkus.deployment.CollectionClassProcessor#setupCollectionClasses","started":"13:16:43.880","dependents":[425],"id":19,"thread":"build-9"},{"duration":6,"stepId":"io.quarkus.stork.deployment.SmallRyeStorkProcessor#unremoveableBeans","started":"13:16:43.910","dependents":[361,367],"id":101,"thread":"build-21"},{"duration":6,"stepId":"io.quarkus.netty.deployment.NettyProcessor#cleanupUnsafeLog","started":"13:16:43.897","dependents":[172,323],"id":69,"thread":"build-17"},{"duration":6,"stepId":"io.quarkus.deployment.steps.ReflectiveHierarchyStep#build","started":"13:16:45.037","dependents":[425],"id":408,"thread":"build-39"},{"duration":5,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#scanResources","started":"13:16:44.237","dependents":[338,317,290,392,289,386,291,285,407,287,390,399,332,284,286],"id":283,"thread":"build-7"},{"duration":5,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#gatherMvnpmJars","started":"13:16:43.916","dependents":[417,415],"id":116,"thread":"build-33"},{"duration":5,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#exceptionMappers","started":"13:16:43.883","dependents":[319],"id":27,"thread":"build-10"},{"duration":5,"stepId":"io.quarkus.arc.deployment.ConfigStaticInitBuildSteps#transformConfigProducer","started":"13:16:43.881","dependents":[338],"id":17,"thread":"build-5"},{"duration":5,"stepId":"io.quarkus.vertx.http.deployment.devmode.NotFoundProcessor#routeNotFound","started":"13:16:45.437","dependents":[426],"id":420,"thread":"build-33"},{"duration":4,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#registerAnnotatedUserDefinedRuntimeFilters","started":"13:16:44.335","dependents":[355,425,426,354,353],"id":335,"thread":"build-42"},{"duration":4,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#registerAutoSecurityFilter","started":"13:16:44.335","dependents":[355,426,354,353],"id":336,"thread":"build-6"},{"duration":4,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#setUpDefaultMediaType","started":"13:16:43.904","dependents":[407],"id":77,"thread":"build-33"},{"duration":4,"stepId":"io.quarkus.vertx.http.deployment.devmode.ArcDevProcessor#registerRoutes","started":"13:16:44.646","dependents":[420,377,418,419,426],"id":372,"thread":"build-5"},{"duration":4,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#checkMixingStacks","started":"13:16:43.948","dependents":[422],"id":190,"thread":"build-28"},{"duration":4,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#build","started":"13:16:43.988","dependents":[355,426,354,353],"id":234,"thread":"build-34"},{"duration":4,"stepId":"io.quarkus.arc.deployment.SplitPackageProcessor#splitPackageDetection","started":"13:16:44.207","dependents":[377],"id":251,"thread":"build-40"},{"duration":4,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#unremovable","started":"13:16:44.233","dependents":[324,338,361,367],"id":273,"thread":"build-16"},{"duration":4,"stepId":"io.quarkus.devui.deployment.logstream.LogStreamProcessor#handler","started":"13:16:44.218","dependents":[323,426],"id":254,"thread":"build-14"},{"duration":4,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#cacheControlSupport","started":"13:16:43.894","dependents":[390],"id":54,"thread":"build-21"},{"duration":4,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#handler","started":"13:16:45.029","dependents":[420,406,426],"id":405,"thread":"build-16"},{"duration":4,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createRelocationMap","started":"13:16:43.926","dependents":[415],"id":135,"thread":"build-10"},{"duration":4,"stepId":"io.quarkus.netty.deployment.NettyProcessor#build","started":"13:16:43.912","dependents":[425,200],"id":100,"thread":"build-23"},{"duration":4,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#createJsonRpcRouter","started":"13:16:44.841","dependents":[426],"id":387,"thread":"build-39"},{"duration":4,"stepId":"io.quarkus.tls.CertificatesProcessor#initializeCertificate","started":"13:16:44.510","dependents":[421,355,426,354,353],"id":352,"thread":"build-40"},{"duration":4,"stepId":"io.quarkus.deployment.ExtensionLoader#config","started":"13:16:43.896","dependents":[365,346,202,424,131,195,357,419,339,356,403,252,250,147,204,254,410,399,372,80,422,367,328,423,77,93,288,219,426,300,233,74,373,240,76,75,249,199,324,73,78,417,316,308,421,315,381,335,371,312,193,230,242,390,409,203,82,100,337,221,248,374,314,412,237,382,98,377,210,205,329,115,251,91,384,323,159,222,99,402,352,261,407,111,107,378,243,318,245,405,108,338,375,239,227,110,413,109,211,141,138,201,292,253,207,322,336,215,212,347,224],"id":62,"thread":"build-24"},{"duration":4,"stepId":"io.quarkus.deployment.dev.ConfigureDisableInstrumentationBuildStep#configure","started":"13:16:43.945","dependents":[422],"id":187,"thread":"build-38"},{"duration":4,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#registerSwaggerUiHandler","started":"13:16:45.122","dependents":[418,419,426],"id":412,"thread":"build-16"},{"duration":4,"stepId":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor#initializeStorkFilter","started":"13:16:43.926","dependents":[324,338,255,425],"id":137,"thread":"build-6"},{"duration":4,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#securityExceptionMappers","started":"13:16:43.913","dependents":[319],"id":103,"thread":"build-32"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#resolveRolesAllowedConfigExpressions","started":"13:16:44.237","dependents":[362,355,426,354,353],"id":281,"thread":"build-41"},{"duration":3,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#findEnablementStereotypes","started":"13:16:44.233","dependents":[274,279,272,277],"id":270,"thread":"build-26"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#contextInjection","started":"13:16:43.936","dependents":[324,338,328,331],"id":170,"thread":"build-6"},{"duration":3,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#preinitializeRouter","started":"13:16:44.021","dependents":[355,419,426,354,353],"id":242,"thread":"build-40"},{"duration":3,"stepId":"io.quarkus.deployment.steps.AdditionalClassLoaderResourcesBuildStep#appendAdditionalClassloaderResources","started":"13:16:43.896","dependents":[255],"id":57,"thread":"build-5"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#fileHandling","started":"13:16:43.895","dependents":[398,397,407],"id":56,"thread":"build-13"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#buildResourceInterceptors","started":"13:16:44.270","dependents":[324,338,390,332,399,397],"id":320,"thread":"build-40"},{"duration":3,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#setupAuthenticationMechanisms","started":"13:16:43.960","dependents":[324,338,421,337,336,405,426],"id":203,"thread":"build-49"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#scanForParameterContainers","started":"13:16:44.243","dependents":[390,407],"id":298,"thread":"build-8"},{"duration":3,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#generateCustomizer","started":"13:16:44.234","dependents":[324],"id":278,"thread":"build-18"},{"duration":3,"stepId":"io.quarkus.deployment.recording.AnnotationProxyBuildStep#build","started":"13:16:44.102","dependents":[351],"id":247,"thread":"build-14"},{"duration":3,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#registerVerticleClasses","started":"13:16:44.234","dependents":[425],"id":276,"thread":"build-23"},{"duration":3,"stepId":"io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsProcessor#collectInterceptedStaticMethods","started":"13:16:44.505","dependents":[350,383,361,367],"id":349,"thread":"build-44"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#transformEndpoints","started":"13:16:44.326","dependents":[338],"id":332,"thread":"build-23"},{"duration":3,"stepId":"io.quarkus.devui.deployment.menu.ReadmeProcessor#createReadmePage","started":"13:16:43.888","dependents":[413],"id":34,"thread":"build-2"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForContextResolvers","started":"13:16:44.243","dependents":[324,338,394,399,425],"id":299,"thread":"build-16"},{"duration":3,"stepId":"io.quarkus.devui.deployment.build.BuildMetricsDevUIProcessor#additionalBeans","started":"13:16:43.927","dependents":[324,338],"id":134,"thread":"build-39"},{"duration":3,"stepId":"io.quarkus.arc.deployment.ArcProcessor#initializeContainer","started":"13:16:44.834","dependents":[426,379],"id":378,"thread":"build-59"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForExceptionMappers","started":"13:16:44.270","dependents":[324,338,399,425],"id":319,"thread":"build-42"},{"duration":3,"stepId":"io.quarkus.arc.deployment.ShutdownBuildSteps#unremovableBeans","started":"13:16:43.931","dependents":[361,367],"id":151,"thread":"build-28"},{"duration":3,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#build","started":"13:16:44.507","dependents":[422,426,356,352],"id":351,"thread":"build-53"},{"duration":3,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setUpDefaultLevels","started":"13:16:43.906","dependents":[373,323],"id":84,"thread":"build-17"},{"duration":3,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#defaultUnwrappedExceptions","started":"13:16:43.926","dependents":[319],"id":132,"thread":"build-21"},{"duration":3,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#logging","started":"13:16:43.900","dependents":[84],"id":70,"thread":"build-5"},{"duration":2,"stepId":"io.quarkus.deployment.SecureRandomProcessor#registerReflectiveMethods","started":"13:16:43.935","dependents":[425],"id":164,"thread":"build-21"},{"duration":2,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#registerBean","started":"13:16:43.917","dependents":[324,338],"id":106,"thread":"build-14"},{"duration":2,"stepId":"io.quarkus.devui.deployment.menu.ContinuousTestingProcessor#createJsonRPCService","started":"13:16:43.931","dependents":[229,304,225],"id":146,"thread":"build-24"},{"duration":2,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#createKnownInternalImportMap","started":"13:16:43.921","dependents":[415],"id":121,"thread":"build-42"},{"duration":2,"stepId":"io.quarkus.resteasy.reactive.common.deployment.JaxrsMethodsProcessor#jaxrsMethods","started":"13:16:43.898","dependents":[315],"id":58,"thread":"build-15"},{"duration":2,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#configFiles","started":"13:16:43.929","dependents":[236],"id":141,"thread":"build-41"},{"duration":2,"stepId":"io.quarkus.deployment.dev.IsolatedDevModeMain$AddApplicationClassPredicateBuildStep$1@3e39a242","started":"13:16:43.927","dependents":[338,390],"id":130,"thread":"build-35"},{"duration":2,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#setupEndpoints","started":"13:16:44.841","dependents":[425,398,397,407,396],"id":386,"thread":"build-9"},{"duration":2,"stepId":"io.quarkus.vertx.http.deployment.ManagementInterfaceSecurityProcessor#setupAuthenticationMechanisms","started":"13:16:43.974","dependents":[324,338,421,426],"id":217,"thread":"build-34"},{"duration":2,"stepId":"io.quarkus.devui.deployment.menu.ContinuousTestingProcessor#continuousTestingState","started":"13:16:44.841","dependents":[426],"id":385,"thread":"build-16"},{"duration":2,"stepId":"io.quarkus.deployment.pkg.steps.JarResultBuildStep#outputTarget","started":"13:16:43.917","dependents":[371,128,141,253],"id":111,"thread":"build-41"},{"duration":2,"stepId":"io.quarkus.arc.deployment.AutoProducerMethodsProcessor#annotationTransformer","started":"13:16:44.326","dependents":[338],"id":329,"thread":"build-6"},{"duration":2,"stepId":"io.quarkus.deployment.recording.substitutions.AdditionalSubstitutionsBuildStep#additionalSubstitutions","started":"13:16:43.885","dependents":[426],"id":23,"thread":"build-14"},{"duration":2,"stepId":"io.quarkus.arc.deployment.AutoAddScopeProcessor#annotationTransformer","started":"13:16:44.327","dependents":[338,361,367],"id":331,"thread":"build-44"},{"duration":2,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#handleApplication","started":"13:16:44.239","dependents":[319,298,297,425,386,398,407,293,295,294,390,296,399,320,299],"id":288,"thread":"build-18"},{"duration":2,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForInterceptors","started":"13:16:44.243","dependents":[320],"id":297,"thread":"build-27"},{"duration":2,"stepId":"io.quarkus.devui.deployment.menu.ContinuousTestingProcessor#createContinuousTestingPages","started":"13:16:43.888","dependents":[413],"id":33,"thread":"build-5"},{"duration":2,"stepId":"io.quarkus.netty.deployment.NettyProcessor#registerEventLoopBeans","started":"13:16:44.022","dependents":[355,426,354,353],"id":241,"thread":"build-21"},{"duration":2,"stepId":"io.quarkus.config.yaml.deployment.ConfigYamlProcessor#feature","started":"13:16:43.883","dependents":[426],"id":14,"thread":"build-12"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ConfigStaticInitBuildSteps#registerBeans","started":"13:16:43.893","dependents":[324,338],"id":46,"thread":"build-20"},{"duration":2,"stepId":"io.quarkus.arc.deployment.SyntheticBeansProcessor#initStatic","started":"13:16:44.515","dependents":[358,426],"id":353,"thread":"build-16"},{"duration":2,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#generateRestClientConfigBuilder","started":"13:16:44.234","dependents":[373],"id":266,"thread":"build-17"},{"duration":2,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#supportMixins","started":"13:16:44.234","dependents":[355,425,426,354,353],"id":269,"thread":"build-45"},{"duration":2,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#collectEventConsumers","started":"13:16:44.505","dependents":[351,358],"id":348,"thread":"build-40"},{"duration":2,"stepId":"io.quarkus.arc.deployment.TestsAsBeansProcessor#testClassBeans","started":"13:16:43.888","dependents":[324,338],"id":31,"thread":"build-7"},{"duration":2,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#additionalBean","started":"13:16:43.977","dependents":[324,338,255],"id":225,"thread":"build-34"},{"duration":2,"stepId":"io.quarkus.arc.deployment.ArcProcessor#exposeCustomScopeNames","started":"13:16:43.925","dependents":[324,338,147,133,318,168,347,331,329],"id":126,"thread":"build-22"},{"duration":2,"stepId":"io.quarkus.vertx.deployment.EventBusCodecProcessor#registerCodecs","started":"13:16:44.326","dependents":[351,425],"id":330,"thread":"build-21"},{"duration":2,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#doNotRemoveVertxOptionsCustomizers","started":"13:16:43.888","dependents":[361,367],"id":32,"thread":"build-10"},{"duration":2,"stepId":"io.quarkus.arc.deployment.devui.ArcDevUIProcessor#pages","started":"13:16:44.685","dependents":[376,388],"id":375,"thread":"build-42"},{"duration":2,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setUpDefaultLogCleanupFilters","started":"13:16:43.937","dependents":[373],"id":172,"thread":"build-38"},{"duration":2,"stepId":"io.quarkus.netty.deployment.NettyProcessor#cleanupMacDNSInLog","started":"13:16:43.906","dependents":[172,323],"id":79,"thread":"build-32"},{"duration":2,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#openSocket","started":"13:16:45.446","dependents":[425,426],"id":424,"thread":"build-16"},{"duration":2,"stepId":"io.quarkus.arc.deployment.devui.ArcDevUIProcessor#registerMonitoringComponents","started":"13:16:43.932","dependents":[324,338],"id":147,"thread":"build-38"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#deprioritizeLegacyProviders","started":"13:16:43.887","dependents":[398,407],"id":28,"thread":"build-15"},{"duration":1,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#unlessBuildProperty","started":"13:16:44.238","dependents":[288,307,282],"id":279,"thread":"build-17"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveDevModeProcessor#openCommand","started":"13:16:44.947","dependents":[395],"id":393,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#registerOpenApiSchemaClassesForReflection","started":"13:16:44.335","dependents":[408,425],"id":334,"thread":"build-21"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#handleJsonAnnotations","started":"13:16:44.948","dependents":[394,425,426],"id":392,"thread":"build-33"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#additionalBeans","started":"13:16:44.245","dependents":[324,338,425],"id":302,"thread":"build-53"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#runtimeConfiguration","started":"13:16:45.026","dependents":[426,403],"id":402,"thread":"build-54"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ArcProcessor#loggerProducer","started":"13:16:43.930","dependents":[324,338],"id":139,"thread":"build-38"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#additionalBeans","started":"13:16:43.879","dependents":[324,338],"id":5,"thread":"build-5"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#convertRoutes","started":"13:16:45.033","dependents":[418,419],"id":406,"thread":"build-39"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForParamConverters_dcdfdd2a310a09abe5ee3f0ed2b2bc49f36f3d07","started":"13:16:44.245","dependents":[324,338,390,399,425],"id":301,"thread":"build-41"},{"duration":1,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#setupExceptionHandler","started":"13:16:44.100","dependents":[253],"id":246,"thread":"build-21"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateConfigMappingsInjectionPoints","started":"13:16:44.646","dependents":[370,373],"id":367,"thread":"build-43"},{"duration":1,"stepId":"io.quarkus.deployment.ide.IdeProcessor#effectiveIde","started":"13:16:43.959","dependents":[413,228,246,253],"id":201,"thread":"build-31"},{"duration":1,"stepId":"io.quarkus.deployment.index.ApplicationArchiveBuildStep#addConfiguredIndexedDependencies","started":"13:16:43.913","dependents":[250],"id":91,"thread":"build-17"},{"duration":1,"stepId":"io.quarkus.arc.deployment.init.InitializationTaskProcessor#startApplicationInitializer","started":"13:16:44.525","dependents":[426],"id":357,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setProperty","started":"13:16:43.914","dependents":[426],"id":97,"thread":"build-36"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForFeatures","started":"13:16:44.243","dependents":[399,302],"id":295,"thread":"build-53"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ArcProcessor#registerSyntheticObservers","started":"13:16:44.525","dependents":[360,377,425,359,361,367],"id":358,"thread":"build-40"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#recordableConstructor","started":"13:16:43.877","dependents":[426],"id":3,"thread":"build-6"},{"duration":1,"stepId":"io.quarkus.mutiny.deployment.MutinyProcessor#runtimeInit","started":"13:16:43.988","dependents":[426],"id":232,"thread":"build-7"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.StaticResourcesProcessor#collectStaticResources","started":"13:16:43.946","dependents":[384],"id":185,"thread":"build-28"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#asyncSupport","started":"13:16:43.894","dependents":[390],"id":45,"thread":"build-15"},{"duration":1,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#featureAndCapability","started":"13:16:43.885","dependents":[180,426],"id":20,"thread":"build-2"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#jsonDefault","started":"13:16:43.882","dependents":[390],"id":12,"thread":"build-13"},{"duration":1,"stepId":"io.quarkus.deployment.steps.CapabilityAggregationStep#provideCapabilities","started":"13:16:43.938","dependents":[180],"id":169,"thread":"build-21"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForDynamicFeatures","started":"13:16:44.243","dependents":[399,302],"id":296,"thread":"build-14"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateStaticInitConfigProperty","started":"13:16:44.647","dependents":[425,426],"id":368,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#generateConfigProperties","started":"13:16:44.234","dependents":[365,342,425,341,367],"id":267,"thread":"build-7"},{"duration":1,"stepId":"io.quarkus.arc.deployment.StartupBuildSteps#addScope","started":"13:16:43.928","dependents":[331],"id":133,"thread":"build-42"},{"duration":1,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#setupRequestCollectingFilter","started":"13:16:43.903","dependents":[320],"id":71,"thread":"build-15"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateRuntimeConfigProperty","started":"13:16:44.647","dependents":[425,426],"id":369,"thread":"build-22"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ArcProcessor#notifyBeanContainerListeners","started":"13:16:44.838","dependents":[380,426],"id":379,"thread":"build-6"},{"duration":1,"stepId":"io.quarkus.config.yaml.deployment.ConfigYamlProcessor#yamlConfig","started":"13:16:43.921","dependents":[373],"id":118,"thread":"build-21"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#detectBasicAuthImplicitlyRequired","started":"13:16:44.505","dependents":[426],"id":346,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.deployment.steps.BlockingOperationControlBuildStep#blockingOP","started":"13:16:43.978","dependents":[426],"id":223,"thread":"build-5"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.StaticResourcesProcessor#runtimeInit","started":"13:16:44.841","dependents":[421,426],"id":384,"thread":"build-6"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#initializeRouter","started":"13:16:45.435","dependents":[421,420,426],"id":419,"thread":"build-6"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#shouldNotRemoveHttpServerOptionsCustomizers","started":"13:16:43.923","dependents":[361,367],"id":122,"thread":"build-39"},{"duration":1,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#suppressNonRuntimeConfigChanged","started":"13:16:43.876","dependents":[204],"id":1,"thread":"build-4"},{"duration":1,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#register","started":"13:16:44.233","dependents":[324,338,408,425],"id":262,"thread":"build-4"},{"duration":1,"stepId":"io.quarkus.devui.deployment.menu.ReadmeProcessor#createJsonRPCServiceForCache","started":"13:16:43.887","dependents":[304,225],"id":26,"thread":"build-9"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.GeneratedStaticResourcesProcessor#produceResources","started":"13:16:43.939","dependents":[185],"id":171,"thread":"build-50"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ReflectiveBeanClassesProcessor#implicitReflectiveBeanClasses","started":"13:16:44.505","dependents":[377],"id":345,"thread":"build-21"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ObservabilityProcessor#preAuthFailureFilter","started":"13:16:45.026","dependents":[421,404,405,426],"id":401,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#httpRoot","started":"13:16:43.904","dependents":[371,421,420,393,406,417,188],"id":75,"thread":"build-29"},{"duration":1,"stepId":"io.quarkus.stork.deployment.SmallRyeStorkProcessor#checkThatTheKubernetesExtensionIsUsedWhenKubernetesServiceDiscoveryInOnTheClasspath","started":"13:16:43.946","dependents":[356],"id":186,"thread":"build-35"},{"duration":1,"stepId":"io.quarkus.deployment.steps.RegisterForReflectionBuildStep#build","started":"13:16:44.233","dependents":[408,425],"id":264,"thread":"build-5"},{"duration":1,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#additionalBeans","started":"13:16:43.901","dependents":[324,338],"id":67,"thread":"build-27"},{"duration":1,"stepId":"io.quarkus.deployment.steps.ShutdownListenerBuildStep#setupShutdown","started":"13:16:45.445","dependents":[426],"id":423,"thread":"build-33"},{"duration":1,"stepId":"io.quarkus.arc.deployment.WrongAnnotationUsageProcessor#detect","started":"13:16:44.505","dependents":[377],"id":347,"thread":"build-53"},{"duration":1,"stepId":"io.quarkus.devui.deployment.build.BuildMetricsDevUIProcessor#createJsonRPCService","started":"13:16:43.920","dependents":[304,225],"id":117,"thread":"build-41"},{"duration":1,"stepId":"io.quarkus.deployment.steps.CapabilityAggregationStep#aggregateCapabilities","started":"13:16:43.944","dependents":[338,424,184,195,181,264,401,399,182,308,186,198,185,281,319,190,244,407,371,207,312,334,390,408,318,273,203],"id":180,"thread":"build-48"},{"duration":1,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#mapPageBuildTimeData","started":"13:16:44.688","dependents":[414],"id":376,"thread":"build-53"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#additionalReflection","started":"13:16:44.950","dependents":[425],"id":397,"thread":"build-33"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#providersFromClasspath","started":"13:16:43.886","dependents":[398,397,407,396],"id":21,"thread":"build-16"},{"duration":1,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#additionalBean","started":"13:16:43.876","dependents":[324,338],"id":2,"thread":"build-5"},{"duration":1,"stepId":"io.quarkus.arc.deployment.ArcProcessor#signalBeanContainerReady","started":"13:16:44.839","dependents":[421,384,385,383,381,386,398,426,382,407,420,390,387,399,422],"id":380,"thread":"build-59"},{"duration":1,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#registerCustomExceptionMappers","started":"13:16:43.882","dependents":[317],"id":9,"thread":"build-10"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#integrateEagerSecurity","started":"13:16:44.248","dependents":[390],"id":312,"thread":"build-44"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.DependenciesProcessor#createBuildTimeActions","started":"13:16:43.918","dependents":[229],"id":105,"thread":"build-23"},{"duration":0,"stepId":"io.quarkus.deployment.JniProcessor#setupJni","started":"13:16:43.920","dependents":[200],"id":110,"thread":"build-15"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#addDefaultAuthFailureHandler","started":"13:16:45.028","dependents":[421,405,426],"id":404,"thread":"build-39"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#produceEagerSecurityInterceptorStorage","started":"13:16:44.249","dependents":[355,426,354,353],"id":313,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#installCliCommands","started":"13:16:44.949","dependents":[422],"id":395,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#cleanupVertxWarnings","started":"13:16:43.879","dependents":[172,323],"id":4,"thread":"build-9"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ProfileBuildStep#defaultProfile","started":"13:16:43.936","dependents":[373],"id":157,"thread":"build-52"},{"duration":0,"stepId":"io.quarkus.netty.deployment.NettyProcessor#registerQualifiers","started":"13:16:43.943","dependents":[324,338],"id":177,"thread":"build-51"},{"duration":0,"stepId":"io.quarkus.deployment.steps.CurateOutcomeBuildStep#removeResources","started":"13:16:43.920","dependents":[409],"id":109,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#vetoMPConfigProperties","started":"13:16:43.887","dependents":[338],"id":25,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.credentials.CredentialsProcessor#unremoveable","started":"13:16:43.933","dependents":[361,367],"id":148,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#mapDeploymentMethods","started":"13:16:43.986","dependents":[387,304],"id":229,"thread":"build-7"},{"duration":0,"stepId":"io.quarkus.arc.deployment.HotDeploymentConfigBuildStep#startup","started":"13:16:43.940","dependents":[187],"id":173,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.EndpointsProcessor#createEndpointsPage","started":"13:16:43.920","dependents":[413],"id":113,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ShutdownBuildSteps#registerShutdownObservers","started":"13:16:44.527","dependents":[361],"id":360,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#handleSseEventFilter","started":"13:16:44.326","dependents":[425],"id":326,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setMinLevelForInitialConfigurator","started":"13:16:43.919","dependents":[426],"id":107,"thread":"build-23"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#quarkusApplication","started":"13:16:44.224","dependents":[324,338],"id":256,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.deployment.execannotations.ExecutionModelAnnotationsProcessor#check","started":"13:16:44.256","dependents":[],"id":315,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#activateSslNativeSupport","started":"13:16:43.886","dependents":[200],"id":16,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#marker","started":"13:16:43.882","dependents":[250],"id":8,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#unremovableBeans","started":"13:16:44.242","dependents":[361,367],"id":285,"thread":"build-14"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerProviderBeans","started":"13:16:44.236","dependents":[324,338],"id":265,"thread":"build-53"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#additionalProviders","started":"13:16:44.949","dependents":[398,397,407,396],"id":394,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#perClassExceptionMapperSupport","started":"13:16:44.242","dependents":[338],"id":287,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#applicationSpecificUnwrappedExceptions","started":"13:16:44.237","dependents":[319],"id":271,"thread":"build-2"},{"duration":0,"stepId":"io.quarkus.arc.deployment.AutoInjectFieldProcessor#autoInjectQualifiers","started":"13:16:44.326","dependents":[328,331],"id":325,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#registerSecurityInterceptors","started":"13:16:43.945","dependents":[324,338],"id":181,"thread":"build-51"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setUpDarkeningDefault","started":"13:16:43.933","dependents":[373],"id":143,"thread":"build-50"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#determineRegisteredRestClients","started":"13:16:44.233","dependents":[266,300,318],"id":261,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#jacksonRegistered","started":"13:16:43.893","dependents":[72],"id":35,"thread":"build-2"},{"duration":0,"stepId":"io.quarkus.deployment.steps.DevModeBuildStep#watchChanges","started":"13:16:43.908","dependents":[236],"id":78,"thread":"build-34"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#addAllWriteableMarker","started":"13:16:44.950","dependents":[409],"id":396,"thread":"build-54"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingWithPanacheProcessor#process","started":"13:16:44.233","dependents":[409],"id":258,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.deployment.console.ConsoleProcessor#missingDevUIMessageHandler","started":"13:16:44.100","dependents":[422],"id":244,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#sharedStateListener","started":"13:16:43.881","dependents":[248],"id":6,"thread":"build-11"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.devui.ResteasyReactiveDevUIProcessor#createPages","started":"13:16:43.900","dependents":[376,388],"id":63,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.vertx.deployment.EventConsumerMethodsProcessor#eventConsumerMethods","started":"13:16:43.881","dependents":[315],"id":7,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#preventLoggerContention","started":"13:16:43.898","dependents":[84],"id":52,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.devconsole.RestClientReactiveDevUIProcessor#beans","started":"13:16:43.894","dependents":[324,338],"id":42,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.deployment.ForkJoinPoolProcessor#setProperty","started":"13:16:43.928","dependents":[426],"id":127,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.GeneratedStaticResourcesProcessor#devMode","started":"13:16:43.933","dependents":[236,202,171],"id":145,"thread":"build-47"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#generateCustomProducer","started":"13:16:44.243","dependents":[324,338],"id":291,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.deployment.steps.DevServicesConfigBuildStep#setup","started":"13:16:44.247","dependents":[321,373,422,339,311],"id":310,"thread":"build-36"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#initializeRolesAllowedConfigExp","started":"13:16:44.646","dependents":[426],"id":362,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.devui.deployment.BuildTimeContentProcessor#loadAllBuildTimeTemplates","started":"13:16:45.353","dependents":[417],"id":416,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#unlessBuildProfile","started":"13:16:44.237","dependents":[288,307,282],"id":272,"thread":"build-53"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initMtlsClientAuth","started":"13:16:43.904","dependents":[324,338],"id":73,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#config","started":"13:16:43.937","dependents":[373],"id":166,"thread":"build-37"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#registerContextPropagation","started":"13:16:43.906","dependents":[226],"id":76,"thread":"build-31"},{"duration":0,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#ifBuildProperty","started":"13:16:44.237","dependents":[288,307,282],"id":274,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#feature","started":"13:16:43.889","dependents":[426],"id":29,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#unremovableAsyncObserverExceptionHandlers","started":"13:16:43.927","dependents":[361,367],"id":125,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.arc.deployment.LookupConditionsProcessor#suppressConditionsGenerators","started":"13:16:44.326","dependents":[338],"id":327,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#brandingFiles","started":"13:16:43.899","dependents":[236],"id":60,"thread":"build-30"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ExecutorServiceProcessor#executorServiceBean","started":"13:16:43.988","dependents":[355,354,353],"id":231,"thread":"build-36"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#buildSetup","started":"13:16:43.894","dependents":[426],"id":40,"thread":"build-7"},{"duration":0,"stepId":"io.quarkus.arc.deployment.LifecycleEventsBuildStep#startupEvent","started":"13:16:45.445","dependents":[424,426],"id":422,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#logCleanup","started":"13:16:43.937","dependents":[172,323],"id":161,"thread":"build-28"},{"duration":0,"stepId":"io.quarkus.deployment.ConstructorPropertiesProcessor#build","started":"13:16:44.233","dependents":[425],"id":259,"thread":"build-18"},{"duration":0,"stepId":"io.quarkus.stork.deployment.SmallRyeStorkProcessor#initializeStork","started":"13:16:44.525","dependents":[426],"id":356,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.arc.deployment.StartupBuildSteps#unremovableBeans","started":"13:16:43.893","dependents":[361,367],"id":39,"thread":"build-10"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#pathInterfaceImpls","started":"13:16:44.242","dependents":[324,338],"id":289,"thread":"build-8"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#configureHandlers","started":"13:16:45.028","dependents":[426],"id":403,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.BuildMetricsProcessor#createBuildMetricsPages","started":"13:16:43.937","dependents":[413],"id":163,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.ConfigurationProcessor#createConfigurationPages","started":"13:16:44.248","dependents":[413],"id":311,"thread":"build-53"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#unremovableBeans","started":"13:16:43.895","dependents":[361,367],"id":44,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#registerBean","started":"13:16:43.897","dependents":[324,338],"id":49,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.deployment.ExtensionLoader#booleanSupplierFactory","started":"13:16:43.937","dependents":[169],"id":165,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.swaggerui.deployment.SwaggerUiProcessor#feature","started":"13:16:43.909","dependents":[426],"id":82,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#feature","started":"13:16:43.886","dependents":[426],"id":18,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.devmode.NotFoundProcessor#resourceNotFoundDataAvailable","started":"13:16:43.897","dependents":[324,338],"id":50,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.deployment.steps.DevServicesConfigBuildStep#deprecated","started":"13:16:43.936","dependents":[310],"id":160,"thread":"build-38"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerProvidersInstances","started":"13:16:44.224","dependents":[314],"id":257,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ReflectiveHierarchyStep#ignoreJavaClassWarnings","started":"13:16:43.894","dependents":[408],"id":41,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.ExtensionsProcessor#createExtensionsPages","started":"13:16:44.913","dependents":[413],"id":389,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveVertxWebSocketIntegrationProcessor#scanner","started":"13:16:43.895","dependents":[390],"id":43,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.arc.deployment.UnremovableAnnotationsProcessor#unremovableBeans","started":"13:16:43.936","dependents":[361,367],"id":155,"thread":"build-46"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#searchForProviders","started":"13:16:43.945","dependents":[250],"id":182,"thread":"build-28"},{"duration":0,"stepId":"io.quarkus.devui.deployment.logstream.LogStreamProcessor#additionalBean","started":"13:16:43.911","dependents":[324,338],"id":90,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.deployment.execannotations.ExecutionModelAnnotationsProcessor#devuiJsonRpcServices","started":"13:16:43.910","dependents":[315],"id":87,"thread":"build-25"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#handleClassLevelExceptionMappers","started":"13:16:44.242","dependents":[390,425],"id":286,"thread":"build-53"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.jackson.deployment.RestClientReactiveJacksonProcessor#feature","started":"13:16:43.915","dependents":[426],"id":92,"thread":"build-33"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#registerHttpAuthMechanismAnnotations","started":"13:16:43.936","dependents":[308],"id":158,"thread":"build-39"},{"duration":0,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#applicationReflection","started":"13:16:43.920","dependents":[425],"id":114,"thread":"build-23"},{"duration":0,"stepId":"io.quarkus.arc.deployment.TestsAsBeansProcessor#testAnnotations","started":"13:16:43.901","dependents":[324,338,147],"id":65,"thread":"build-29"},{"duration":0,"stepId":"io.quarkus.deployment.steps.MainClassBuildStep#setupVersionField","started":"13:16:43.898","dependents":[425],"id":53,"thread":"build-29"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigClasses","started":"13:16:44.648","dependents":[426],"id":370,"thread":"build-44"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.devconsole.RestClientReactiveDevUIProcessor#create","started":"13:16:43.935","dependents":[376,388],"id":153,"thread":"build-46"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#resourceIndex","started":"13:16:44.236","dependents":[324,283,391],"id":268,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsProcessor#callInitializer","started":"13:16:44.841","dependents":[426],"id":383,"thread":"build-54"},{"duration":0,"stepId":"io.quarkus.netty.deployment.NettyProcessor#limitArenaSize","started":"13:16:43.916","dependents":[426],"id":99,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#validateAsyncObserverExceptionHandlers","started":"13:16:44.646","dependents":[377],"id":364,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.ManagementInterfaceSecurityProcessor#initializeAuthMechanismHandler","started":"13:16:44.841","dependents":[426],"id":381,"thread":"build-53"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#setMinimalNettyMaxOrderSize","started":"13:16:43.884","dependents":[100,99],"id":10,"thread":"build-14"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerHeaderFactoryBeans","started":"13:16:44.233","dependents":[324,338],"id":263,"thread":"build-34"},{"duration":0,"stepId":"io.quarkus.deployment.steps.PreloadClassesBuildStep#registerPreInitClasses","started":"13:16:43.926","dependents":[],"id":123,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#createSynthBeansForConfiguredInjectionPoints","started":"13:16:44.505","dependents":[355,426,354,353],"id":344,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.EndpointsProcessor#createJsonRPCService","started":"13:16:43.943","dependents":[304,225],"id":176,"thread":"build-28"},{"duration":0,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#runtimeOnly","started":"13:16:43.915","dependents":[373],"id":94,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.devui.deployment.DevUIProcessor#createAllRoutes","started":"13:16:45.122","dependents":[417],"id":411,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#announceFeature","started":"13:16:43.936","dependents":[426],"id":156,"thread":"build-24"},{"duration":0,"stepId":"io.quarkus.smallrye.context.deployment.SmallRyeContextPropagationProcessor#transformInjectionPoint","started":"13:16:43.887","dependents":[338],"id":24,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#autoAddScope","started":"13:16:43.932","dependents":[331],"id":142,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.deployment.steps.BannerProcessor#watchBannerChanges","started":"13:16:43.905","dependents":[236],"id":74,"thread":"build-27"},{"duration":0,"stepId":"io.quarkus.smallrye.openapi.deployment.SmallRyeOpenApiProcessor#contributeClassesToIndex","started":"13:16:43.937","dependents":[255],"id":162,"thread":"build-39"},{"duration":0,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#ifBuildProfile","started":"13:16:44.238","dependents":[288,307,282],"id":277,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#registerSafeDuplicatedContextInterceptor","started":"13:16:43.893","dependents":[324,338],"id":36,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.deployment.dev.testing.TestTracingProcessor#handle","started":"13:16:43.901","dependents":[172,323],"id":64,"thread":"build-30"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerCustomConfigBeanTypes","started":"13:16:44.505","dependents":[355,425,354,353],"id":343,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.arc.deployment.BuildTimeEnabledProcessor#conditionTransformer","started":"13:16:44.239","dependents":[338],"id":282,"thread":"build-23"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ShutdownBuildSteps#addScope","started":"13:16:43.938","dependents":[331],"id":168,"thread":"build-48"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigMappingsBean","started":"13:16:44.505","dependents":[358],"id":341,"thread":"build-43"},{"duration":0,"stepId":"io.quarkus.arc.deployment.StartupBuildSteps#registerStartupObservers","started":"13:16:44.527","dependents":[361],"id":359,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerCompressionInterceptors","started":"13:16:43.893","dependents":[425],"id":37,"thread":"build-7"},{"duration":0,"stepId":"io.quarkus.vertx.deployment.VertxProcessor#reinitializeClassesForNetty","started":"13:16:43.916","dependents":[200],"id":96,"thread":"build-28"},{"duration":0,"stepId":"io.quarkus.deployment.pkg.steps.NativeImageBuildStep#ignoreBuildPropertyChanges","started":"13:16:43.887","dependents":[204],"id":22,"thread":"build-18"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ArcProcessor#launchMode","started":"13:16:43.935","dependents":[324,338],"id":152,"thread":"build-39"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ObserverValidationProcessor#validateApplicationObserver","started":"13:16:44.646","dependents":[377],"id":363,"thread":"build-22"},{"duration":0,"stepId":"io.quarkus.arc.deployment.LoggingBeanSupportProcessor#discoveredComponents","started":"13:16:43.931","dependents":[324,338,147],"id":140,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#validateConfigPropertiesInjectionPoints","started":"13:16:44.646","dependents":[370],"id":365,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#unremovableContextMethodParams","started":"13:16:44.243","dependents":[361,367],"id":290,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.devservices.DevServicesRestClientHttpProxyProcessor#start","started":"13:16:44.246","dependents":[306,310],"id":305,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#scanForParamConverters_59e3169e3a646b7fcf3083416f558434b73816c5","started":"13:16:44.244","dependents":[301],"id":294,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveProcessor#additionalAsyncTypeMethodScanners","started":"13:16:43.934","dependents":[390],"id":149,"thread":"build-6"},{"duration":0,"stepId":"io.quarkus.arc.deployment.HotDeploymentConfigBuildStep#configFile","started":"13:16:43.900","dependents":[236],"id":59,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.arc.deployment.devui.ArcDevUIProcessor#createJsonRPCService","started":"13:16:43.928","dependents":[304,225],"id":129,"thread":"build-46"},{"duration":0,"stepId":"io.quarkus.deployment.logging.LoggingResourceProcessor#setupLogFilters","started":"13:16:43.885","dependents":[172,323],"id":13,"thread":"build-13"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#notFoundRoutes","started":"13:16:45.435","dependents":[420],"id":418,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveCDIProcessor#subResourcesAsBeans","started":"13:16:44.242","dependents":[324,338,361,367],"id":284,"thread":"build-16"},{"duration":0,"stepId":"io.quarkus.deployment.SslProcessor#setupNativeSsl","started":"13:16:43.919","dependents":[200],"id":108,"thread":"build-21"},{"duration":0,"stepId":"io.quarkus.devui.deployment.menu.DevServicesProcessor#createDevServicesPages","started":"13:16:44.247","dependents":[413],"id":306,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.VertxHttpProcessor#frameworkRoot","started":"13:16:43.916","dependents":[413,421,406,412,131,228,414,419,339,121,113,393,417,415,372,405,188],"id":98,"thread":"build-35"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#handleFieldSecurity","started":"13:16:44.948","dependents":[392],"id":391,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#initializeAuthenticationHandler","started":"13:16:44.841","dependents":[426],"id":382,"thread":"build-37"},{"duration":0,"stepId":"io.quarkus.rest.client.reactive.deployment.RestClientReactiveProcessor#registerQueryParamStyleForConfig","started":"13:16:43.889","dependents":[249],"id":30,"thread":"build-20"},{"duration":0,"stepId":"io.quarkus.jackson.deployment.JacksonProcessor#autoRegisterModules","started":"13:16:44.234","dependents":[278],"id":260,"thread":"build-17"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProcessor#reflection","started":"13:16:43.900","dependents":[425],"id":61,"thread":"build-12"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#configPropertyInjectionPoints","started":"13:16:44.646","dependents":[368,425,369],"id":366,"thread":"build-44"},{"duration":0,"stepId":"io.quarkus.arc.deployment.ConfigBuildStep#registerConfigPropertiesBean","started":"13:16:44.505","dependents":[358],"id":342,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.vertx.core.deployment.VertxCoreProcessor#filterNettyHostsFileParsingWarn","started":"13:16:43.896","dependents":[172,323],"id":47,"thread":"build-15"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ConfigGenerationBuildStep#runtimeOverrideConfig","started":"13:16:43.934","dependents":[373],"id":150,"thread":"build-51"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.devui.ResteasyReactiveDevUIProcessor#createJsonRPCService","started":"13:16:43.900","dependents":[304,225],"id":66,"thread":"build-31"},{"duration":0,"stepId":"io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsProcessor#processInterceptedStaticMethods","started":"13:16:44.509","dependents":[425,409],"id":350,"thread":"build-40"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.server.deployment.ObservabilityProcessor#methodScanner","started":"13:16:43.945","dependents":[390],"id":184,"thread":"build-35"},{"duration":0,"stepId":"io.quarkus.arc.deployment.AutoInjectFieldProcessor#annotationTransformer","started":"13:16:44.326","dependents":[338],"id":328,"thread":"build-42"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ApplicationInfoBuildStep#create","started":"13:16:43.931","dependents":[426],"id":138,"thread":"build-49"},{"duration":0,"stepId":"io.quarkus.vertx.http.deployment.HttpSecurityProcessor#collectInterceptedMethods","started":"13:16:44.248","dependents":[312,313],"id":309,"thread":"build-5"},{"duration":0,"stepId":"io.quarkus.resteasy.reactive.common.deployment.ResteasyReactiveCommonProcessor#scanForIOInterceptors","started":"13:16:44.243","dependents":[320],"id":293,"thread":"build-41"},{"duration":0,"stepId":"io.quarkus.deployment.steps.ReflectionDiagnosticProcessor#writeReflectionData","started":"13:16:45.448","dependents":[],"id":425,"thread":"build-33"}],"started":"2026-04-17T13:16:43.873","items":[{"count":933,"class":"io.quarkus.deployment.builditem.ConfigDescriptionBuildItem"},{"count":334,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem"},{"count":282,"class":"io.quarkus.deployment.builditem.GeneratedClassBuildItem"},{"count":106,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem"},{"count":52,"class":"io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem"},{"count":46,"class":"io.quarkus.arc.deployment.AdditionalBeanBuildItem"},{"count":44,"class":"io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem"},{"count":39,"class":"io.quarkus.hibernate.validator.spi.AdditionalConstrainedClassBuildItem"},{"count":35,"class":"io.quarkus.arc.deployment.SyntheticBeanBuildItem"},{"count":33,"class":"io.quarkus.vertx.http.deployment.RouteBuildItem"},{"count":31,"class":"io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem"},{"count":30,"class":"io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem"},{"count":17,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem"},{"count":16,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem"},{"count":14,"class":"io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem"},{"count":13,"class":"io.quarkus.arc.deployment.UnremovableBeanBuildItem"},{"count":13,"class":"io.quarkus.deployment.builditem.CapabilityBuildItem"},{"count":11,"class":"io.quarkus.deployment.builditem.SuppressNonRuntimeConfigChangedWarningBuildItem"},{"count":11,"class":"io.quarkus.arc.deployment.ConfigPropertyBuildItem"},{"count":11,"class":"io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem"},{"count":11,"class":"io.quarkus.deployment.builditem.ConfigClassBuildItem"},{"count":10,"class":"io.quarkus.deployment.builditem.FeatureBuildItem"},{"count":10,"class":"io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem"},{"count":10,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem"},{"count":9,"class":"io.quarkus.devui.spi.JsonRPCProvidersBuildItem"},{"count":8,"class":"io.quarkus.devui.deployment.InternalPageBuildItem"},{"count":8,"class":"io.quarkus.arc.deployment.AnnotationsTransformerBuildItem"},{"count":8,"class":"io.quarkus.deployment.logging.LogCleanupFilterBuildItem"},{"count":7,"class":"io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem"},{"count":7,"class":"io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem"},{"count":7,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem"},{"count":6,"class":"io.quarkus.deployment.builditem.SystemPropertyBuildItem"},{"count":6,"class":"io.quarkus.deployment.builditem.RunTimeConfigBuilderBuildItem"},{"count":6,"class":"io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem"},{"count":6,"class":"io.quarkus.arc.deployment.GeneratedBeanBuildItem"},{"count":6,"class":"io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem"},{"count":6,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem"},{"count":5,"class":"io.quarkus.devui.deployment.DevUIWebJarBuildItem"},{"count":5,"class":"io.quarkus.devui.spi.buildtime.BuildTimeActionBuildItem"},{"count":5,"class":"io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem"},{"count":5,"class":"io.quarkus.deployment.builditem.ConsoleCommandBuildItem"},{"count":5,"class":"io.quarkus.devui.deployment.DevUIRoutesBuildItem"},{"count":4,"class":"io.quarkus.deployment.execannotations.ExecutionModelAnnotationsAllowedBuildItem"},{"count":4,"class":"io.quarkus.vertx.http.deployment.spi.RouteBuildItem"},{"count":4,"class":"io.quarkus.deployment.builditem.StaticInitConfigBuilderBuildItem"},{"count":4,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyReaderOverrideBuildItem"},{"count":4,"class":"io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem"},{"count":4,"class":"io.quarkus.resteasy.reactive.spi.MessageBodyWriterOverrideBuildItem"},{"count":4,"class":"io.quarkus.devui.spi.page.CardPageBuildItem"},{"count":3,"class":"io.quarkus.vertx.http.deployment.HttpAuthMechanismAnnotationBuildItem"},{"count":3,"class":"io.quarkus.vertx.http.deployment.FilterBuildItem"},{"count":3,"class":"io.quarkus.arc.deployment.AutoAddScopeBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.BytecodeTransformerBuildItem"},{"count":3,"class":"io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem"},{"count":3,"class":"io.quarkus.devui.deployment.BuildTimeConstBuildItem"},{"count":3,"class":"io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.ServiceStartBuildItem"},{"count":3,"class":"io.quarkus.deployment.builditem.GeneratedResourceBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.ShutdownListenerBuildItem"},{"count":2,"class":"io.quarkus.resteasy.reactive.common.deployment.ResourceInterceptorsContributorBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem"},{"count":2,"class":"io.quarkus.devui.spi.buildtime.QuteTemplateBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.RecordableConstructorBuildItem"},{"count":2,"class":"io.quarkus.deployment.dev.testing.TestListenerBuildItem"},{"count":2,"class":"io.quarkus.resteasy.reactive.server.spi.UnwrappedExceptionBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem"},{"count":2,"class":"io.quarkus.devui.spi.buildtime.StaticContentBuildItem"},{"count":2,"class":"io.quarkus.devui.deployment.InternalImportMapBuildItem"},{"count":2,"class":"io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem"},{"count":2,"class":"io.quarkus.deployment.builditem.LogCategoryBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.MvnpmBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.AnnotationProxyBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.TransformedClassesBuildItem"},{"count":1,"class":"io.quarkus.netty.deployment.EventLoopGroupBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.devui.ArcBeanInfoBuildItem"},{"count":1,"class":"io.quarkus.deployment.console.ConsoleInstalledBuildItem"},{"count":1,"class":"io.quarkus.jaxrs.client.reactive.deployment.RestClientDefaultProducesBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.RunTimeConfigurationProxyBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.SynthesisFinishedBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ConfigurationTypeBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ResourceInterceptorsBuildItem"},{"count":1,"class":"io.quarkus.vertx.core.deployment.EventLoopCountBuildItem"},{"count":1,"class":"io.quarkus.vertx.core.deployment.CoreVertxBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BuildCompatibleExtensionsBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ContextResolversBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.ThemeVarsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem"},{"count":1,"class":"io.quarkus.smallrye.context.deployment.ContextPropagationInitializedBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ExceptionMappersBuildItem"},{"count":1,"class":"io.quarkus.vertx.deployment.LocalCodecSelectorTypesBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.InterceptorResolverBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.InitialRouterBuildItem"},{"count":1,"class":"io.quarkus.smallrye.openapi.deployment.spi.OpenApiDocumentBuildItem"},{"count":1,"class":"io.quarkus.deployment.dev.ExceptionNotificationBuildItem"},{"count":1,"class":"io.quarkus.swaggerui.deployment.SwaggerUiBuildItem"},{"count":1,"class":"io.quarkus.deployment.pkg.builditem.CompiledJavaVersionBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ValidationPhaseBuildItem"},{"count":1,"class":"io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveEnricherBuildItem"},{"count":1,"class":"io.quarkus.netty.deployment.EventLoopSupplierBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanArchiveIndexBuildItem"},{"count":1,"class":"io.quarkus.jackson.spi.JacksonModuleBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ConsoleFormatterBannerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.SuppressConditionGeneratorBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BuildTimeEnabledStereotypesBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationArchivesBuildItem"},{"count":1,"class":"io.quarkus.deployment.BooleanSupplierFactoryBuildItem"},{"count":1,"class":"io.quarkus.tls.TlsRegistryBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ContextHandlerBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ParamConverterProvidersBuildItem"},{"count":1,"class":"io.quarkus.rest.client.reactive.spi.DevServicesRestClientProxyProvider$BuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.spi.HandlerConfigurationProviderBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.TransformedAnnotationsBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveResourceMethodEntriesBuildItem"},{"count":1,"class":"io.quarkus.rest.client.reactive.deployment.RegisteredRestClientBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.GeneratedFileSystemResourceHandledBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem"},{"count":1,"class":"io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.PreBeanContainerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.InjectionPointTransformerBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ThreadFactoryBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationIndexBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem"},{"count":1,"class":"io.quarkus.netty.deployment.MinNettyAllocatorMaxOrderBuildItem"},{"count":1,"class":"io.quarkus.smallrye.openapi.deployment.OpenApiFilteredIndexViewBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.VertxWebRouterBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.CombinedIndexBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.jackson.deployment.processor.ResteasyReactiveJacksonProviderDefinedBuildItem"},{"count":1,"class":"io.quarkus.deployment.Capabilities"},{"count":1,"class":"io.quarkus.devui.deployment.ExtensionsBuildItem"},{"count":1,"class":"io.quarkus.deployment.logging.LoggingSetupBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ExecutorBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ArcContainerBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.JsonRPCRuntimeMethodsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.SetupEndpointsResultBuildItem"},{"count":1,"class":"io.quarkus.smallrye.context.deployment.spi.ThreadContextProviderBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveDeploymentInfoBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationClassNameBuildItem"},{"count":1,"class":"io.quarkus.jaxrs.client.reactive.deployment.RestClientDefaultConsumesBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ResourceScanningResultBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.StreamingLogHandlerBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ServerSerialisersBuildItem"},{"count":1,"class":"io.quarkus.deployment.dev.DisableInstrumentationForIndexPredicateBuildItem"},{"count":1,"class":"io.quarkus.deployment.logging.LoggingDecorateBuildItem"},{"count":1,"class":"io.quarkus.vertx.deployment.VertxBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveDeploymentBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BeanContainerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CurrentContextFactoryBuildItem"},{"count":1,"class":"io.quarkus.rest.client.reactive.deployment.AnnotationToRegisterIntoClientContextBuildItem"},{"count":1,"class":"io.quarkus.deployment.ide.EffectiveIdeBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ParameterContainersBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.JaxRsResourceIndexBuildItem"},{"count":1,"class":"io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem"},{"count":1,"class":"io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.HttpRootPathBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ConfigurationBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.DeploymentMethodBuildItem"},{"count":1,"class":"io.quarkus.deployment.steps.CapabilityAggregationStep$CapabilitiesConfiguredInDescriptorsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationStartBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem"},{"count":1,"class":"io.quarkus.devui.deployment.RelocationImportMapBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ConfigMappingBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem"},{"count":1,"class":"io.quarkus.vertx.http.deployment.BodyHandlerBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.BuildExclusionsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.LogCategoryMinLevelDefaultsBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.IOThreadDetectorBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.InvokerFactoryBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.SslNativeConfigBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.server.deployment.BuiltInReaderOverrideBuildItem"},{"count":1,"class":"io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.ApplicationInfoBuildItem"},{"count":1,"class":"io.quarkus.deployment.ide.IdeRunningProcessBuildItem"},{"count":1,"class":"io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem"},{"count":1,"class":"io.quarkus.deployment.ide.IdeFileBuildItem"},{"count":1,"class":"io.quarkus.deployment.builditem.MainClassBuildItem"}],"itemsCount":2425,"buildTarget":"dc-backend-1.0.0-SNAPSHOT"} \ No newline at end of file diff --git a/dc-backend/target/classes/application.properties b/dc-backend/target/classes/application.properties index 2baab1e..0bbe228 100644 --- a/dc-backend/target/classes/application.properties +++ b/dc-backend/target/classes/application.properties @@ -64,6 +64,14 @@ dc.ai.catalog.temperature=0.1 dc.ai.ocr.timeout=120s dc.ai.ocr.max-retries=2 +# ============================================================================= +# Mistral OCR REST Client +# ============================================================================= +quarkus.rest-client.mistral-ocr.url=https://api.mistral.ai +quarkus.rest-client.mistral-ocr.scope=jakarta.inject.Singleton +quarkus.rest-client.mistral-ocr.connect-timeout=10000 +quarkus.rest-client.mistral-ocr.read-timeout=120000 + # ============================================================================= # ORDS REST Client # ============================================================================= @@ -71,11 +79,25 @@ dc.ai.ocr.max-retries=2 quarkus.rest-client.ords-api.url=http://ords:8080/ords/ai_dev quarkus.rest-client.ords-api.scope=jakarta.inject.Singleton quarkus.rest-client.ords-api.connect-timeout=10000 -quarkus.rest-client.ords-api.read-timeout=60000 +quarkus.rest-client.ords-api.read-timeout=600000 # ORDS-Authentifizierung (OAuth2 Bearer Token – leer = kein Auth in Dev) dc.ords.bearer-token= +# ============================================================================= +# Externe Referenz-Dokumente (Links im Dokument → herunterladen & mitbewerten) +# ============================================================================= +# Feature an/aus +dc.references.enabled=true +# Max. Anzahl heruntergeladener Referenzen pro Hauptdokument +dc.references.max-per-document=5 +# Max. Dateigröße pro Referenz (Bytes); größere Downloads werden verworfen +dc.references.max-size-bytes=15000000 +# Verbindungs-/Lese-Timeout pro Download +dc.references.timeout=30s +# Erlaubte Content-Types (alles andere wird verworfen) +dc.references.allowed-content-types=application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/msword,application/vnd.oasis.opendocument.text,text/plain,text/markdown,text/html + # ============================================================================= # Logging # ============================================================================= diff --git a/dc-backend/target/classes/de/frigosped/dc/client/OrdsClient.class b/dc-backend/target/classes/de/frigosped/dc/client/OrdsClient.class index 2ce67b09828f904277b8272675a7cc372acfa2d2..0a7bccf6589c98fd34a66802190f508ae3de98ea 100644 GIT binary patch delta 734 zcmZvY%}(1;5QWbe94Af`P*sdbswzkUTBKqi{FavT59tr2l+s04D83*NB$jPf2)bl} z#O6Lpm9AJIb<0!m0Bm>x#MqEH!Yr=t)yy~No;hcko7ei)-Nhwy$@ho34O5**8G5vPtgA9Kr@Pd!c2(Hvhf#^l16qef^(0R=h4TB z82N>8#l?tQKb$2j2SKg`K^CK9E6ff$?3=9m8489w=^jR6@Cik(3ZC=Ql#yGivqjj=Hq76oovq|%B;trKRJ+3Z1P(kZbk!;NQfbWIs-EUJJ3K@Mg}7w#RWB6m zYkT^kKcGLVr!#l+vboHj`ei5g&fK}r%$=G1^Y34O1HdZ0O+p)7N1xons1V8wt-sp*3*xC~thND8rP7%7H9OPo2Ia z;4Yc=o|=9R^JJGzCokUwwUHgfEdlW!l#q=8D#K|fmAehRg9?gL*-yJTBVylz*pFk!KrKQm>w2sfI(sI(c4C&Y=>HW)(RJm z&}-AEC6hH9@=mU{YIPl+X|DQFSAYDlyzy<(tG;KP8z9hjtzPfAM6<}U8@2n9y1!mW zW-8x+Zaf^dzER5yV@x4*q4Pb}Ayf{{-xS(TqfLhESE>P1Z@x4uzJjZDqhvXsd4z5~S8<*wbiyakmF;GtI8U;e%!^8#pG9!nfF~w?+6C zktMywZlgWIY)98g)USnof{lbXG#9AZuXhV+nv2jsl^d2wbOg`QEP!!rxHcU6efY)U zjlbGx`YMUDRY@GDO5*HP5~rn-I0BW#xu+x!H6?L?DT$*>Nt{Yb;#g1;2Z55<`ATB9 zqnrTjH|WIgm$*u!G*EWT`~k*q%4`$Aqy`+<8T^XS0A9dLTp5ssE&R3~eg}zPQP~E~ z!(;5>ZOEY&9bQC98R?vv{T&ke)i>4Ykrn9=u{>9Go*9-_~%VYBOGp6(0kKv(N)8M(K@^cIYCO_jk&jXgn0?FlP zT<3Wh!$UK#!E;;joUlAoI?n>jQ{;K3be=~sJcm)9@V!o1o|Mk>nB}o~o|MkB_~ATf zY=6z_Jj*Q4&wPK)>O4s z9Ixm+n=FsV=Xgcu$;9$p$g$7P)K#75CCd}=Gj&zx$;R?rn5k8kXI~s6ZErG~m!64Jo?(&U^2< zp%*pWy`y)!o+=u85=24zRMCZU<#3oT(r}SV4vmnbXvx>^+azejtrP9Le%)=xz7E~J z#BXiK^>$N75GVvbgsZ2@)i`Rbv(hDp#^|!5i*JKaM_yAan#jfNr%BL>oN#E;L^gw_ zuKjwVIeprp8K$`8cg|wRj?b= zuiDKcoz%Re(7YOhA9_i^>(1stdK|PA-Ku`RNb8CQ8#>*=858A7?qa4?aJDk%WZCvr z4pG}(ogT;jYcD|wQ(Q{X2-IHmiaK>hvr|?CLG?6fIIbDsz*KM?{ z_;`1y+I#A~A_uWs-EP1VVY%ffya&0ixY9)y`a;ntlJn6E9d;p@uP_1emv;Y5cTX_W z*6kn!L;I89nf&8)V_AAClRn#M*v>@kZNBB3UgKr=$gfNAZ)F{b7UdV}E#Num_91&t z7yEHLscSAI9zQ?&YdrF_h*mD(&t?Q){Au7V`DM1#$rm1#poG}fif1O zV=QLHSj-3PETKbb2;ViVTv!D=xbhdN-%Xf{FS|MPYkZw7fbP;gtQ37i_whZO{09|( zgR=m=Zt|R%{D8hi6emB1<(m$zto}(w`{;R-J(ERrj%H~# zo&}NTF|A8Hqc+dgK0I8T9?zKJc_Q+R+dS7rp6_LzahvBxA0FGUyHqWxi^IYbcwRvv$;o+I-z0aKC*`jTsf97qTvdHrT zy^#86-sY+F;n~1U_IR@Q`H@}l TH!{zv&GYE|Jo}ik0@eNpxSyuO diff --git a/dc-backend/target/classes/de/frigosped/dc/model/OrdsProject.class b/dc-backend/target/classes/de/frigosped/dc/model/OrdsProject.class index 3cef71310255d56900fb6c22634281102831b345..f2d05733b8ac71744369b06235bd8f2836bffcc6 100644 GIT binary patch literal 3700 zcma)+=~5d<5XXCEKpZP0PR9m}Wqe3rBi|PUM;tadG9m_)`&elQu|(3UR*PM{RZ^8y zs`4QZ;D<`(KeH=oHEEUkvc1#Y)AO62?*91i-~SL%j!uRtMi=7LXHq{67?eD*-dowK zRj*_Vr4v#14H{T=>W;r=(1moSI7EXq6sKX6M#wZMWsB^Q=TzKgL)clnoUOUGsAdbE z-Q4rsV1LZ95jWonoOkP$6&dKFNu!iBsIP9-ghAsGpMBqRLa%X?CZyMhEt+M|Y50y? zm%FD-nwAVhWy`m!Ze_=o+Rse7EVYTUCoEss>t}~e;YrJkNwYG}K-0JU)238iHEEu% z88q1N+=?ffO@k&P5Ic2WRD`EkubZ?$H$YK#YmKTv$Kn#ktevQ^#0nFm;@ zJ-1A{El(V)yS{VefD`CjHOr~W!!sr=O6#Dqo)vzT$~z|AmC8h*+;-|8griuqCf$>k zWMIjaU??Apy1MlPlOD>8414Z}qVSqwiXNNvL_){IWXKbqne<%gP8+sN5YoSaM>A+N z@6^TaX{{u@1FKY34>y7I`ApOVPM8vhMxrMu3o=T1CCM4n<+KwIk-V*CG!@n!mtWc#XF+ek*jd1G*lGZ2=`8l$Q?D#uIkE$i zkx8~JCm5yBLNScaQG781@fVXrOi7F>B{7zi#2``zq|C&?;?kcK*N?ucc25T$;n5OH=FFfC?CVE;Kp3=1 zTMSP^=efr6yrS0}PeSKO_27|j*X9{jJlibKn9j4n^6c-``9z1WNJZ}`wm+X5@ z>O8kto;?3vlRD4s9z3#^+xeMRJa1W^OFB=6<=N$VF6lgrJ$MRHo`T}pW8dqF&U1(5 z`HFw9D>~2J9z3$Q+xeMQJnv|qspmPJC(H62&>>gPb2`tx9z1f!w0Y(gPm#W6cv3pg z1D59-`j+EK={yg6@Dwn!+C0H~eaF7nO`Yd4%kw?|UN?1~Cp~#0?-lU;!1APZo@Xr2 zk33IW=Xrj9o}XBrC7ox5<@uTCS<-n{&(HG<%d@QWtg$>6&$F!aygWZoiS4iZI!}(} zDf9hxU*}ox$rI_Xpnhz&zaHs4n=Fsu`|FX;v(=O5fBkjD@;udfUb8$Ep698~vweP^ KV@#nKIsX9x#1E4I literal 3058 zcma)+TXWM!6vxk+#3qSC?t$C@6E1cF5ruMVQXnJ*LQRsA#JxQ!)?&oimPb?cPXD`-ZFxBwc+t_G+x~XXIs5N_fB!>7oAk>t<>*X-1}qw+yrA*-&IiYC zIIX(9fAU_|B0>2LujNIL1)V9Ct3z~_h6*%n(Fj?BimtTZ2439{+tRh&n%(qW*|7Hm zH#`XZI6mjO$Xn?puK2C`x=M7;qA?m5G|+OIQqW||=P(Mq#B0)`Ddjcd%CHuA?a1?6 zDtgAES;a6^b0VkV*LPi|yw=|s|f_VGBBfwC-Ew5amr!^nwFL#4WC(IvVp z=xjUi>wyeIK~pJ+-Bu*)GSIA7ELx_kps4xHc0(fbChle~_Kv)!v@3qiX>4Q9Bdm1H zb&GDO9Vc3TohS#QcOK5RT={Id)&&jLWweVeCQIei zUFf307{c3rV~3e^&cv>=oEWO2{e;U-}j;DHZJoO%|sds>iXzCmuAPZkSB>2mzr$9># zyOtPkEiukoVtBR0#A=Cg)Dk16C5A~$OplhB7|1z7(^4MauduQq1?1q$UnG9lZg=oi zW}*vp7hfxOps(p3R)X%+1AO~~|DeKeFy^QiJkJI{q(|^l!QVit6X#b}|D=Ms^@5I` zjXjdB6_&aifmjfuw;%+q(+0ycYVs_xJm1n|j%U>5xtPJDYSiT!(>zaDo(YrZGRyOn z=b12hiWxkcX`W5Z^Ng+6w8^u~@@(<-nl^c^X7H$&YL{fS)S)S&v}#Q zMh4GLnrBDzykP4!Z}OB`o|k;R=1rcJ3?4PZ-TPV8JiGLY>E|Vr=O)YZ9aXq~UNU)Z zW$>uCq|0+j^Sq`#hNo!q*euUJ9dJBFljn8@&ko+KE>B#q@7a1?GkNZ?Ja71VT{C&^ zX7Z%!74saj^(vV>_gJ1IzFsAh=YA$ns$MbAFKy(2KhlV^G5`Po diff --git a/dc-backend/target/classes/de/frigosped/dc/service/CheckOrchestrationService.class b/dc-backend/target/classes/de/frigosped/dc/service/CheckOrchestrationService.class index efd4f56fec9dbd07916d22f86be04822e7b12fb2..6928f1c65b904b2c4860dc764448a826e7f9dcbd 100644 GIT binary patch literal 25314 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 delta 6919 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 a<5w@v5)FOOVul)dj$esHRICTF6_yURm diff --git a/dc-backend/target/classes/de/frigosped/dc/service/DocumentProcessingService.class b/dc-backend/target/classes/de/frigosped/dc/service/DocumentProcessingService.class index 8a07175ea38f760268bbad74846a3ba1d4237d69..cf45ca05842186ce94aa8d592810dc212a4fb834 100644 GIT binary patch literal 16529 zcmcIr3w%`Nl|SdpB==^xNJtD2!Y?-YH1au-CC-pt+vHicvY*l;-l8K+PdwwyS4VQ+voPR-D<7u|9tn(%$;O_>~DXI z=HC0=d%p9X@4UWq&iBnr?;d@Mh#J-R{iLYGAfH90RK`@XE!-at#lrEf(CV$*?2Zgm z+0tk{npwtFGH31vKbcf+kl&(FWHBv>*rCo;v@4NL+L2JCBb2sN{m~9Pv^>$#*K5Z! z?WshEolZyNU2B~$Orz`_nN+wVvkrHr%jR6&)RrG~Z6*cShEu+2O8{c-a1f#G42#B4 z1=E<6oleC1?Um79d)+|NW?C|5c#q+4il8kA+DeP6=uEJnZS^Hg6WbE0uF$rviF7&? zOLTRC;!qpzcB;XoaZD3d$PBxqcFGROEb9X^(}6iN({ueafocqzXwg|T32-Mv1Expp zt$kgcOc$J*Y)435ez|W%R8wfGK>>@V(b)x(b(qX#c8Am5&4~!O1KGBkGy{waCzG*g zM>rEr#6!tQr(l_7(QKLnqU`tfg=1-^YA;dQ0XLa6kLeO`|NeNSE|Ik3J9=ZCiBxYm zlMW_2JE0#DS3T;sCsL7QPDgrUb=fZ_)iPCjk@cnp*rYn9+1~ehkZ?Mh)m<7^A@J5xruqvW+CTpFzHgJ(E9jR zyC)ovXY5p9W$VfnL0D2?_RRF`z?(Pj4)oipbYDle9e_$`H5cu^%%aPw4fJder{dt? zY_FyaH}0(NY%pjQQ&rv%tk}_ECq)NMYG<0#Vt2W1 zN+nVld8v2gb=|4N_VCu2?X{FslCuH4%#*XGFP?!X$x-E}m2{Ow8)*~ttIN);gpUh% z!H&+HQ$*C2aJ8E)x`y7vG`=_7V>c(_u&^tm8CYLD4Pj;=!dYH|UD}Eu^V52|R>T-) znpdQY$EW3IE#9DmA_mzObyAn>dcw&tAksF>y9CM$!pB39MVy*SpnDt?q0G|I#MKp#~T{&>hYM zW<~;Q?P$iH6W5+IDQ@$@q7u3ETeO{E zqy{Jv0H*pm+Pj~2#zjRlg?3u>KG6yvM(P!lyWXN3ge7G$JKmM)Ht9y_Gsp?baCt?> z8W9O>vZD|Pd{UHdw&(-0AeVN;5^39{TbWj5`Lf-PN4o+xmK}Smoypi4JFd+F#*o7? zH*2E+j@W?>(KM_jxVm|bNw;IVz0ovWT};-tJ1qJTeHeNij$*a$k7^~T9L{2y%ttJ` zi*`Y9&PveQ6;Gt>=5QLCU$7=*)pwJ4xw|d;n0PtAJLu|;ltCYdZx}ACln^Ts0CcNm zjc$mrcGEqg@%J*#9jSDn4trs3c@uU>YU+!|FoK`

D&Oguez$94&(YLNg=c#QsSX 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 Fe*mDI(M~mz_zNkU#`-@ZC6 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 C<_Kf} diff --git a/dc-backend/target/classes/de/frigosped/dc/service/EvaluationService.class b/dc-backend/target/classes/de/frigosped/dc/service/EvaluationService.class index 96f65e67b017499de9cfdfffea9df598ff0ae759..1a48a956aa829148010a039198f4cf38139c18ba 100644 GIT binary patch literal 19229 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 delta 6787 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 diff --git a/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 545e599..b8c6bd8 100644 --- a/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,25 +1,50 @@ de/frigosped/dc/model/GeneratedCatalogStructure$GeneratedCategory.class +de/frigosped/dc/model/MistralOcrRequest$Document.class de/frigosped/dc/resource/ExportResource.class -de/frigosped/dc/model/OrdsQuestionCreateRequest.class +de/frigosped/dc/model/DocumentProgressRequest.class +de/frigosped/dc/model/MistralOcrRequest.class +de/frigosped/dc/model/CompleteProjectRequest.class de/frigosped/dc/model/OrdsCategoryCreateRequest.class +de/frigosped/dc/ai/AiModelProducer.class de/frigosped/dc/model/OrdsCatalogCreateRequest.class -de/frigosped/dc/model/OrdsCatalogStatusUpdateRequest.class -de/frigosped/dc/service/EvaluationService.class -de/frigosped/dc/service/MarkdownExportService.class +de/frigosped/dc/model/OrdsDocumentList.class de/frigosped/dc/service/DocumentProcessingService.class -de/frigosped/dc/model/CatalogGenerateResponse.class +de/frigosped/dc/resource/CheckResource.class de/frigosped/dc/service/MarkdownExportService$Seg.class -de/frigosped/dc/model/AiCostRequest.class de/frigosped/dc/model/OrdsCatalogStatusResponse.class +de/frigosped/dc/client/OrdsClient.class +de/frigosped/dc/service/CheckOrchestrationService.class +de/frigosped/dc/model/GeneratedCatalogStructure.class +de/frigosped/dc/client/OrdsLoggingFilter.class +de/frigosped/dc/model/ResultRequest.class +de/frigosped/dc/service/ReferenceDownloadService.class +de/frigosped/dc/service/MarkdownReportBuilder.class +de/frigosped/dc/model/OrdsQuestion.class +de/frigosped/dc/client/MistralOcrClient.class +de/frigosped/dc/model/OrdsQuestionList.class +de/frigosped/dc/model/OrdsQuestionCreateRequest.class +de/frigosped/dc/model/ProgressRequest.class +de/frigosped/dc/model/OrdsDocument.class +de/frigosped/dc/model/OrdsCatalogStatusUpdateRequest.class +de/frigosped/dc/model/MistralOcrResponse.class +de/frigosped/dc/service/EvaluationService.class +de/frigosped/dc/model/OrdsProject.class +de/frigosped/dc/service/MarkdownExportService.class +de/frigosped/dc/model/CatalogGenerateResponse.class +de/frigosped/dc/model/TextsRequest.class +de/frigosped/dc/model/EvaluationResult.class +de/frigosped/dc/model/AiCostRequest.class de/frigosped/dc/model/OrdsDocumentTexts.class +de/frigosped/dc/model/MistralOcrResponse$UsageInfo.class de/frigosped/dc/resource/CatalogResource.class de/frigosped/dc/model/ExportRequest.class de/frigosped/dc/service/CatalogGenerationService.class -de/frigosped/dc/service/CheckOrchestrationService.class +de/frigosped/dc/model/DocumentPdfEntry.class de/frigosped/dc/service/MarkdownExportService$SegType.class -de/frigosped/dc/model/GeneratedCatalogStructure.class -de/frigosped/dc/client/OrdsLoggingFilter.class de/frigosped/dc/service/MarkdownExportService$PdfWriter.class de/frigosped/dc/security/ApiKeyFilter.class +de/frigosped/dc/service/ReferenceDownloadService$DownloadedReference.class de/frigosped/dc/model/GeneratedCatalogStructure$GeneratedQuestion.class de/frigosped/dc/service/AiCostTrackerService.class +de/frigosped/dc/model/MistralOcrResponse$Page.class +de/frigosped/dc/model/ReferenceCreateRequest.class diff --git a/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index f3b4cd9..466b2c9 100644 --- a/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/dc-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,32 +1,41 @@ -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/ai/AiModelProducer.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/client/OrdsLoggingFilter.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/AiCostRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/CatalogGenerateResponse.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/EvaluationResult.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ExportRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/GeneratedCatalogStructure.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCatalogCreateRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCatalogStatusResponse.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCatalogStatusUpdateRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCategoryCreateRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocumentList.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocumentTexts.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestion.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestionCreateRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestionList.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ProgressRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ResultRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/TextsRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/CatalogResource.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/CheckResource.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/ExportResource.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/security/ApiKeyFilter.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/AiCostTrackerService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/CatalogGenerationService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownExportService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/ai/AiModelProducer.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/client/MistralOcrClient.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/client/OrdsLoggingFilter.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/AiCostRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/CatalogGenerateResponse.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/CompleteProjectRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/DocumentPdfEntry.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/DocumentProgressRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/EvaluationResult.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ExportRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/GeneratedCatalogStructure.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/MistralOcrResponse.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCatalogCreateRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCatalogStatusResponse.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCatalogStatusUpdateRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsCategoryCreateRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocumentList.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocumentTexts.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestion.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestionCreateRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestionList.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ProgressRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ReferenceCreateRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ResultRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/TextsRequest.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/CatalogResource.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/CheckResource.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/ExportResource.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/security/ApiKeyFilter.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/AiCostTrackerService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/CatalogGenerationService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownExportService.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/MarkdownReportBuilder.java +/home/philipp/git/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/ReferenceDownloadService.java diff --git a/dc-backend/target/maven-status/maven-compiler-plugin/compile/null/createdFiles.lst b/dc-backend/target/maven-status/maven-compiler-plugin/compile/null/createdFiles.lst deleted file mode 100644 index 43b1948..0000000 --- a/dc-backend/target/maven-status/maven-compiler-plugin/compile/null/createdFiles.lst +++ /dev/null @@ -1,12 +0,0 @@ -de/frigosped/dc/model/OrdsQuestion.class -de/frigosped/dc/model/OrdsQuestionList.class -de/frigosped/dc/model/ProgressRequest.class -de/frigosped/dc/client/OrdsClient.class -de/frigosped/dc/ai/AiModelProducer.class -de/frigosped/dc/model/OrdsDocument.class -de/frigosped/dc/model/OrdsProject.class -de/frigosped/dc/model/OrdsDocumentList.class -de/frigosped/dc/model/TextsRequest.class -de/frigosped/dc/model/ResultRequest.class -de/frigosped/dc/model/EvaluationResult.class -de/frigosped/dc/resource/CheckResource.class diff --git a/dc-backend/target/maven-status/maven-compiler-plugin/compile/null/inputFiles.lst b/dc-backend/target/maven-status/maven-compiler-plugin/compile/null/inputFiles.lst deleted file mode 100644 index 2846593..0000000 --- a/dc-backend/target/maven-status/maven-compiler-plugin/compile/null/inputFiles.lst +++ /dev/null @@ -1,15 +0,0 @@ -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/ai/AiModelProducer.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/client/OrdsClient.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/EvaluationResult.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocument.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsDocumentList.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsProject.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestion.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/OrdsQuestionList.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ProgressRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/ResultRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/model/TextsRequest.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/resource/CheckResource.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/CheckOrchestrationService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/DocumentProcessingService.java -/mnt/c/source/Frigosped/Dokumenten-Check/dc-backend/src/main/java/de/frigosped/dc/service/EvaluationService.java diff --git a/dc-backend/update.sh b/dc-backend/update.sh old mode 100644 new mode 100755 index 025fb03..ffcf767 --- a/dc-backend/update.sh +++ b/dc-backend/update.sh @@ -6,21 +6,36 @@ export KUBECONFIG="$SCRIPT_DIR/../env/frigo-dev.yaml" TAG="${1:-latest}" +DB_USER="wksp_ai" +DB_PASS="s!)löyx209aasgtrasdasiudkash5235FDSXljLJ" +DB_HOST="10.75.10.171" +DB_PORT="32710" +DB_SID="freepdb1" +DB_CONNECT="${DB_USER}/${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_SID}" + echo "=== dc-backend Update ===" echo "" +# --- DB Migration --- +echo "→ [1/4] DB Migration..." +sql -s "$DB_CONNECT" @"$SCRIPT_DIR/../Scripts/DC_DOCUMENT_PROGRESS_ALTER.sql" +sql -s "$DB_CONNECT" @"$SCRIPT_DIR/../Scripts/DC_DOCUMENT_PROGRESS_ORDS.sql" +sql -s "$DB_CONNECT" @"$SCRIPT_DIR/../Scripts/DC_REFERENCES_ALTER.sql" +echo "✓ DB Migration abgeschlossen." + # --- Build & Push --- -echo "→ [1/3] Build & Push Image (Tag: $TAG)..." +echo "" +echo "→ [2/4] Build & Push Image (Tag: $TAG)..." bash "$SCRIPT_DIR/build-and-push.sh" "$TAG" # --- Apply Kubernetes Manifests --- echo "" -echo "→ [2/3] Kubernetes Manifests anwenden..." +echo "→ [3/4] Kubernetes Manifests anwenden..." kubectl apply -f "$SCRIPT_DIR/k8s/deployment.yaml" # --- Rolling Restart (zieht neues 'latest'-Image) --- echo "" -echo "→ [3/3] Rolling Restart..." +echo "→ [4/4] Rolling Restart..." kubectl rollout restart deployment/dc-backend -n ai-env echo "" diff --git a/sqlrun.sh b/sqlrun.sh old mode 100644 new mode 100755 diff --git a/tmp/markdownb64.txt b/tmp/markdownb64.txt new file mode 100644 index 0000000..cefb3cf --- /dev/null +++ b/tmp/markdownb64.txt @@ -0,0 +1,802 @@ +# Prüfbericht: AGB Nordfrost + +**Abgeschlossen:** 12.06.2026 15:38 + +--- + +## Zusammenfassung + +✅ OK: 20 +⚠️ Hinweise: 21 +❌ Abweichungen: 31 +**Gesamt: 72** + +--- + +## Ergebnisse + +## 📄 08_Allgemeine_Geschaeftsbedingungen_Transportauftrag_(AGB_Transport).pdf + +✅ OK: 20 +⚠️ Hinweise: 21 +❌ Abweichungen: 31 +**Gesamt: 72** + +--- + + +### Anwendungsbereich & Ausschlüsse (§2.1–2.4) + + + +❌ **Sind die ADSP 2017 als maßgebliche AGB im Vertrag ausdrücklich vereinbart?** + +Nein. Die ADSp 2017 werden weder erwähnt noch ausdrücklich vereinbart. In Ziffer 30 wird explizit festgehalten, dass die Ziffer 19 ADSp keine Anwendung findet. + +*Score: 0%* + +> Die ADSp 2017 sind nicht als maßgebliche AGB vereinbart. Der Vertrag enthält stattdessen eigene Regelungen, die die ADSp ausdrücklich ausschließen. + + + +✅ **Ist der Vorrang zwingender gesetzlicher Bestimmungen klarstellend verankert?** + +Ja. In Ziffer 6 wird klargestellt, dass die AGB nur gelten, soweit keine zwingenden gesetzlichen Vorschriften oder Rahmenvertragsbestimmungen entgegenstehen. + +*Score: 100%* + +> Der Vorrang zwingender gesetzlicher Bestimmungen ist in Ziffer 6 explizit verankert, was den Anforderungen entspricht. + + + +⚠️ **Wurde der Ausschlusskatalog (z.B. Schwertransporte, Umzugsgut, Verpackungsarbeiten) bei entsprechender Leistung geprüft?** + +Nein. Es gibt keinen Ausschlusskatalog für bestimmte Transportarten (z.B. Schwertransporte, Umzugsgut, Verpackungsarbeiten). Der Vertrag enthält keine entsprechenden Einschränkungen. + +*Score: 0%* + +> Ein Ausschlusskatalog für spezifische Leistungen fehlt vollständig. Der Vertrag regelt alle Transportarten ohne Einschränkungen. + + + +✅ **Ist die Nichtanwendung auf Verbraucher klarstellend ausgeschlossen bzw. der B2B-Charakter bestätigt?** + +Ja. Der Vertrag adressiert ausschließlich gewerbliche Auftragnehmer (B2B). In Ziffer 36 wird deutsches Recht vereinbart, und der B2B-Charakter ergibt sich aus dem Kontext (z.B. Genehmigungspflichten nach GüKG, ISO 50001, IFS Logistics). + +*Score: 90%* + +> Der B2B-Charakter ist durch den Kontext und die spezifischen Anforderungen (z.B. GüKG, Zertifizierungen) klar erkennbar, auch wenn keine explizite Verbraucherausnahme formuliert ist. + + +### Informationspflichten & Güterkennzeichnung (§1.3, §1.7, §3.1–3.4) + + + +❌ **Sind Rohgewicht (inkl. Verpackung und gestellter Lademittel) sowie Anzahl und Art der Packstücke konkret angegeben?** + +Nein. Das Dokument enthält keine konkreten Angaben zu Rohgewicht (inkl. Verpackung und gestellter Lademittel), Anzahl oder Art der Packstücke. ( Keine direkte Erwähnung dieser Pflichtangaben ) + +*Score: 0%* + +> Die AGB regeln zwar Transportmodalitäten, enthalten aber keine expliziten Vorgaben zur Dokumentation von Rohgewicht, Packstückanzahl oder -art. Dies ist für die Erfüllung der Informationspflichten (§1.3, §1.7, §3.1–3.4) essenziell. + + + +❌ **Sind bei gefährlichen Gütern die genaue Gefahrart, die Klassifizierung nach Gefahrgutrecht und erforderliche Vorsichtsmaßnahmen dokumentiert?** + +Ja, teilweise. Bei Gefahrgut wird die Einhaltung der GGVS/ADR-Bestimmungen und die Mitführung der erforderlichen Schulungsbescheinigung des Fahrers verlangt. ( 'Bei Annahme von Aufträgen über die Beförderung von Gefahrgut, hat der Auftragnehmer sicherzustellen, dass das eingesetzte Fahrzeug gemäß den GGVS/ADR-Bestimmungen vollständig ausgerüstet ist und der Fahrer im Besitz der erforderlichen Schulungsbescheinigung ist und diese mit sich führt.' ) + +*Score: 70%* + +> Die AGB verweisen auf die Einhaltung der GGVS/ADR-Bestimmungen, was die Klassifizierung und Vorsichtsmaßnahmen impliziert. Allerdings fehlt eine explizite Dokumentationspflicht der genauen Gefahrart im Transportauftrag selbst. + + + +❌ **Ist wertvolles Gut (≥100 EUR/kg) oder diebstahlgefährdetes Gut mit expliziter Risiko- und Wertangabe deklariert?** + +Nein. Es gibt keine Regelung zur Deklaration von wertvollem Gut (≥100 EUR/kg) oder diebstahlgefährdetem Gut mit expliziter Risiko- und Wertangabe. ( Keine Erwähnung von Wertdeklarationen oder Risikoangaben ) + +*Score: 0%* + +> Die AGB enthalten keine Vorgaben zur Kennzeichnung oder Deklaration von wertvollem oder diebstahlgefährdetem Gut, obwohl dies für die Risikominimierung und Haftungsfragen relevant wäre. + + + +⚠️ **Ist die Vollständigkeit der zoll-, außenwirtschafts- und sicherheitsrelevanten Daten (Embargos, SOLAS, Schutzrechte) geprüft?** + +Nein. Es gibt keine Hinweise auf eine Prüfung der Vollständigkeit zoll-, außenwirtschafts- oder sicherheitsrelevanter Daten (z.B. Embargos, SOLAS, Schutzrechte). ( Keine Erwähnung von Zoll-, Embargo- oder SOLAS-Prüfungen ) + +*Score: 0%* + +> Die AGB regeln zwar Sicherheits- und Hygienevorgaben, enthalten aber keine expliziten Anforderungen zur Prüfung zollrechtlicher, außenwirtschaftlicher oder sicherheitsrelevanter Daten. + + + +❌ **Sind alle für die zollamtliche oder sicherheitsrechtliche Behandlung notwendigen Urkunden und Unterlagen beigefügt?** + +Nein. Die AGB enthalten keine explizite Regelung zu zollamtlichen oder sicherheitsrechtlichen Urkunden/Unterlagen (z.B. Carnet ATA, Zollpapiere, Sicherheitsbescheinigungen). Lediglich in Ziffer 31 wird die 'Gelangenheitsbescheinigung' bei innergemeinschaftlichem Verkehr erwähnt, aber dies deckt nicht alle notwendigen Unterlagen ab. + +*Score: 20%* + +> Die AGB sind unvollständig, da sie keine allgemeine Pflicht zur Beifügung aller zoll-/sicherheitsrelevanten Dokumente vorsehen. Nur ein Teilaspekt (Gelangenheitsbescheinigung) wird geregelt. + + +### Technische Ausstattung & Fahrzeugstandards (§4.2) + + + +✅ **Sind die eingesetzten Fahrzeuge und Lademittel als technisch einwandfrei und gesetzeskonform bestätigt?** + +Ja, teilweise. Die AGB fordern in Ziffer 2 die Einhaltung gesetzlicher Genehmigungen (§§ 3, 6 GüKG, ADR, ATP-Zertifikat etc.) und in Ziffer 4/5 spezifische technische Anforderungen (z.B. Wegfahrsperre, ATP-Zertifikat, Kühlaggregat). Allerdings fehlt eine pauschale Bestätigung der technischen Einwandfreiheit aller Fahrzeuge. + +*Score: 85%* + +> Die AGB enthalten konkrete Vorgaben zu Genehmigungen und technischen Standards, erfüllen aber nicht alle Aspekte einer vollständigen Bestätigung der technischen Einwandfreiheit. + + + +✅ **Sind Vorrichtungen und Ladungssicherungsmittel zum Schutz des Gutes spezifiziert?** + +Ja. In Ziffer 15 werden Ladungssicherungsmittel explizit genannt (Spanngurte, Antirutschmatten, Kantenschoner etc.) und die Verantwortung des Auftragnehmers für betriebssichere Verladung betont. Zudem werden Anforderungen an die Stauung (Luftumwälzung, Abstand zu Türen) geregelt. + +*Score: 100%* + +> Die AGB spezifizieren detailliert Ladungssicherungsmittel und -verfahren, sodass die Anforderung vollständig erfüllt ist. + + + +⚠️ **Ist die Anforderung an schadstoffarme, lärmreduzierte und energiesparende Fahrzeuge enthalten oder erfüllt?** + +Nein. Die AGB enthalten keine Anforderungen an schadstoffarme, lärmreduzierte oder energiesparende Fahrzeuge (z.B. Euro-Normen, Lärmgrenzwerte, CO₂-Einsparung). Lediglich in Ziffer 3 wird ein Energiemanagementsystem nach ISO 50001 'begrüßt', aber nicht verbindlich vorgeschrieben. + +*Score: 0%* + +> Die AGB erfüllen diese Anforderung nicht, da keine konkreten technischen oder ökologischen Standards für Fahrzeuge vorgegeben werden. + + +### Personal, Sicherheit & Zollabwicklung (§1.11, §4.1, §4.3–4.6) + + + +✅ **Ist die Bereitstellung von fachlich geschultem Personal und erforderlichen Fahrerbescheinigungen bestätigt?** + +Ja, der Auftragnehmer hat sicherzustellen, dass das zum Transport eingesetzte Personal über die notwendigen Fahrerlaubnisse, sonstige vorgeschriebene und vereinbarte Erlaubnisse sowie die notwendige körperliche und geistige Eignung und Zuverlässigkeit verfügt (§18). Ferner wird die Schulung im Umgang mit Temperaturmessgeräten und die Ausstattung mit kältetauglicher Sicherheitskleidung bestätigt. + +*Score: 95%* + +> Die AGB enthalten klare Vorgaben zur Qualifikation des Personals und zu Fahrerbescheinigungen. Die Anforderungen gehen über das gesetzliche Mindestmaß hinaus. + + + +⚠️ **Wird die Beachtung bekanntgemachter Haus-, Betriebs- oder Baustellenordnungen fremder Betriebsgelände bestätigt?** + +Nein, eine explizite Bestätigung zur Beachtung fremder Haus-, Betriebs- oder Baustellenordnungen ist in den AGB nicht enthalten. + +*Score: 0%* + +> Die AGB regeln keine spezifischen Pflichten zur Einhaltung fremder Betriebsordnungen. Dies wäre jedoch in der Praxis relevant, insbesondere bei Kundenstandorten. + + + +❌ **Liegt eine schriftliche Vollmacht zur direkten zollamtlichen Vertretung vor?** + +Nein, eine schriftliche Vollmacht zur direkten zollamtlichen Vertretung wird in den AGB nicht erwähnt. + +*Score: 0%* + +> Die AGB enthalten keine Regelung zur zollamtlichen Vertretung. Dies ist insbesondere bei grenzüberschreitenden Transporten problematisch. + + + +⚠️ **Ist die Ermächtigung zum Öffnen von Verpackungen und Auslegen von Abgaben bei grenzüberschreitender Beförderung geregelt?** + +Nein, die Ermächtigung zum Öffnen von Verpackungen oder Auslegen von Abgaben bei grenzüberschreitender Beförderung ist in den AGB nicht geregelt. + +*Score: 0%* + +> Die AGB enthalten keine spezifischen Regelungen zu zollrechtlichen Befugnissen. Dies könnte zu Verzögerungen oder Haftungsrisiken führen. + + + +✅ **Ist die Prüfung auf offensichtliche Mängel und die unverzügliche Anzeige bekannter Gefahrumstände dokumentiert?** + +Ja, die Prüfung auf offensichtliche Mängel ist dokumentiert: In Ziffer 9 (Kontrolle Ware) ist der Auftragnehmer verpflichtet, bei Übernahme der Güter diese auf Vollständigkeit, Unversehrtheit und Identität zu überprüfen und dies auf dem Frachtbrief zu vermerken. Zudem ist in Ziffer 12 (Meldung Abweichungen) geregelt, dass bei Unstimmigkeiten oder Abweichungen die Verladung einzustellen und unverzüglich Weisungen einzuholen ist. Die unverzügliche Anzeige bekannter Gefahrumstände ist in Ziffer 14 (Gefahrgut) und Ziffer 25 (Meldung Abweichung Transport) enthalten, wo bei Gefahrgut oder Transporthindernissen unverzüglich zu informieren und Weisungen einzuholen ist. + +*Score: 95%* + +> Die Anforderungen sind klar und umfassend geregelt. Die Dokumentationspflichten sind explizit in den Ziffern 9 und 12 verankert, die unverzügliche Meldung von Gefahrumständen in Ziffern 14 und 25. + + +### Informations- und Meldepflichten + + + +✅ **Ist im Dokument bei Güter- oder Verspätungsschaden eine Verpflichtung des Spediteurs zur unverzüglichen Verschaffung aller bekannten Informationen zur Sicherung von Schadensersatzansprüchen enthalten?** + +Ja, das Dokument enthält eine solche Verpflichtung: In Ziffer 24 (Meldung Verzögerung) ist geregelt, dass der Auftragnehmer bei absehbarer Nichteinhaltung von Lade- oder Lieferfristen unverzüglich zu informieren und Weisungen einzuholen hat. In Ziffer 25 (Meldung Abweichung Transport) ist die unverzügliche Information bei Transporthindernissen oder Unregelmäßigkeiten vorgesehen. Zudem ist in Ziffer 26 (Meldung Abweichung Ware) die unverzügliche Information bei Differenzen, Beschädigungen oder Temperaturabweichungen geregelt. + +*Score: 90%* + +> Die Meldepflichten sind detailliert und klar formuliert. Die Pflicht zur unverzüglichen Information und Einholung von Weisungen ist in mehreren Ziffern (24, 25, 26) verankert. + + + +⚠️ **Benennen beide Vertragsparteien im Dokument explizit Kontaktpersonen für die Vertragsabwicklung und ist eine Aktualisierungspflicht bei Änderungen festgelegt?** + +Nein, das Dokument benennt keine expliziten Kontaktpersonen für die Vertragsabwicklung. Es gibt zwar in Ziffer 4 (Hygiene und Food defense) die Verpflichtung, dem Auftraggeber den Namen und die Nummer des Mobiltelefons des Fahrers mitzuteilen, jedoch keine allgemeine Regelung zu Kontaktpersonen oder einer Aktualisierungspflicht bei Änderungen. + +*Score: 20%* + +> Es fehlt eine explizite Benennung von Kontaktpersonen oder eine Aktualisierungspflicht. Lediglich die Erreichbarkeit des Fahrers ist geregelt, nicht jedoch die Vertragsparteien selbst. + + + +✅ **Sieht das Dokument vor, dass Leistungshindernisse oder höhere Gewalt unverzüglich gemeldet werden müssen und beide Parteien davon befreit sind?** + +Ja, das Dokument sieht eine unverzügliche Meldung von Leistungshindernissen oder höherer Gewalt vor: In Ziffer 25 (Meldung Abweichung Transport) ist geregelt, dass Transporthindernisse oder Unregelmäßigkeiten unverzüglich zu melden sind und Weisungen einzuholen sind. Zudem ist in Ziffer 12 (Meldung Abweichungen) und Ziffer 26 (Meldung Abweichung Ware) die unverzügliche Information bei Abweichungen oder Unstimmigkeiten vorgesehen. Eine Befreiung von der Leistungspflicht bei höherer Gewalt ist nicht explizit genannt, aber die Meldepflicht ist klar geregelt. + +*Score: 85%* + +> Die Meldepflicht ist klar formuliert, eine explizite Befreiung bei höherer Gewalt fehlt jedoch. Die Anforderungen sind dennoch überwiegend erfüllt. + + + +⚠️ **Ist im Dokument eine Verpflichtung des Spediteurs zur Auskunft über den Geschäftsstand und zur Rechenschaftslegung nach Auftragsausführung festgelegt?** + +Nein, eine explizite Verpflichtung zur Auskunft über den Geschäftsstand oder Rechenschaftslegung nach Auftragsausführung ist nicht festgelegt. Lediglich die Dokumentation und Vorlage von Frachtbriefen, Temperaturaufzeichnungen und Transportaufträgen (Ziffer 28) sowie die Meldung von Abweichungen (z. B. Ziffer 24, 25, 26) sind geregelt. + +*Score: 0%* + +> Die AGB enthalten keine direkte Pflicht zur laufenden Auskunft oder Rechenschaftslegung. Dokumentationspflichten beziehen sich auf die Nachweisführung, nicht auf proaktive Informationspflichten. + + + +✅ **Ist im Dokument festgelegt, dass der Spediteur bei Fehlbeständen oder zu befürchteten Gutveränderungen den Auftraggeber unverzüglich zu informieren hat?** + +Ja, der Spediteur ist verpflichtet, bei Fehlbeständen, Beschädigungen, Temperaturabweichungen oder sonstigen Unstimmigkeiten den Auftraggeber unverzüglich zu informieren und Weisungen einzuholen (z. B. Ziffer 9, 12, 26). + +*Score: 100%* + +> Die AGB enthalten klare Meldepflichten bei Abweichungen, die den Auftraggeber unverzüglich zu informieren und Weisungen einzuholen vorschreiben. + + +### Leistungsumfang, Weisungen & Vertragsgegenstand + + + +❌ **Schließt das Dokument die Gestellung von Lademitteln, Ver-/Entladung, Sendungsverfolgung und Retouren mangels ausdrücklicher Vereinbarung vom Leistungsumfang aus?** + +Nein, das Dokument schließt Lademittelgestellung oder Retouren nicht explizit aus. Die Gestellung von Lademitteln (z. B. Palettentausch, Ziffer 29) und die Dokumentation von Rücknahmen (Ziffer 26) sind sogar geregelt. Sendungsverfolgung und Verladung sind hingegen nicht ausdrücklich ausgeschlossen, sondern Teil der Leistungspflichten (z. B. Ziffer 9, 15). + +*Score: 0%* + +> Die AGB enthalten keine explizite Ausschlussklausel für Lademittel oder Retouren. Im Gegenteil: Palettentausch und Rücknahmepflichten sind geregelt. + + + +✅ **Bedürfen weitergehende Leistungs- und Informationspflichten (z. B. Audits, Monitoring) im Dokument der ausdrücklichen Vereinbarung?** + +Ja, weitergehende Leistungs- und Informationspflichten wie Audits oder Monitoring bedürfen der ausdrücklichen Vereinbarung (z. B. Ziffer 7: IFS Logistics/BRCGS/QS nur bei Vereinbarung; Ziffer 20: Subunternehmer nur mit Zustimmung). + +*Score: 100%* + +> Die AGB sehen vor, dass zusätzliche Pflichten (z. B. Zertifizierungen, Subunternehmer) nur bei ausdrücklicher Vereinbarung gelten. Dies entspricht dem Grundsatz der Vertragsfreiheit. + + + +⚠️ **Ist im Dokument geregelt, dass Erklärungen des Lager- und Fahrpersonals zur Wirksamkeit der Genehmigung der Vertragspartei bedürfen?** + +Nein, das Dokument enthält keine Regelung, dass Erklärungen des Lager- oder Fahrpersonals zur Wirksamkeit der Genehmigung der Vertragspartei bedürfen. + +*Score: 0%* + +> Die AGB regeln zwar Genehmigungspflichten (z.B. in Ziffer 2), aber nicht die Wirksamkeit von Erklärungen des Personals. Eine solche Klausel fehlt vollständig. + + + +✅ **Ist im Dokument die Pflicht des Spediteurs zur Beachtung von Weisungen sowie zur unverzüglichen Benachrichtigung bei Nichtbefolgung festgelegt?** + +Ja, das Dokument enthält in mehreren Ziffern (z.B. Ziffer 9, 12, 24, 25) die Pflicht des Auftragnehmers, bei Abweichungen oder Unstimmigkeiten unverzüglich Weisungen einzuholen und uns zu benachrichtigen. Beispiel: Ziffer 9: 'Bei Differenzen [...] hat er die Verladung einzustellen und uns unverzüglich zu benachrichtigen und von uns Weisungen einzuholen.' + +*Score: 100%* + +> Die AGB enthalten klare und umfassende Regelungen zur Beachtung von Weisungen und zur unverzüglichen Benachrichtigung bei Nichtbefolgung. + + + +❌ **Wird im Dokument klargestellt, dass Frachtüberweisungs- oder Nachnahmemweisungen die Zahlungspflicht des Auftraggebers für Vergütung und Aufwendungen unberührt lassen?** + +Nein, das Dokument enthält keine Regelung, die klarstellt, dass Frachtüberweisungs- oder Nachnahmeanweisungen die Zahlungspflicht des Auftraggebers für Vergütung und Aufwendungen unberührt lassen. + +*Score: 0%* + +> Die AGB regeln zwar die Vergütung (z.B. Ziffer 22, 30), aber nicht explizit den Einfluss von Frachtüberweisungs- oder Nachnahmeanweisungen auf die Zahlungspflicht. + + +### Dokumentenführung, EDI & Quittungen + + + +⚠️ **Regelt das Dokument bei EDI-Vereinbarung die betriebsbereiten IT-Systeme, Sicherheitsmaßnahmen gegen unbefugten Zugriff sowie die Pflicht zur rechtzeitigen Mitteilung von Systemänderungen?** + +Nein, das Dokument enthält keine Regelungen zu EDI-Vereinbarungen, betriebsbereiten IT-Systemen, Sicherheitsmaßnahmen gegen unbefugten Zugriff oder der Pflicht zur Mitteilung von Systemänderungen. + +*Score: 0%* + +> Die AGB behandeln keine elektronischen Datenverarbeitungsprozesse oder EDI-spezifische Anforderungen. Solche Klauseln fehlen vollständig. + + + +⚠️ **Stellen elektronisch oder digital erstellte Dokumente im Dokument schriftlichen Dokumenten gleich und ist die elektronische Archivierung mit Vernichtung der Originale erlaubt?** + +Das Dokument enthält keine explizite Regelung zur Gleichstellung elektronischer/digitaler Dokumente mit schriftlichen Dokumenten oder zur elektronischen Archivierung mit Vernichtung der Originale. Es wird in Ziffer 28 lediglich die Übersendung von Originaldokumenten (Frachtbriefe, Temperaturaufzeichnungen, Transportauftrag) verlangt. + +*Score: 0%* + +> Die Anforderungen an digitale Dokumente und Archivierung sind nicht geregelt. Dies ist ein kritischer Mangel, da moderne Transportprozesse zunehmend digital ablaufen. + + + +❌ **Begrenzt die Übernahmquittung im Dokument im Zweifel die Bestätigung auf Anzahl und Art der Packstücke und regelt sie die Widerlegbarkeit bei geschlossenen Ladeeinheiten?** + +Die Übernahmquittung wird in Ziffer 9 erwähnt, jedoch ohne explizite Begrenzung auf Anzahl und Art der Packstücke oder Regelung zur Widerlegbarkeit bei geschlossenen Ladeeinheiten. Es wird lediglich die Unterzeichnung des Frachtbriefs durch Fahrer und Ladestelle verlangt. + +*Score: 30%* + +> Die Regelung ist unpräzise und entspricht nicht den Anforderungen an eine klare Übernahmebestätigung. Eine detailliertere Regelung wäre erforderlich. + + + +❌ **Fordert das Dokument vom Spediteur die Einholung einer Ablieferungsquittung und listet es anerkannte Dokumententypen (z. B. Lieferscheine, Konnossemente) auf?** + +In Ziffer 26 wird die Einholung einer Ablieferungsquittung verlangt, die Vollständigkeit, Unversehrtheit, Temperatur und Ablieferungszeitpunkt bestätigt. Anerkannte Dokumententypen wie Lieferscheine oder Frachtbriefe werden genannt, jedoch keine explizite Liste weiterer anerkannter Dokumente (z. B. Konnossemente). + +*Score: 70%* + +> Die Anforderung ist teilweise erfüllt, aber eine explizite Aufzählung anerkannter Dokumententypen fehlt. Dies könnte zu Unsicherheiten führen. + + + +⚠️ **Ermöglicht das Dokument die elektronische oder digitale Erstellung von Übernahme- oder Ablieferungsquittungen, sofern nicht ausdrücklich Frachtbriefe oder Konnossemente verlangt werden?** + +Das Dokument sieht in Ziffer 28 die Übersendung von Originaldokumenten vor und enthält keine Regelung zur elektronischen oder digitalen Erstellung von Übernahme- oder Ablieferungsquittungen. Es wird ausdrücklich die Übersendung von Originalen verlangt. + +*Score: 0%* + +> Elektronische oder digitale Quittungen sind nicht vorgesehen. Dies entspricht nicht modernen Anforderungen an digitale Prozesse. + + +### Ladungssicherung, Kontrollen, Fristen & Ablieferung + + + +❌ **Trägt der Auftraggeber im Dokument die Pflicht zur verpackungsgerechten Gestaltung, haltbaren Kennzeichnung und erkennbaren Zusammengehörigkeit der Packstücke?** + +Nein. Das Dokument enthält keine Regelung zur verpackungsgerechten Gestaltung, haltbaren Kennzeichnung oder erkennbaren Zusammengehörigkeit der Packstücke. + +*Score: 0%* + +> Die AGB behandeln primär Transportbedingungen, Hygiene, Temperaturkontrolle und Dokumentation, nicht jedoch Verpackungsvorgaben oder Packstückkennzeichnung. + + + +❌ **Ist im Dokument die durchgehende Ladungssicherung bis zur letzten Entladestelle sowie die Pflicht zu Kontrollen auf Vollzähligkeit, Identität und äußerliche Schäden an jeder Schnittstelle festgelegt?** + +Ja, teilweise. In Ziffer 15 (Ladungssicherung) ist die Verantwortung für die beförderungssichere Verladung nach § 412 HGB und VDI 2700 ff geregelt. Kontrollen auf Vollzähligkeit, Identität und Schäden sind in Ziffer 9 (Kontrolle Ware) für die Übernahme festgelegt, jedoch nicht explizit für jede Schnittstelle bis zur letzten Entladestelle. + +*Score: 70%* + +> Die Ladungssicherung ist detailliert geregelt, aber die durchgehende Kontrolle bis zur letzten Entladestelle ist nicht explizit erwähnt. Die Übernahmekontrolle ist jedoch klar definiert. + + + +❌ **Regelt das Dokument die maximale Lade-/Entladezeit von 2 Stunden für 40t-Komplettladungen, den Beginn mit der Ankunft, das Ende bei Erfüllung der Pflichten sowie die Standgeldpflicht bei Überschreitung?** + +Nein. Das Dokument enthält keine Regelung zu einer maximalen Lade-/Entladezeit von 2 Stunden für 40t-Komplettladungen oder zu Standgeldpflichten bei Überschreitung. + +*Score: 0%* + +> Die AGB regeln zwar Meldungen bei Verzögerungen (Ziffer 24) und Standzeiten (Ziffer 27), aber keine konkreten Zeitvorgaben oder Standgeldregelungen. + + + +❌ **Sieht das Dokument vor, dass bei Nichtantreffen des Empfängers eine Ablieferung an erwachsene Familienangehörige oder ermächtigte Personen erfolgt und die Ablieferung nur unter Aufsicht erfolgen darf?** + +Nein. Das Dokument enthält keine Regelung zur Ablieferung an Familienangehörige oder ermächtigte Personen bei Nichtantreffen des Empfängers oder zur Ablieferung unter Aufsicht. + +*Score: 0%* + +> Die AGB regeln die Dokumentation der Ablieferung (Ziffer 26) und Quittierungspflichten, aber keine spezifischen Ablieferungsmodalitäten bei Abwesenheit des Empfängers. + + +### Lagerbedingungen & Bestandsführung + + + +❌ **Ist im Dokument geregelt, dass der Spediteur bei Einlagerung bei einem fremden Lagerhalter den Namen und Ort unverzüglich schriftlich bekannt geben muss?** + +Nein, eine solche Regelung ist im Dokument nicht enthalten. + +*Score: 0%* + +> Das Dokument enthält keine Bestimmungen zur Bekanntgabe von Namen und Ort eines fremden Lagerhalters bei Einlagerung. + + + +✅ **Begrenzt sich die Pflicht zur Sicherung des Gutes im Dokument auf die gesetzlichen Brandschutzvorschriften, es sei denn, weitergehende Maßnahmen sind ausdrücklich vereinbart?** + +Nein, die Pflicht zur Sicherung des Gutes geht über gesetzliche Brandschutzvorschriften hinaus. Es werden spezifische Anforderungen wie Verschlussvorrichtungen, GPS, Alarmanlagen, Wegfahrsperren, Kraftstoffunterbrechung, Verplombung und Königszapfenschloss genannt (vgl. Ziffer 4). + +*Score: 100%* + +> Die Anforderungen an die Gutsicherung sind detailliert und gehen über gesetzliche Brandschutzvorschriften hinaus. + + + +❌ **Beginnt die Lagerübernahme im Dokument mit der Entladung des Fahrzeugs, endet sie mit der Verladung, und ist eine jährliche physische Inventur vorgesehen?** + +Nein, das Dokument regelt keine Lagerübernahme im Sinne einer Einlagerung bei einem fremden Lagerhalter. Es werden zwar Kontrollen bei Übernahme und Transport geregelt (z. B. Ziffer 9, 11, 26), aber keine Lagerbedingungen oder Inventurvorschriften. + +*Score: 0%* + +> Das Dokument enthält keine Regelungen zu Lagerübernahme, Verladung oder Inventur. + + + +✅ **Sieht das Dokument eine Eingangskontrolle nach Art, Menge und Beschaffenheit bei Übernahme sowie regelmäßige Kontrollen durch geeignetes Personal zur Gutsicherung vor?** + +Ja, das Dokument sieht eine Eingangskontrolle bei Übernahme vor (z. B. Ziffer 9: Prüfung auf Vollständigkeit, Unversehrtheit und Identität; Ziffer 11: Temperaturkontrolle mit kalibriertem Stechthermometer). Regelmäßige Kontrollen werden ebenfalls gefordert (z. B. Ziffer 10: Temperaturkontrolle während des Transports; Ziffer 18: Hygieneschulungen). + +*Score: 100%* + +> Die Anforderungen an Eingangskontrollen und regelmäßige Kontrollen sind klar geregelt. + + +### Vergütung, Aufwendungen & Abrechnung + + + +❌ **Ist die vereinbarte Vergütung als Pauschalpreis für Beförderung und Lagerung ausgelegt, sodass gesonderte Nachforderungen für vorhersehbare Kosten ausgeschlossen sind?** + +Nein. Die AGB regeln in Ziffer 22 nur, dass die Frachtvergütung die gesamte Ladefläche umfasst und kurzfristige Beiladungen im Frachtpreis enthalten sind. Eine ausdrückliche Regelung, dass gesonderte Nachforderungen für vorhersehbare Kosten ausgeschlossen sind, fehlt. + +*Score: 30%* + +> Die AGB enthalten keine klare Pauschalpreisvereinbarung für Beförderung und Lagerung. Gesonderte Nachforderungen sind nicht explizit ausgeschlossen. + + + +⚠️ **Ist der Anspruch auf Ersatz notwendiger Aufwendungen wie Detention- oder Demurrage-Kosten sowie Nachverpackungen zum Gutschutz geregelt?** + +Nein. Die AGB regeln in Ziffer 27 zwar Standzeiten und deren Dokumentation, aber es gibt keine explizite Regelung zu Detention- oder Demurrage-Kosten oder Nachverpackungen zum Gutschutz. + +*Score: 20%* + +> Die AGB enthalten keine spezifischen Regelungen zu Ersatzansprüchen für Detention-, Demurrage-Kosten oder Nachverpackungen. + + + +❌ **Ist der Spediteur berechtigt, fremde Abgaben (Zölle, Steuern, Frachten) vorzulegen und der Auftraggeber zur Erstattung und Freistellung verpflichtet?** + +Nein. Die AGB regeln in Ziffer 30 nur, dass die Frachtvergütung alle Kosten des Auftragnehmers abgilt, außer Standzeiten. Eine ausdrückliche Regelung zu fremden Abgaben wie Zöllen oder Steuern fehlt. + +*Score: 10%* + +> Die AGB enthalten keine Regelung zur Vorlage und Erstattung fremder Abgaben wie Zölle oder Steuern durch den Spediteur. + + + +✅ **Erfordert die Fälligkeit der Vergütung den Zugang einer den gesetzlichen Anforderungen genügenden Rechnung oder Zahlungsaufstellung?** + +Ja. In Ziffer 30 ist geregelt, dass die Rechnungen des Auftragnehmers 30 Tage nach Zugang der Rechnungen und der beigefügten Originalempfangsquittungen, Temperaturaufzeichnungen und Kopie des Transportauftrags zur Zahlung fällig sind. Dies entspricht den gesetzlichen Anforderungen an eine ordnungsgemäße Rechnung. + +*Score: 100%* + +> Die Fälligkeit der Vergütung ist an den Zugang einer den gesetzlichen Anforderungen genügenden Rechnung geknüpft, wie in Ziffer 30 geregelt. + + + +⚠️ **Ist die Zahlung in Euro oder fremder Währung erlaubt und die Euro-Umrechnung an einen nachweisbaren amtlichen Kurs zum Zahlungstag gebunden?** + +Die Zahlung ist in Euro erlaubt. Eine explizite Bindung an einen amtlichen Kurs zum Zahlungstag ist nicht geregelt (Ziffer 30: 'Die Rechnungen des Auftragnehmers sind 30 Tage nach Zugang der Rechnungen [...] zur Zahlung fällig.'). + +*Score: 30%* + +> Die AGB enthalten keine Regelung zur Währungsumrechnung oder Bindung an einen amtlichen Kurs. Dies ist für grenzüberschreitende Transporte relevant, um Wechselkursrisiken zu minimieren. + + + +⚠️ **Ist das Gutschriftenverfahren explizit vereinbart oder wird im Zweifel die sofortige Erteilung von Gutschriften nach Leistungserbringung vorausgesetzt?** + +Ein Gutschriftenverfahren ist nicht explizit vereinbart. Die AGB sehen vor, dass Rechnungen des Auftragnehmers nach Leistungserbringung zu zahlen sind (Ziffer 30: 'Die Rechnungen des Auftragnehmers sind 30 Tage nach Zugang [...] zur Zahlung fällig.'). + +*Score: 20%* + +> Es gibt keine Regelung zum Gutschriftenverfahren. Die AGB regeln nur die Fälligkeit von Rechnungen, nicht jedoch die Erteilung von Gutschriften. + + +### Haftungsbeschränkungen & -ausschlüsse + + + +❌ **Ist festgelegt, dass der Spediteur bei verschuldensabhängiger Haftung für Güterschäden statt Schadenersatz Wert- und Kostenersatz nach §§ 429, 430, 432 HGB leistet?** + +Die AGB enthalten keine direkte Regelung zu Wert- und Kostenersatz nach §§ 429, 430, 432 HGB. Stattdessen wird die Haftung des Auftragnehmers auf 40 SZR pro Kilogramm Rohgewicht erhöht (Ziffer 16: 'Die vom Auftragnehmer zu leistende Entschädigung [...] wird auf 40 Sonderziehungsrechte [...] erhöht.'). + +*Score: 0%* + +> Die AGB weichen von den gesetzlichen Bestimmungen ab und regeln die Haftung abweichend, ohne explizit Wert- und Kostenersatz nach HGB zu erwähnen. + + + +✅ **Ist die Haftung für Güterschäden in der Obhut des Spediteurs auf 8,33 SZR pro Kilogramm Rohgewicht begrenzt?** + +Die Haftung für Güterschäden ist auf 8,33 SZR pro Kilogramm Rohgewicht begrenzt (Ziffer 16: 'unsere Haftung als Absender [...] auf einen Betrag von 8,33 Sonderziehungsrechten für jedes Kilogramm des Rohgewichtes der Sendung beschränkt.'). + +*Score: 100%* + +> Die AGB enthalten eine klare und wirksame Haftungsbegrenzung auf 8,33 SZR pro Kilogramm, was den gesetzlichen Vorgaben entspricht. + + + +❌ **Ist ein Gesamthaftungshöchstbetrag von 1,25 Millionen Euro je Schadenfall bzw. 2,5 Millionen Euro je Schadenereignis vereinbart?** + +Nein. Es ist keine Vereinbarung eines Gesamthaftungshöchstbetrags von 1,25 Millionen Euro je Schadenfall bzw. 2,5 Millionen Euro je Schadenereignis enthalten. + +*Score: 0%* + +> Die AGB enthalten keine explizite Regelung zu einem Gesamthaftungshöchstbetrag in den genannten Höhen. Die Haftung ist nur auf 40 SZR/kg begrenzt, was deutlich unter den geforderten Beträgen liegt. + + + +❌ **Ist die Haftung für andere Schäden (außer Personenschäden und Drittsachschäden) auf das Dreifache des Güterschadenslimits plus maximal 125.000 Euro begrenzt?** + +Nein. Die Haftung für andere Schäden (außer Personenschäden und Drittsachschäden) ist nicht auf das Dreifache des Güterschadenslimits plus maximal 125.000 Euro begrenzt. + +*Score: 0%* + +> Die AGB enthalten keine solche spezifische Haftungsbegrenzung. Die Haftung ist nur auf 40 SZR/kg begrenzt, ohne zusätzliche Obergrenzen für andere Schäden. + + + +❌ **Ist die Haftung bei verfügter Lagerung auf 8,33 SZR/kg, maximal 35.000 Euro je Schadenfall und 70.000 Euro jährlich bei Inventurdifferenzen begrenzt?** + +Nein. Die Haftung bei verfügter Lagerung ist nicht auf 8,33 SZR/kg, maximal 35.000 Euro je Schadenfall und 70.000 Euro jährlich bei Inventurdifferenzen begrenzt. + +*Score: 0%* + +> Die AGB enthalten keine Regelung zur Haftungsbegrenzung bei verfügter Lagerung. Die Haftung ist nur auf 40 SZR/kg begrenzt, ohne spezifische Lagerungsregelungen. + + + +❌ **Sind die Haftungsausschlüsse für nautische Fehler, Feuer/Explosion an Bord und vor Reisebeginn bestehende Schiffsmängel gemäß HGB und CMNI enthalten?** + +Nein. Die Haftungsausschlüsse für nautische Fehler, Feuer/Explosion an Bord und vor Reisebeginn bestehende Schiffsmängel gemäß HGB und CMNI sind nicht enthalten. + +*Score: 0%* + +> Die AGB enthalten keine solchen spezifischen Haftungsausschlüsse. Es handelt sich um Transport-AGB ohne Bezug zu Schiffs- oder Lagerungsrisiken. + + + +❌ **Finden die Haftungsbeschränkungen und -ausschlüsse auch auf außervertragliche Ansprüche Anwendung?** + +Nein, die Haftungsbeschränkungen und -ausschlüsse beziehen sich ausschließlich auf vertragliche Ansprüche (vgl. Ziffer 16 der AGB: 'Die Haftung des von uns beauftragten Auftragnehmers richtet sich ausschließlich nach den gesetzlichen Bestimmungen.'). + +*Score: 0%* + +> Die AGB regeln explizit nur vertragliche Haftungsansprüche und schließen außervertragliche Ansprüche nicht ein. Dies entspricht nicht den Anforderungen an eine umfassende Haftungsbeschränkung. + + +### Sicherungsrechte & Aufrechnung + + + +❌ **Ist die Aufrechnung oder Zurückbehaltung nur bei fälligen, unbestrittenen oder rechtskräftig festgestellten Gegenansprüchen zulässig?** + +Nein, die AGB enthalten keine explizite Regelung zur Aufrechnung oder Zurückbehaltung. Lediglich in Ziffer 30 wird die Aufrechnung mit 'etwaigen uns zustehenden Gegenansprüchen' erwähnt, ohne jedoch die gesetzlichen Voraussetzungen (Fälligkeit, Unbestrittenheit oder rechtskräftige Feststellung) zu wiederholen oder einzuschränken. + +*Score: 30%* + +> Die AGB regeln die Aufrechnung nicht ausreichend präzise, um die gesetzlichen Anforderungen zu erfüllen. Eine klare Beschränkung auf fällige, unbestrittene oder rechtskräftig festgestellte Gegenansprüche fehlt. + + + +⚠️ **Ist das gesetzliche Pfand- und Zurückbehaltungsrecht anerkannt und die Pfandverwertungsfrist auf eine Woche festgelegt?** + +Nein, das gesetzliche Pfand- und Zurückbehaltungsrecht wird nicht explizit anerkannt, und eine Pfandverwertungsfrist von einer Woche ist nicht festgelegt. Die AGB enthalten hierzu keine Regelungen. + +*Score: 0%* + +> Die AGB schweigen vollständig zu Pfand- und Zurückbehaltungsrechten sowie deren Verwertung. Dies entspricht nicht den gesetzlichen Anforderungen. + + + +⚠️ **Ist dem Auftraggeber das Recht eingeräumt, die Pfandverwertung durch Stellung eines gleichwertigen Sicherungsmittels zu untersagen?** + +Nein, die AGB enthalten keine Regelung, die dem Auftraggeber das Recht einräumt, die Pfandverwertung durch Stellung eines gleichwertigen Sicherungsmittels zu untersagen. + +*Score: 0%* + +> Die AGB regeln weder das Pfandrecht noch die Möglichkeit, dessen Verwertung durch Sicherheitsleistung zu untersagen. Dies entspricht nicht den gesetzlichen Vorgaben. + + +### Versicherungsbesorgung & Nebenpflichten + + + +⚠️ **Ist klargestellt, dass weitergehende Leistungs- und Informationspflichten nur bei ausdrücklicher Vereinbarung gelten?** + +Nein. Das Dokument enthält keine explizite Regelung, dass weitergehende Leistungs- und Informationspflichten nur bei ausdrücklicher Vereinbarung gelten. Vielmehr werden zahlreiche Pflichten (z.B. Genehmigungen, Hygiene, Temperaturkontrolle) ohne Einschränkung auf Vereinbarungen auferlegt. + +*Score: 0%* + +> Die AGB enthalten keine Klausel, die weitergehende Pflichten an eine ausdrückliche Vereinbarung knüpft. Dies wäre jedoch erforderlich, um die Anforderung zu erfüllen. + + + +⚠️ **Ist geregelt, dass der Spediteur die Güterversicherung auf Auftrag besorgt oder bei Interesse des Auftraggebers (z.B. Warenwertangabe) von sich aus eindeckt?** + +Nein. Das Dokument regelt keine Pflicht des Auftragnehmers zur Besorgung einer Güterversicherung oder zur Eindeckung eines Versicherungsschutzes auf Initiative des Auftraggebers (z.B. bei Warenwertangabe). + +*Score: 0%* + +> Es fehlt eine klare Regelung zur Güterversicherung oder zur Eindeckung von Versicherungsschutz auf Weisung oder bei Interesse des Auftraggebers. + + + +⚠️ **Enthält das Dokument die Pflicht zur Befolgung von Versicherungswisungen sowie zur unverzüglichen Mitteilung, falls kein Versicherungsschutz möglich ist?** + +Nein. Das Dokument enthält keine explizite Pflicht zur Befolgung von Versicherungsweisungen oder zur unverzüglichen Mitteilung, falls kein Versicherungsschutz möglich ist. Lediglich in Ziffer 19 (Mindestlohn) wird auf die Einhaltung gesetzlicher Vorschriften verwiesen, jedoch nicht spezifisch zu Versicherungsweisungen. + +*Score: 0%* + +> Es fehlt eine konkrete Regelung zur Befolgung von Versicherungsweisungen oder zur Kommunikation von Versicherungslücken. + + + +⚠️ **Ist eine ortsübliche Vergütung für die Abwicklung von Versicherungsfällen und Havereien auf Weisung vereinbart?** + +Nein. Das Dokument enthält keine Regelung zu einer ortsüblichen Vergütung für die Abwicklung von Versicherungsfällen oder Havereien auf Weisung. + +*Score: 0%* + +> Es fehlt jegliche Vereinbarung oder Regelung zu Vergütungsfragen im Zusammenhang mit Versicherungsfällen oder Havereien. + + +### Haftungsausschlüsse & -begrenzungen + + + +❌ **Ist bei Verletzung vertragswesentlicher Pflichten der Ersatzanspruch auf den vorhersehbaren, typischen Schaden begrenzt?** + +Nein, die AGB enthalten keine explizite Begrenzung des Ersatzanspruchs auf vorhersehbare, typische Schäden bei Verletzung vertragswesentlicher Pflichten. Die Haftung wird zwar in Ziffer 16 auf 40 Sonderziehungsrechte (SZR) pro kg Rohgewicht erhöht (§ 431 HGB) und die Absenderhaftung auf 8,33 SZR pro kg beschränkt, jedoch wird keine Einschränkung auf vorhersehbare oder typische Schäden vorgenommen. Die Pönale-Regelung (150 € bei Verspätung/Nichteinhaltung) deutet eher auf eine pauschale Schadensersatzregelung hin, ohne Bezug zur Vorhersehbarkeit. + +*Score: 30%* + +> Die AGB regeln zwar Haftungshöchstgrenzen, aber keine Begrenzung auf vorhersehbare, typische Schäden. Dies wäre jedoch nach § 276 Abs. 3 BGB und § 435 HGB erforderlich, um eine wirksame Haftungsbegrenzung zu schaffen. Die Regelungen sind daher unvollständig und nicht ausreichend. + + + +❌ **Ist die Haftung des Auftraggebers nach §§ 414, 455, 468 und 488 HGB auf 200.000 Euro je Schadenereignis begrenzt?** + +Nein. Die Haftung des Auftraggebers als Absender ist in Ziffer 16 der AGB auf 8,33 Sonderziehungsrechte (SZR) je Kilogramm des Rohgewichts der Sendung begrenzt ("Abweichend von § 414 HGB ist unsere Haftung als Absender aus § 414 HGB auf einen Betrag von 8,33 Sonderziehungsrechten für jedes Kilogramm des Rohgewichtes der Sendung beschränkt"). Eine pauschale Begrenzung auf 200.000 Euro je Schadenereignis wird nicht genannt. + +*Score: 0%* + +> Die Haftungsbegrenzung des Auftraggebers ist zwar klar geregelt, aber nicht in der geforderten Höhe von 200.000 Euro. Stattdessen wird auf 8,33 SZR/kg abgestellt, was je nach Rohgewicht deutlich darunter oder darüber liegen kann. Eine pauschale Euro-Begrenzung fehlt vollständig. + + +### Haftpflichtversicherung & Nachweispflicht + + + +✅ **Ist der Spediteur verpflichtet, auf Verlangen des Auftraggebers das Bestehen einer gültigen Haftpflichtversicherung durch eine Versicherungsbestätigung nachzuweisen?** + +Ja, der Auftragnehmer ist verpflichtet, auf Verlangen das Bestehen einer gültigen Haftpflichtversicherung durch eine Versicherungsbestätigung nachzuweisen (Ziffer 17: 'Auf Verlangen hat uns der Auftragnehmer das Bestehen dieser Versicherungen durch eine Bestätigung der Versicherungsgesellschaft [...] nachzuweisen'). + +*Score: 100%* + +> Die Anforderung ist klar und explizit in Ziffer 17 geregelt. Der Nachweis ist auf Verlangen zu erbringen, was den gesetzlichen Anforderungen entspricht. + + + +❌ **Kann der Auftraggeber den Vertrag außerordentlich kündigen, wenn der Spediteur den Versicherungsnachweis innerhalb einer angemessenen Frist nicht erbringt?** + +Nein, die AGB enthalten keine ausdrückliche Regelung zur außerordentlichen Kündigung bei Nichtvorlage des Versicherungsnachweises. Ziffer 33 regelt zwar eine Kürzung der Frachtvergütung und Kündigungsmöglichkeiten bei Vertragsverletzungen, aber nicht spezifisch für Versicherungsnachweise. + +*Score: 0%* + +> Es fehlt eine klare vertragliche Regelung zur Kündigung bei Nichtvorlage des Versicherungsnachweises. Die allgemeine Kündigungsregelung in Ziffer 33 ist nicht spezifisch genug. + + + +❌ **Darf der Spediteur sich nur bei ausreichendem Versicherungsschutz bei Auftragserteilung auf die Haftungsbestimmungen der ADSp berufen?** + +Nein, die AGB enthalten keine direkte Regelung, dass sich der Spediteur nur bei ausreichendem Versicherungsschutz auf die Haftungsbestimmungen der ADSp berufen darf. Ziffer 16 regelt zwar die Haftung, aber nicht den Zusammenhang mit der ADSp oder Versicherungsschutz bei Auftragserteilung. + +*Score: 0%* + +> Die AGB verweisen nicht auf die ADSp (Ziffer 16 regelt die Haftung eigenständig) und enthalten keine Bedingung für die Berufung auf ADSp-Haftungsbestimmungen in Abhängigkeit vom Versicherungsschutz. + + +### Rechtswahl, Erfüllungsort & Gerichtsstand + + + +✅ **Gilt für die Rechtsbeziehung zwischen Spediteur und Auftraggeber deutsches Recht?** + +Ja, für die Rechtsbeziehung gilt deutsches Recht (Ziffer 36: 'Zwischen dem Auftragnehmer und uns gilt deutsches Recht'). + +*Score: 100%* + +> Die Rechtswahl ist explizit in Ziffer 36 geregelt und entspricht den gesetzlichen Anforderungen. + + + +❌ **Ist der Erfüllungsort für alle Beteiligten der Ort derjenigen Niederlassung des Spediteurs, an die der Auftrag gerichtet ist?** + +Nein. Der Erfüllungsort wird im Dokument nicht explizit geregelt. Es wird lediglich der Gerichtsstand in Ziffer 36 als "Schortens" vereinbart, aber keine Aussage zum Erfüllungsort getroffen. ("Der Gerichtsstand Schortens ist als vereinbart.") + +*Score: 0%* + +> Die AGB enthalten keine Regelung zum Erfüllungsort. Dies ist ein zentrales Manko, da der Erfüllungsort für die Bestimmung des anwendbaren Rechts und die Zuständigkeit von Bedeutung ist. Eine klare Regelung fehlt vollständig. + + +### Compliance, Geheimhaltung & Arbeitsrecht + + + +✅ **Ist der Spediteur verpflichtet, Mindestlohn- und Arbeitsplatzvorschriften einzuhalten und den Auftraggeber von Ansprüchen wegen Nichtzahlung des Mindestlohns freizustellen?** + +Ja, der Auftragnehmer ist verpflichtet, die Vorschriften des Mindestlohngesetzes einzuhalten (§19). Bei Verstößen hat er den Auftraggeber von Ansprüchen Dritter freizustellen und entstandene Schäden zu ersetzen ('hat uns bei Verstößen von etwaigen Ansprüchen Dritter freizustellen und uns alle hieraus entstehenden Schäden zu ersetzen'). + +*Score: 100%* + +> Die AGB enthalten eine klare Verpflichtung zur Einhaltung des Mindestlohns und eine Freistellungspflicht bei Verstößen. Dies entspricht den gesetzlichen Anforderungen. + + + +✅ **Ist sichergestellt, dass der Spediteur GüKG-Erlaubnis/Lizenz besitzt, Fahrerqualifikation nach §7b GüKG erfüllt und gesetzlich mitzuführende Dokumente auf Anforderung vorlegt?** + +Ja, der Auftragnehmer muss über die erforderlichen Genehmigungen nach §§ 3, 6 GüKG verfügen und diese auf Verlangen nachweisen (§2). Die Fahrer müssen die jeweilige Erlaubnisausfertigung für das eingesetzte Fahrzeug mitführen ('verfügt und die Fahrer die jeweilig für den Transport erforderliche Erlaubnisausfertigung für das jeweilig eingesetzte Fahrzeug mit sich führen'). + +*Score: 100%* + +> Die AGB fordern explizit die Einhaltung der GüKG-Vorschriften und den Nachweis der Genehmigungen. Dies entspricht den gesetzlichen Anforderungen. + + + +❌ **Ist die Organisation der Arbeitszeiten, Lenk- und Ruhezeiten sowie ein generelles Alkohol- und Drogenverbot beim Führen des Fahrzeugs geregelt?** + +Nein, die AGB enthalten keine explizite Regelung zu Lenk- und Ruhezeiten oder ein generelles Alkohol- und Drogenverbot. Lediglich die Einhaltung aller gesetzlichen Vorschriften wird pauschal gefordert (§19), ohne konkrete Vorgaben zu nennen. + +*Score: 0%* + +> Die AGB verweisen zwar auf die Einhaltung gesetzlicher Vorschriften, enthalten aber keine konkreten Regelungen zu Lenk- und Ruhezeiten oder Alkohol-/Drogenverboten. Dies ist ein erhebliches Compliance-Defizit. + + + +⚠️ **Werden die Einhaltung von UNGC/ILO-Grundsätzen, das Verbot von Kinder- und Zwangsarbeit, Diskriminierungsverbote sowie Umwelt- und Antikorruptionsstandards verbindlich festgelegt?** + +Nein, die AGB enthalten keine verbindlichen Festlegungen zu UNGC/ILO-Grundsätzen, Kinder- oder Zwangsarbeit, Diskriminierungsverboten oder Umwelt-/Antikorruptionsstandards. Es wird lediglich auf die Einhaltung aller gesetzlichen Vorschriften verwiesen (§19), ohne konkrete Standards zu nennen. + +*Score: 0%* + +> Die AGB enthalten keine spezifischen Regelungen zu ethischen oder ökologischen Standards. Lediglich die Einhaltung gesetzlicher Vorschriften wird pauschal gefordert, was für eine verbindliche Festlegung nicht ausreicht. + + +### Manuelle Abfragen + + + +✅ **Sind konditionale Zahlungen, wie Pönalen vorhanden? +Liste sie alle auf!** + +Ja, es ist eine Pönale in Höhe von 150,-- Euro bei verspäteter Anlieferung und/oder Nichteinhaltung der Vorgaben aus dem Transportauftrag vorgesehen ("Bei verspäteter Anlieferung und/oder Nichteinhaltung der Vorgaben aus dem Transportauftrag, wird eine Pönale von 150,-- Euro berechnet zzgl. der daraus entstandenen Kosten oder Schäden."). + +*Score: 100%* + +> Die Pönale ist klar definiert und umfasst sowohl Verspätungen als auch Nichteinhaltungen von Transportvorgaben. Die Höhe ist konkret angegeben. + +--- \ No newline at end of file