# Dokumenten-Check Projekt: https://projects.zoho.eu/portal/aquantico#project/323095000000333012 ## Datenmodell ```mermaid ``` ```mermaid erDiagram dc_users { number id PK varchar2_50 username UK varchar2_100 email UK varchar2_255 password_hash varchar2_20 role number is_active date created_at date updated_at } dc_user_roles { number id PK varchar2_20 role_name UK clob description } dc_document_types { number id PK varchar2_100 name clob description } dc_reference_documents { number id PK number uploaded_by FK number document_type_id FK varchar2_100 name clob description varchar2_500 file_path date upload_date varchar2_100 mime_type } dc_question_catalogs { number id PK number created_by_user FK number based_on_reference FK number document_type_id FK varchar2_100 name varchar2_4000 description } dc_question_categories { number id PK number catalog_id FK varchar2_100 name clob description } dc_questions { number id PK number category_id FK clob question_text varchar2_20 evaluation_type number threshold varchar2_20 result_handling clob example_0_percent clob example_100_percent } dc_projects { number id PK number catalog_id FK number created_by_user FK varchar2_100 name clob description varchar2_20 status number progress date completed_at varchar2_100 notification_email } dc_project_documents { number id PK number project_id FK number document_type_id FK blob original_file blob translated_file varchar2_255 filename varchar2_100 mime_type date uploaded_at } dc_results { number id PK number project_id FK number question_id FK number doc_id FK clob answer number score varchar2_20 result_type number deviation number warning clob the_comment date evaluated_at } dc_users ||--o{ dc_reference_documents : "uploads" dc_users ||--o{ dc_question_catalogs : "creates" dc_users ||--o{ dc_projects : "creates" dc_document_types ||--o{ dc_reference_documents : "used_in" dc_document_types ||--o{ dc_question_catalogs : "used_in" dc_document_types ||--o{ dc_project_documents : "used_in" dc_reference_documents ||--o{ dc_question_catalogs : "based_on" dc_question_catalogs ||--o{ dc_question_categories : "contains" dc_question_categories ||--o{ dc_questions : "contains" dc_question_catalogs ||--o{ dc_projects : "uses" dc_projects ||--o{ dc_project_documents : "contains" dc_projects ||--o{ dc_results : "has" dc_questions ||--o{ dc_results : "answered_in" dc_project_documents ||--o{ dc_results : "evaluated_in" ``` ## Backend ```mermaid graph TB subgraph Browser["🌐 Browser"] APEX["Oracle APEX\nFrontend UI"] end subgraph OracleDB["πŸ—„οΈ Oracle Database 23c"] ORDS["ORDS REST Services\n/api/dc/..."] subgraph Schema["DB Schema"] Config["Konfiguration\ndc_document_types\ndc_question_catalogs\ndc_question_categories\ndc_questions"] Exec["AusfΓΌhrung\ndc_projects\ndc_project_documents\ndc_results"] Users["dc_users\ndc_user_roles\ndc_reference_documents"] end ORDS <--> Schema end subgraph Backend["β˜• Quarkus Backend (Port 8090)"] CheckRes["CheckResource\nPOST /check/{projectId}"] Orch["CheckOrchestrationService\nHauptablauf (async)"] DocProc["DocumentProcessingService\nPDF/DOCX/ODT β†’ Markdown"] EvalSvc["EvaluationService\nÜbersetzung + Auswertung"] OrdsClient["OrdsClient\nMicroProfile REST Client"] CheckRes -->|"202 Accepted\nFire & Forget"| Orch Orch --> DocProc Orch --> EvalSvc Orch --> OrdsClient end subgraph AI["πŸ€– Ollama (ollama.aquantico.de)"] MainModel["gpt-oss:20b\nÜbersetzung + Fragenauswertung"] OcrModel["quen3.5:9b\nPDF Vision-OCR"] end APEX -->|"Dokument hochladen\nProjekt anlegen"| ORDS APEX -->|"POST /check/{id}"| CheckRes APEX -->|"Ergebnisse lesen"| ORDS OrdsClient -->|"Projekt/Dok/Fragen laden\nErgebnisse speichern"| ORDS DocProc -->|"Base64-Bild\nX-API-KEY"| OcrModel EvalSvc -->|"Text + Prompt\nX-API-KEY"| MainModel subgraph PhaseA["Phase A (pro Dokument)"] A1["Download BLOB"] --> A2["OCR / Konvertierung\nβ†’ Markdown"] A2 --> A3["Übersetzung β†’ Deutsch"] A3 --> A4["Texte speichern (ORDS)"] end subgraph PhaseB["Phase B (pro Dok Γ— Frage)"] B1["KI-Auswertung\nscore 0–100"] --> B2["Abweichung/Hinweis\nberechnen (Java)"] B2 --> B3["Ergebnis speichern\n(ORDS)"] end Orch --> PhaseA Orch --> PhaseB style Browser fill:#dbeafe style OracleDB fill:#fef3c7 style Backend fill:#dcfce7 style AI fill:#f3e8ff style PhaseA fill:#f0f9ff style PhaseB fill:#f0f9ff ``` ## Dialoge ### Fragenkatalog #### Tabelle * Name * Beschreibung * Referenz-Dokument #### Dialog * Name (Im Kopf aussuchen) * Referenz-Dokument Upload (Im Kopf) * Master/Detail * Fragen-Kategorien * Fragen ### Projekte #### Tabelle * Name * Beschreibung * Anz ok,nok,undefiniert #### Dialog * Name (Im Kopf Aussuchen) * Beschreibung * Master/Details * Dokumente * Ergebnisse ## Source ```mermaid sequenceDiagram autonumber participant Client as APEX / Client participant AKF as ApiKeyFilter participant CR as CheckResource participant COS as CheckOrchestrationService participant OLF as OrdsLoggingFilter participant OC as OrdsClient participant DPS as DocumentProcessingService participant ES as EvaluationService participant OCR_MDL as OllamaChatModel [ocr] participant MAIN_MDL as OllamaChatModel [main] participant ORDS as ORDS / Oracle DB participant OLLAMA as ollama.aquantico.de %% ─── Eingehende Anfrage ────────────────────────────────────────────────── Client->>AKF: POST /check/{projectId} (Header: X-API-KEY) AKF->>AKF: filter(ContainerRequestContext) Note over AKF: PrΓΌft X-API-KEY gegen dc.api.key
β†’ 401 wenn ungΓΌltig, sonst weiter AKF->>CR: weiterleiten (Key gΓΌltig) CR->>CR: startCheck(projectId) CR-->>Client: 202 Accepted { message, project_id, hint } Note over CR,COS: CompletableFuture.runAsync() – Fire & Forget CR-)COS: process(projectId) %% ─── Alle ORDS-Aufrufe laufen durch OrdsLoggingFilter ─────────────────── Note over OLF,ORDS: OrdsLoggingFilter.filter() loggt jeden Request/Response
des REST-Clients (ClientRequestFilter + ClientResponseFilter) %% ─── Initialisierung ──────────────────────────────────────────────────── rect rgb(235, 245, 255) Note over COS,ORDS: Initialisierung COS->>OC: getProject(projectId) OC->>OLF: filter(ClientRequestContext) OLF->>ORDS: GET /api/dc/projects/{id} ORDS-->>OLF: OrdsProject (JSON) OLF->>OLF: filter(ClientRequestContext, ClientResponseContext) OLF-->>COS: OrdsProject Note over COS: Status-PrΓΌfung: nur PENDING wird verarbeitet COS->>OC: startProject(projectId) OC->>ORDS: POST /api/dc/projects/{id}/start ORDS-->>COS: 200 OK | 409 Conflict COS->>OC: getDocuments(projectId) OC->>ORDS: GET /api/dc/projects/{id}/documents/ ORDS-->>COS: OrdsDocumentList COS->>OC: getQuestions(catalogId) OC->>ORDS: GET /api/dc/catalogs/{id}/questions/ ORDS-->>COS: OrdsQuestionList COS->>OC: deleteResults(projectId) OC->>ORDS: DELETE /api/dc/projects/{id}/results Note over ORDS: Alte Ergebnisse lΓΆschen β†’ Re-Processing mΓΆglich end %% ─── Phase A: OCR + Übersetzung ───────────────────────────────────────── rect rgb(220, 255, 225) Note over COS,OLLAMA: Phase A – OCR + Übersetzung (pro Dokument) loop fΓΌr jedes OrdsDocument COS->>OC: downloadFile(docId) OC->>ORDS: GET /api/dc/documents/{id}/file ORDS-->>COS: byte[] (BLOB) COS->>DPS: extractText(fileBytes, mimeType, filename) DPS->>DPS: resolveMimeType(mimeType, filename) alt MIME = application/pdf DPS->>DPS: extractFromPdf(fileBytes) DPS->>DPS: Loader.loadPDF(fileBytes) β†’ PDDocument DPS->>DPS: new PDFRenderer(pdf) loop fΓΌr jede PDF-Seite DPS->>DPS: renderer.renderImageWithDPI(i, 200f, RGB) DPS->>DPS: ImageIO.write(image, "PNG", baos) β†’ Base64 DPS->>DPS: ocrPage(base64Image, pageNum, totalPages) DPS->>OCR_MDL: generate(List) Note over DPS,OCR_MDL: UserMessage: ImageContent(PNG) + TextContent(OCR-Prompt) OCR_MDL->>OLLAMA: POST /api/chat [Vision, X-API-KEY] OLLAMA-->>OCR_MDL: Markdown-Seitentext OCR_MDL-->>DPS: String (Seite i) end else MIME = application/vnd...wordprocessingml / msword DPS->>DPS: extractFromDocx(fileBytes) DPS->>DPS: new XWPFDocument(stream) loop fΓΌr jedes Body-Element DPS->>DPS: paragraphToMarkdown(XWPFParagraph) DPS->>DPS: tableToMarkdown(XWPFTable) end else MIME = application/vnd.oasis.opendocument.text DPS->>DPS: extractFromOdt(fileBytes) DPS->>DPS: OdfTextDocument.loadDocument(stream) DPS->>DPS: getElementsByTagName("text:p") β†’ Markdown else text/plain | text/markdown Note over DPS: direkt als UTF-8 String zurΓΌck end DPS-->>COS: originalText (Markdown) COS->>ES: translate(originalText) ES->>MAIN_MDL: generate(List) Note over ES,MAIN_MDL: SystemMessage: Übersetzer-Prompt
UserMessage: originalText MAIN_MDL->>OLLAMA: POST /api/chat [X-API-KEY] OLLAMA-->>MAIN_MDL: Übersetzung (Deutsch) MAIN_MDL-->>ES: Response ES-->>COS: translatedText COS->>OC: updateTexts(docId, TextsRequest(original, translated)) OC->>ORDS: PUT /api/dc/documents/{id}/texts COS->>COS: pushProgress(projectId, step, total) COS->>OC: updateProgress(projectId, ProgressRequest(pct)) OC->>ORDS: PUT /api/dc/projects/{id}/progress end end %% ─── Phase B: Fragen auswerten ─────────────────────────────────────────── rect rgb(255, 245, 220) Note over COS,OLLAMA: Phase B – Fragenauswertung (pro Dokument Γ— Frage) loop fΓΌr jedes OrdsDocument Γ— jede OrdsQuestion COS->>ES: evaluate(question, originalText) ES->>ES: buildEvaluationSystem() ES->>ES: buildEvaluationUser(question, documentText) Note over ES: Prompt: Dokument + Frage + Schwellwert
+ Beispiele 0% / 100% ES->>MAIN_MDL: generate(List) MAIN_MDL->>OLLAMA: POST /api/chat [X-API-KEY] OLLAMA-->>MAIN_MDL: JSON { answer, score, result_type, the_comment } MAIN_MDL-->>ES: Response ES->>ES: parseEvaluationResult(rawText, question) ES->>ES: extractJson(text) Note over ES: Regex: ```json...``` oder erstes {...} im Text ES->>ES: objectMapper.readValue(json, EvaluationResult) ES->>ES: deriveResultType(score, threshold) [Fallback] ES-->>COS: EvaluationResult { answer, score, resultType, theComment } Note over COS: score < threshold?
result_handling=ABWEICHUNG β†’ deviation=1
result_handling=HINWEIS β†’ warning=1 COS->>OC: saveResult(projectId, ResultRequest) OC->>ORDS: POST /api/dc/projects/{id}/results β†’ 201 Created COS->>COS: pushProgress(projectId, step, total) COS->>OC: updateProgress(projectId, ProgressRequest(pct)) OC->>ORDS: PUT /api/dc/projects/{id}/progress end end %% ─── Abschluss ─────────────────────────────────────────────────────────── COS->>OC: completeProject(projectId) OC->>ORDS: POST /api/dc/projects/{id}/complete Note over ORDS: status β†’ COMPLETED, completed_at = SYSDATE ```