initial commit

This commit is contained in:
Wolf G. Beckmann
2026-04-27 14:57:33 +02:00
commit 1816168d1f
60 changed files with 4615 additions and 0 deletions

297
Scripts/DC_BACKEND_PKG.sql Normal file
View File

@@ -0,0 +1,297 @@
-- =============================================================================
-- DC_BACKEND_PKG
-- PL/SQL-Schnittstelle zum Quarkus dc-backend (Kubernetes Service dc-backend:8090)
--
-- Voraussetzungen:
-- - APEX_WEB_SERVICE-Berechtigung für das Schema:
-- GRANT EXECUTE ON APEX_240100.APEX_WEB_SERVICE TO <schema>; -- APEX-Version anpassen
-- - ACL für ausgehende HTTP-Verbindung im selben Namespace:
-- DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
-- host => 'dc-backend', lower_port => 8090, upper_port => 8090,
-- ace => xs$ace_type(privilege_list => xs$name_list('connect','resolve'),
-- principal_name => '<schema>', principal_type => xs_acl.ptype_db));
--
-- Deployment:
-- sqlplus user/pass@db @"Scripts/DC_BACKEND_PKG.sql"
-- =============================================================================
-- =============================================================================
-- Package Spec
-- =============================================================================
CREATE OR REPLACE PACKAGE DC_BACKEND_PKG AS
-- Basis-URL des dc-backend Kubernetes Service
c_base_url CONSTANT VARCHAR2(200) := 'http://dc-backend:8090';
-- API-Key (X-API-KEY Header).
-- Leer = kein Auth (Dev-Modus).
-- Zur Laufzeit setzen: DC_BACKEND_PKG.g_api_key := 'mein-key';
g_api_key VARCHAR2(500) := '';
-- -------------------------------------------------------------------------
-- Startet die KI-gestützte Dokumentenprüfung für ein Projekt (asynchron).
-- Das Projekt muss im Status PENDING sein.
-- POST /check/{p_project_id} → 202 Accepted
--
-- p_project_id ID des dc_projects-Eintrags
-- p_status_code HTTP-Statuscode der Antwort (202 = OK, 409 = nicht PENDING)
-- p_response JSON-Antwort des Backend
-- -------------------------------------------------------------------------
PROCEDURE start_check(
p_project_id IN NUMBER,
p_status_code OUT NUMBER,
p_response OUT VARCHAR2
);
-- -------------------------------------------------------------------------
-- Health-Check: prüft ob der dc-backend Service erreichbar ist.
-- GET /check/health → 200 UP
--
-- Rückgabe: TRUE = Service läuft, FALSE = nicht erreichbar
-- -------------------------------------------------------------------------
FUNCTION health_check RETURN BOOLEAN;
-- -------------------------------------------------------------------------
-- Startet die Prüfung und wirft eine Exception wenn nicht 202 zurückkommt.
-- Convenience-Wrapper für direkte APEX-Button-Aufrufe.
-- -------------------------------------------------------------------------
PROCEDURE start_check_or_raise(
p_project_id IN NUMBER
);
-- -------------------------------------------------------------------------
-- Fortschritts-Record
-- -------------------------------------------------------------------------
TYPE t_project_status IS RECORD (
status dc_projects.status%TYPE,
processing_progress dc_projects.progress%TYPE,
is_completed BOOLEAN,
is_running BOOLEAN,
is_pending BOOLEAN
);
-- -------------------------------------------------------------------------
-- Liest Status und Fortschritt eines Projekts direkt aus der DB.
-- Kein REST-Aufruf sofort und transaktionssicher.
--
-- Beispiel:
-- l_s := DC_BACKEND_PKG.get_status(1);
-- DBMS_OUTPUT.PUT_LINE(l_s.status || ' ' || l_s.processing_progress || '%');
-- -------------------------------------------------------------------------
FUNCTION get_status(
p_project_id IN NUMBER
) RETURN t_project_status;
-- -------------------------------------------------------------------------
-- Wartet (polling) bis das Projekt COMPLETED oder ein Timeout erreicht ist.
-- Wirft eine Exception bei Timeout oder wenn das Projekt nicht gefunden wird.
--
-- p_timeout_sec Max. Wartezeit in Sekunden (Default: 1800 = 30 Min.)
-- p_interval_sec Polling-Intervall in Sekunden (Default: 5)
-- -------------------------------------------------------------------------
PROCEDURE wait_for_completion(
p_project_id IN NUMBER,
p_timeout_sec IN NUMBER DEFAULT 1800,
p_interval_sec IN NUMBER DEFAULT 5
);
END DC_BACKEND_PKG;
/
-- =============================================================================
-- Package Body
-- =============================================================================
CREATE OR REPLACE PACKAGE BODY DC_BACKEND_PKG AS
-- -------------------------------------------------------------------------
-- Setzt gemeinsame Request-Header (Content-Type + optional API-Key)
-- -------------------------------------------------------------------------
PROCEDURE set_headers IS
l_idx PLS_INTEGER := 1;
BEGIN
APEX_WEB_SERVICE.G_REQUEST_HEADERS.DELETE;
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).NAME := 'Content-Type';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).VALUE := 'application/json';
l_idx := l_idx + 1;
IF g_api_key IS NOT NULL AND LENGTH(TRIM(g_api_key)) > 0 THEN
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).NAME := 'X-API-KEY';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(l_idx).VALUE := g_api_key;
END IF;
END set_headers;
-- -------------------------------------------------------------------------
PROCEDURE start_check(
p_project_id IN NUMBER,
p_status_code OUT NUMBER,
p_response OUT VARCHAR2
) IS
l_url VARCHAR2(500);
l_response CLOB;
BEGIN
l_url := c_base_url || '/check/' || TO_CHAR(p_project_id);
set_headers();
l_response := APEX_WEB_SERVICE.MAKE_REST_REQUEST(
p_url => l_url,
p_http_method => 'POST',
p_body => '{}' -- leerer Body, projectId ist im Pfad
);
p_status_code := APEX_WEB_SERVICE.G_STATUS_CODE;
p_response := SUBSTR(l_response, 1, 4000);
EXCEPTION
WHEN OTHERS THEN
p_status_code := -1;
p_response := 'Verbindungsfehler: ' || SQLERRM;
END start_check;
-- -------------------------------------------------------------------------
FUNCTION health_check RETURN BOOLEAN IS
l_url VARCHAR2(500);
l_response CLOB;
l_status NUMBER;
BEGIN
l_url := c_base_url || '/check/health';
set_headers();
l_response := APEX_WEB_SERVICE.MAKE_REST_REQUEST(
p_url => l_url,
p_http_method => 'GET'
);
l_status := APEX_WEB_SERVICE.G_STATUS_CODE;
RETURN l_status = 200;
EXCEPTION
WHEN OTHERS THEN
RETURN FALSE;
END health_check;
-- -------------------------------------------------------------------------
PROCEDURE start_check_or_raise(
p_project_id IN NUMBER
) IS
l_status NUMBER;
l_response VARCHAR2(4000);
BEGIN
start_check(
p_project_id => p_project_id,
p_status_code => l_status,
p_response => l_response
);
IF l_status = 202 THEN
-- OK: Verarbeitung wurde gestartet
NULL;
ELSIF l_status = 409 THEN
RAISE_APPLICATION_ERROR(-20100,
'Projekt ' || p_project_id || ' ist nicht im Status PENDING.');
ELSIF l_status = 401 THEN
RAISE_APPLICATION_ERROR(-20101,
'Ungültiger API-Key für dc-backend.');
ELSIF l_status = -1 THEN
RAISE_APPLICATION_ERROR(-20102,
'dc-backend nicht erreichbar: ' || l_response);
ELSE
RAISE_APPLICATION_ERROR(-20103,
'Unerwarteter HTTP-Status ' || l_status || ': ' || l_response);
END IF;
END start_check_or_raise;
-- -------------------------------------------------------------------------
FUNCTION get_status(
p_project_id IN NUMBER
) RETURN t_project_status IS
l_rec t_project_status;
BEGIN
SELECT status, progress
INTO l_rec.status, l_rec.processing_progress
FROM dc_projects
WHERE id = p_project_id;
l_rec.is_completed := l_rec.status = 'COMPLETED';
l_rec.is_running := l_rec.status = 'IN_PROGRESS';
l_rec.is_pending := l_rec.status = 'PENDING';
RETURN l_rec;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20110,
'Projekt ' || p_project_id || ' nicht gefunden.');
END get_status;
-- -------------------------------------------------------------------------
PROCEDURE wait_for_completion(
p_project_id IN NUMBER,
p_timeout_sec IN NUMBER DEFAULT 1800,
p_interval_sec IN NUMBER DEFAULT 5
) IS
l_start TIMESTAMP := SYSTIMESTAMP;
l_status t_project_status;
BEGIN
LOOP
l_status := get_status(p_project_id);
EXIT WHEN l_status.is_completed;
IF EXTRACT(SECOND FROM (SYSTIMESTAMP - l_start))
+ EXTRACT(MINUTE FROM (SYSTIMESTAMP - l_start)) * 60
+ EXTRACT(HOUR FROM (SYSTIMESTAMP - l_start)) * 3600
> p_timeout_sec
THEN
RAISE_APPLICATION_ERROR(-20111,
'Timeout nach ' || p_timeout_sec || 's Projekt ' ||
p_project_id || ' noch im Status ' || l_status.status ||
' (' || l_status.processing_progress || '%).');
END IF;
DBMS_SESSION.SLEEP(p_interval_sec);
END LOOP;
END wait_for_completion;
END DC_BACKEND_PKG;
/
-- =============================================================================
-- Schnelltest (optional, auskommentiert)
-- =============================================================================
/*
-- Health-Check
BEGIN
IF DC_BACKEND_PKG.health_check() THEN
DBMS_OUTPUT.PUT_LINE('dc-backend: UP');
ELSE
DBMS_OUTPUT.PUT_LINE('dc-backend: NICHT ERREICHBAR');
END IF;
END;
/
-- Prüfung starten (Projekt-ID anpassen)
DECLARE
l_status NUMBER;
l_response VARCHAR2(4000);
BEGIN
DC_BACKEND_PKG.start_check(
p_project_id => 1,
p_status_code => l_status,
p_response => l_response
);
DBMS_OUTPUT.PUT_LINE('Status: ' || l_status);
DBMS_OUTPUT.PUT_LINE('Response: ' || l_response);
END;
/
*/

View File

@@ -0,0 +1,535 @@
drop table dc_users cascade constraints;
drop table dc_document_types cascade constraints;
drop table dc_user_roles cascade constraints;
drop table dc_question_catalogs cascade constraints;
drop table dc_question_categories cascade constraints;
drop table dc_questions cascade constraints;
drop table dc_reference_documents cascade constraints;
drop table dc_projects cascade constraints;
drop table dc_project_documents cascade constraints;
drop table dc_results cascade constraints;
-- create tables
create table dc_users (
id number generated by default on null as identity
constraint dc_users_id_pk primary key,
username varchar2(50 char)
constraint users_username_unq unique not null,
email varchar2(100 char)
constraint users_email_unq unique not null,
password_hash varchar2(255 char),
role varchar2(20 char),
is_active number default on null 1,
created_at date default on null sysdate,
updated_at date default on null sysdate,
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
create table dc_document_types (
id number generated by default on null as identity
constraint dc_document_types_id_pk primary key,
name varchar2(100 char) not null,
description clob,
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
create table dc_reference_documents (
id number generated by default on null as identity
constraint dc_reference_documents_id_pk primary key,
uploaded_by number constraint dc_reference_documents_uploaded_by_fk
references dc_users,
document_type_id number constraint dc_reference_documents_doc_type_fk
references dc_document_types,
name varchar2(100 char) not null,
description clob,
file_path varchar2(500 char),
upload_date date default on null sysdate,
mime_type varchar2(100 char),
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_reference_documents_i1 on dc_reference_documents (uploaded_by);
create table dc_user_roles (
id number generated by default on null as identity
constraint dc_user_roles_id_pk primary key,
role_name varchar2(20 char)
constraint user_roles_role_name_unq unique not null,
description clob,
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
create table dc_question_catalogs (
id number generated by default on null as identity
constraint dc_question_catalogs_id_pk primary key,
created_by_user number constraint dc_question_catalogs_created_by_fk
references dc_users,
based_on_reference number constraint dc_question_catalogs_reference_document_fk
references dc_reference_documents,
document_type_id number constraint dc_question_catalogs_doc_type_fk
references dc_document_types,
name varchar2(100 char) not null,
description varchar2(4000 char),
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_question_catalogs_i1 on dc_question_catalogs (created_by);
create table dc_question_categories (
id number generated by default on null as identity
constraint dc_question_categories_id_pk primary key,
catalog_id number constraint dc_question_categories_catalog_id_fk
references dc_question_catalogs,
name varchar2(100 char) not null,
description varchar2(4000 char),
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_question_categories_i1 on dc_question_categories (catalog_id);
create table dc_questions (
id number generated by default on null as identity
constraint dc_questions_id_pk primary key,
category_id number constraint dc_questions_category_id_fk
references dc_question_categories,
question_text clob not null,
evaluation_type varchar2(20 char),
threshold number,
result_handling varchar2(20 char),
example_0_percent clob,
example_100_percent clob,
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_questions_i1 on dc_questions (category_id);
create table dc_projects (
id number generated by default on null as identity
constraint dc_projects_id_pk primary key,
catalog_id number constraint dc_projects_catalog_id_fk
references dc_question_catalogs,
created_by_user number constraint dc_projects_created_by_fk
references dc_users,
name varchar2(100 char) not null,
description clob,
status varchar2(20 char) constraint dc_projects_status_ck
check (status in ('PENDING','IN_PROGRESS','COMPLETED')),
progress number default on null 0,
completed_at date,
notification_email varchar2(100 char),
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_projects_i1 on dc_projects (catalog_id);
create index dc_projects_i2 on dc_projects (created_by);
create table dc_project_documents (
id number generated by default on null as identity
constraint dc_project_documents_id_pk primary key,
project_id number constraint dc_project_documents_project_id_fk
references dc_projects,
document_type_id number constraint dc_project_documents_doc_type_fk
references dc_document_types,
catalog_id number constraint dc_project_documents_catalog_id_fk
references dc_question_catalogs,
original_file blob,
original_text clob,
translated_text clob,
filename varchar2(255 char) not null,
mime_type varchar2(100 char),
uploaded_at date default on null sysdate,
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_project_documents_i1 on dc_project_documents (project_id);
create table dc_results (
id number generated by default on null as identity
constraint dc_results_id_pk primary key,
project_id number constraint dc_results_project_id_fk
references dc_projects,
question_id number constraint dc_results_question_id_fk
references dc_questions,
doc_id number constraint dc_results_doc_id_fk
references dc_project_documents,
answer clob,
score number,
result_type varchar2(20 char) constraint dc_results_result_type_ck
check (result_type in ('OK','UNKLAR','NOK')),
deviation number default on null 0,
warning number default on null 0,
the_comment clob,
evaluated_at date default on null sysdate,
row_version integer not null,
created date not null,
created_by varchar2(255 char) not null,
updated date not null,
updated_by varchar2(255 char) not null
);
-- table index
create index dc_results_i1 on dc_results (project_id);
create index dc_results_i2 on dc_results (question_id);
create index dc_results_i3 on dc_results (doc_id);
-- triggers
create or replace trigger dc_users_biu
before insert or update
on dc_users
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_users_biu;
/
create or replace trigger dc_user_roles_biu
before insert or update
on dc_user_roles
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_user_roles_biu;
/
create or replace trigger dc_document_types_biu
before insert or update
on dc_document_types
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_document_types_biu;
/
create or replace trigger dc_question_catalogs_biu
before insert or update
on dc_question_catalogs
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_question_catalogs_biu;
/
create or replace trigger dc_question_categories_biu
before insert or update
on dc_question_categories
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_question_categories_biu;
/
create or replace trigger dc_questions_biu
before insert or update
on dc_questions
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_questions_biu;
/
create or replace trigger dc_reference_documents_biu
before insert or update
on dc_reference_documents
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_reference_documents_biu;
/
create or replace trigger dc_projects_biu
before insert or update
on dc_projects
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_projects_biu;
/
create or replace trigger dc_project_documents_biu
before insert or update
on dc_project_documents
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_project_documents_biu;
/
create or replace trigger dc_results_biu
before insert or update
on dc_results
for each row
begin
if inserting then
:new.row_version := 1;
elsif updating then
:new.row_version := NVL(:old.row_version, 0) + 1;
end if;
if inserting then
:new.created := sysdate;
:new.created_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := coalesce(sys_context('APEX$SESSION','APP_USER'),user);
end dc_results_biu;
/
-- Generated by Quick SQL 1.2.9 13.4.2026, 17:07:42
/*
users
id number generated by default on null as identity constraint users_id_pk primary key
username vc50 /nn /unique
email vc100 /nn /unique
password_hash vc255
role vc20
is_active num /default 1
created_at date /default sysdate
updated_at date /default sysdate
user_roles
id number generated by default on null as identity constraint user_roles_id_pk primary key
role_name vc20 /nn /unique
description clob
question_catalogs
id number generated by default on null as identity constraint question_catalogs_id_pk primary key
name vc100 /nn
description clob
created_by num /references users
created_at date /default sysdate
updated_at date /default sysdate
question_categories
id number generated by default on null as identity constraint question_categories_id_pk primary key
catalog_id num /references question_catalogs
name vc100 /nn
description clob
questions
id number generated by default on null as identity constraint questions_id_pk primary key
category_id num /references question_categories
question_text clob /nn
evaluation_type vc20
threshold num
result_handling vc20
example_0_percent clob
example_100_percent clob
reference_documents
id number generated by default on null as identity constraint reference_documents_id_pk primary key
name vc100 /nn
description clob
file_path vc500
uploaded_by num /references users
upload_date date /default sysdate
mime_type vc100
projects
id number generated by default on null as identity constraint projects_id_pk primary key
name vc100 /nn
description clob
catalog_id num /references question_catalogs
created_by num /references users
created_at date /default sysdate
status vc20 /check PENDING, IN_PROGRESS, COMPLETED
progress num /default 0
completed_at date
notification_email vc100
project_documents
id number generated by default on null as identity constraint project_documents_id_pk primary key
project_id num /references projects
original_file blob
translated_file blob
filename vc255 /nn
mime_type vc100
uploaded_at date /default sysdate
results
id number generated by default on null as identity constraint results_id_pk primary key
project_id num /references projects
question_id num /references questions
doc_id num /references project_documents
answer clob
score num
result_type vc20 /check OK, UNKLAR, NOK
deviation num /default 0
warning num /default 0
comment clob
evaluated_at date /default sysdate
Non-default options:
# settings = {"apex":"Y","auditcols":"Y","db":"23","drop":"Y","prefix":"Dc","pk":"IDENTITY","rowversion":"Y"}
*/

View File

@@ -0,0 +1,498 @@
DECLARE
-- IDs
v_doc_type_id dc_document_types.id%TYPE;
v_catalog_id dc_question_catalogs.id%TYPE;
v_cat1_id dc_question_categories.id%TYPE;
v_cat2_id dc_question_categories.id%TYPE;
v_cat3_id dc_question_categories.id%TYPE;
v_cat4_id dc_question_categories.id%TYPE;
v_cat5_id dc_question_categories.id%TYPE;
v_cat6_id dc_question_categories.id%TYPE;
BEGIN
-- ============================================================
-- 1. DOKUMENTENTYP
-- ============================================================
INSERT INTO dc_document_types (
name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
'AGB',
'Allgemeine Geschäftsbedingungen vorformulierte Vertragsbedingungen gemäß §305 BGB',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_doc_type_id;
-- ============================================================
-- 2. FRAGENKATALOG
-- ============================================================
INSERT INTO dc_question_catalogs (
created_by_user, based_on_reference, document_type_id,
name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
NULL, NULL, v_doc_type_id,
'AGB-Prüfkatalog (BGB §305310)',
'Prüfung von Allgemeinen Geschäftsbedingungen auf Konformität mit dem deutschen AGB-Recht. '
|| 'Grundlage: §§305310 BGB sowie aktuelle BGH-Rechtsprechung.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_catalog_id;
-- ============================================================
-- 3. KATEGORIEN
-- ============================================================
INSERT INTO dc_question_categories (
catalog_id, name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
v_catalog_id,
'Einbeziehung & Transparenz',
'Prüfung ob die AGB wirksam einbezogen wurden und dem Transparenzgebot (§307 Abs. 1 S. 2 BGB) entsprechen.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_cat1_id;
INSERT INTO dc_question_categories (
catalog_id, name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
v_catalog_id,
'Haftung & Gewährleistung',
'Prüfung von Haftungsbeschränkungen und Gewährleistungsregelungen auf Zulässigkeit nach §309 Nr. 7/8 BGB.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_cat2_id;
INSERT INTO dc_question_categories (
catalog_id, name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
v_catalog_id,
'Zahlungs- & Lieferbedingungen',
'Prüfung von Zahlungsfristen, Verzugszinsen, Lieferfristen und Eigentumsvorbehalt.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_cat3_id;
INSERT INTO dc_question_categories (
catalog_id, name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
v_catalog_id,
'Laufzeit & Kündigung',
'Prüfung von Vertragslaufzeiten, Kündigungsfristen und automatischer Verlängerung gemäß §309 Nr. 9 BGB.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_cat4_id;
INSERT INTO dc_question_categories (
catalog_id, name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
v_catalog_id,
'Datenschutz & Compliance',
'Prüfung der Datenschutzklauseln auf Konformität mit DSGVO und BDSG.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_cat5_id;
INSERT INTO dc_question_categories (
catalog_id, name, description,
row_version, created, created_by, updated, updated_by
) VALUES (
v_catalog_id,
'Verbotene Klauseln §307309 BGB',
'Prüfung auf explizit verbotene oder unangemessen benachteiligende Klauseln.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
)
RETURNING id INTO v_cat6_id;
-- ============================================================
-- 4. FRAGEN
-- ============================================================
------------------------------------------------------------
-- KATEGORIE 1: Einbeziehung & Transparenz
------------------------------------------------------------
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat1_id,
'Wird dem Vertragspartner vor oder bei Vertragsschluss ausdrücklich auf die Geltung der AGB hingewiesen (§305 Abs. 2 Nr. 1 BGB)?',
'SCORE', 50, 'ABWEICHUNG',
'Kein Hinweis auf AGB vorhanden; AGB nur im Footer der Website verlinkt ohne ausdrücklichen Hinweis beim Vertragsschluss.',
'AGB sind ausdrücklich im Vertragsangebot genannt, z. B.: „Es gelten unsere AGB in der jeweils gültigen Fassung, abrufbar unter [URL]."',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat1_id,
'Wird dem Vertragspartner die zumutbare Möglichkeit verschafft, vom Inhalt der AGB Kenntnis zu nehmen (§305 Abs. 2 Nr. 2 BGB)?',
'SCORE', 50, 'ABWEICHUNG',
'AGB nur auf Anfrage erhältlich; kein direkter Link, kein Aushang, keine Übergabe.',
'Vollständiger Text der AGB ist abrufbar unter einem direkt verlinkten URL oder wird als Anhang mitgesendet.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat1_id,
'Sind alle Klauseln klar, verständlich und ohne Fachsprache formuliert (Transparenzgebot §307 Abs. 1 S. 2 BGB)?',
'SCORE', 60, 'ABWEICHUNG',
'Klauseln enthalten unverständliche Fachbegriffe ohne Erläuterung; Satzgefüge sind verschachtelt und mehrdeutig.',
'Klauseln sind in einfacher Sprache formuliert, kurze Sätze, wichtige Begriffe werden erklärt.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat1_id,
'Enthält das Dokument eine Versionsnummer oder ein Datum, das die aktuell gültige Fassung eindeutig kennzeichnet?',
'SCORE', 70, 'HINWEIS',
'Kein Datum, keine Versionsnummer vorhanden.',
'Fußzeile enthält: „Stand: Januar 2025, Version 3.1"',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat1_id,
'Wird geregelt, wie Änderungen der AGB dem Vertragspartner mitgeteilt werden und wann diese wirksam werden?',
'SCORE', 50, 'ABWEICHUNG',
'Keine Regelung zu AGB-Änderungen; stillschweigende Zustimmung durch Weiterbenutzung des Dienstes wird unterstellt.',
'Änderungen werden per E-Mail angekündigt mit einer Einspruchsfrist von mindestens 6 Wochen; Widerspruchsrecht ist ausdrücklich genannt.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
------------------------------------------------------------
-- KATEGORIE 2: Haftung & Gewährleistung
------------------------------------------------------------
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat2_id,
'Wird die Haftung für Vorsatz und grobe Fahrlässigkeit vollständig ausgeschlossen (§309 Nr. 7a BGB unzulässig)?',
'SCORE', 50, 'ABWEICHUNG',
'„Jegliche Haftung, einschließlich Vorsatz und grober Fahrlässigkeit, wird hiermit ausgeschlossen."',
'Haftungsausschluss gilt nur für leichte Fahrlässigkeit; Vorsatz und grobe Fahrlässigkeit bleiben ausdrücklich unberührt.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat2_id,
'Wird die Haftung für Körper- und Gesundheitsschäden ausgeschlossen (§309 Nr. 7b BGB unzulässig)?',
'SCORE', 50, 'ABWEICHUNG',
'„Eine Haftung für Personen- und Sachschäden jeglicher Art ist ausgeschlossen."',
'Haftung für Personenschäden ist explizit ausgenommen: „Die Haftungsbeschränkung gilt nicht für Schäden an Leben, Körper oder Gesundheit."',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat2_id,
'Werden gesetzliche Gewährleistungsrechte des Käufers vollständig ausgeschlossen (§309 Nr. 8b BGB unzulässig bei Neuwaren)?',
'SCORE', 50, 'ABWEICHUNG',
'„Gewährleistungsansprüche sind vollständig ausgeschlossen."',
'Gewährleistungsfrist wird auf das gesetzliche Minimum (2 Jahre bei Neuwaren) gesetzt; vollständiger Ausschluss ist nicht vorhanden.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat2_id,
'Wird die Haftung für die Verletzung wesentlicher Vertragspflichten (Kardinalpflichten) unangemessen eingeschränkt?',
'SCORE', 50, 'ABWEICHUNG',
'„Jegliche Haftung des Anbieters ist auf 50 € begrenzt, unabhängig von der Art der Pflichtverletzung."',
'Haftungsbegrenzung gilt nicht für die Verletzung wesentlicher Vertragspflichten; bei deren Verletzung haftet der Anbieter auf den typischerweise vorhersehbaren Schaden.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
------------------------------------------------------------
-- KATEGORIE 3: Zahlungs- & Lieferbedingungen
------------------------------------------------------------
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat3_id,
'Sind die vereinbarten Zahlungsfristen klar und eindeutig definiert?',
'SCORE', 60, 'HINWEIS',
'Keine Angabe zur Zahlungsfrist; Fälligkeit unklar.',
'Zahlung ist innerhalb von 14 Tagen nach Rechnungsstellung ohne Abzug fällig.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat3_id,
'Werden unangemessen hohe Verzugszinsen oder Vertragsstrafen bei Zahlungsverzug festgelegt (§309 Nr. 6 BGB)?',
'SCORE', 50, 'ABWEICHUNG',
'„Bei Zahlungsverzug werden Zinsen in Höhe von 15 % p. a. sowie eine Bearbeitungsgebühr von 50 € pro Mahnung berechnet."',
'Verzugszinsen entsprechen dem gesetzlichen Zinssatz (§288 BGB); keine unverhältnismäßigen Mahngebühren.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat3_id,
'Sind Lieferfristen verbindlich oder werden nur unverbindliche Richtwerte ohne Konsequenzen bei Überschreitung angegeben?',
'SCORE', 50, 'HINWEIS',
'„Lieferzeiten sind unverbindlich; eine Überschreitung berechtigt nicht zur Stornierung."',
'Lieferfristen sind verbindlich; bei Überschreitung um mehr als 14 Tage hat der Käufer ein Rücktrittsrecht.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat3_id,
'Wird ein erweiterter Eigentumsvorbehalt vereinbart, der über die gelieferte Ware hinausgeht (z. B. Kontokorrentvorbehalt)?',
'SCORE', 50, 'HINWEIS',
'„Das Eigentum verbleibt beim Verkäufer bis zur vollständigen Bezahlung aller gegenwärtigen und zukünftigen Forderungen aus der Geschäftsbeziehung."',
'Einfacher Eigentumsvorbehalt: Eigentum geht nach vollständiger Bezahlung des jeweiligen Kaufpreises über.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
------------------------------------------------------------
-- KATEGORIE 4: Laufzeit & Kündigung
------------------------------------------------------------
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat4_id,
'Beträgt die Erstlaufzeit bei Dauerschuldverhältnissen mehr als zwei Jahre (§309 Nr. 9a BGB unzulässig)?',
'SCORE', 50, 'ABWEICHUNG',
'„Der Vertrag hat eine Mindestlaufzeit von 36 Monaten."',
'Mindestlaufzeit beträgt maximal 24 Monate.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat4_id,
'Verlängert sich der Vertrag bei Nichtkündigung um mehr als ein Jahr automatisch (§309 Nr. 9b BGB unzulässig)?',
'SCORE', 50, 'ABWEICHUNG',
'„Wird der Vertrag nicht 3 Monate vor Ablauf gekündigt, verlängert er sich automatisch um 24 Monate."',
'Automatische Verlängerung beträgt maximal 12 Monate; Kündigungsfrist höchstens 3 Monate.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat4_id,
'Beträgt die Kündigungsfrist für den Vertragspartner mehr als drei Monate (§309 Nr. 9c BGB unzulässig)?',
'SCORE', 50, 'ABWEICHUNG',
'„Der Vertrag kann mit einer Frist von 6 Monaten zum Vertragsende gekündigt werden."',
'Kündigungsfrist beträgt maximal 3 Monate zum Ende der Vertragslaufzeit.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat4_id,
'Ist das Recht zur außerordentlichen Kündigung aus wichtigem Grund explizit oder durch Generalausschluss eingeschränkt?',
'SCORE', 50, 'ABWEICHUNG',
'„Eine Kündigung ist ausschließlich zum regulären Vertragsende möglich."',
'Das Recht zur fristlosen Kündigung aus wichtigem Grund gemäß §314 BGB bleibt ausdrücklich unberührt.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
------------------------------------------------------------
-- KATEGORIE 5: Datenschutz & Compliance
------------------------------------------------------------
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat5_id,
'Wird eine Rechtsgrundlage für die Verarbeitung personenbezogener Daten gemäß Art. 6 DSGVO genannt?',
'SCORE', 70, 'ABWEICHUNG',
'Keine Angabe zur Rechtsgrundlage der Datenverarbeitung in den AGB.',
'Datenverarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung); weitere Grundlagen sind aufgeführt.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat5_id,
'Wird der Verantwortliche gemäß Art. 4 Nr. 7 DSGVO mit vollständigen Kontaktdaten genannt?',
'SCORE', 70, 'ABWEICHUNG',
'Kein Verantwortlicher genannt; nur allgemeiner Firmenname ohne Adresse.',
'Name, Anschrift, E-Mail und ggf. Datenschutzbeauftragter sind vollständig angegeben.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat5_id,
'Werden die Betroffenenrechte (Auskunft, Löschung, Berichtigung, Widerspruch) gemäß Art. 1521 DSGVO kommuniziert?',
'SCORE', 60, 'ABWEICHUNG',
'Kein Hinweis auf Betroffenenrechte.',
'Alle Betroffenenrechte sind aufgeführt mit Hinweis auf den Kontaktweg zur Ausübung.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat5_id,
'Wird eine Weitergabe von Daten an Dritte oder eine Übermittlung in Drittländer außerhalb der EU geregelt?',
'SCORE', 60, 'HINWEIS',
'Keine Regelung zur Datenweitergabe; Drittlandübermittlung unerwähnt.',
'Datenweitergabe an Dritte ist aufgelistet; Drittlandübermittlung erfolgt nur auf Basis von Standardvertragsklauseln gemäß Art. 46 DSGVO.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
------------------------------------------------------------
-- KATEGORIE 6: Verbotene Klauseln §307309 BGB
------------------------------------------------------------
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat6_id,
'Enthält die AGB eine Klausel, die dem Verwender einseitig das Recht einräumt, vereinbarte Leistungen zu ändern (§308 Nr. 4 BGB)?',
'SCORE', 50, 'ABWEICHUNG',
'„Wir behalten uns vor, unsere Leistungen jederzeit und ohne Ankündigung zu ändern oder einzustellen."',
'Leistungsänderungen sind nur aus sachlich gerechtfertigtem Grund zulässig; dem Kunden steht ein Sonderkündigungsrecht zu.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat6_id,
'Wird dem Vertragspartner das Recht zur Aufrechnung mit unbestrittenen oder rechtskräftig festgestellten Forderungen entzogen (§309 Nr. 3 BGB)?',
'SCORE', 50, 'ABWEICHUNG',
'„Eine Aufrechnung gegen Forderungen des Anbieters ist ausgeschlossen."',
'Aufrechnung ist zulässig mit unbestrittenen oder rechtskräftig festgestellten Gegenforderungen.',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat6_id,
'Wird der Gerichtsstand ausschließlich auf den Sitz des Verwenders festgelegt, obwohl der Vertragspartner Verbraucher ist (§29c ZPO)?',
'SCORE', 50, 'ABWEICHUNG',
'„Ausschließlicher Gerichtsstand für alle Streitigkeiten ist München."',
'Gerichtsstandvereinbarung gilt nur für Kaufleute; bei Verbrauchern gilt der gesetzliche Gerichtsstand (Wohnort des Verbrauchers).',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat6_id,
'Wird die Anwendung deutschen Rechts ausdrücklich geregelt, und werden verbraucherschützende Vorschriften des Herkunftslandes des Kunden ausgehebelt?',
'SCORE', 50, 'HINWEIS',
'„Es gilt ausschließlich deutsches Recht unter vollständigem Ausschluss des UN-Kaufrechts sowie aller ausländischen Schutzvorschriften."',
'Rechtswahl gilt für Kaufleute; Verbraucher genießen den Schutz der zwingenden Vorschriften ihres Heimatlandes (Art. 6 Rom-I-VO).',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
INSERT INTO dc_questions (
category_id, question_text, evaluation_type, threshold, result_handling,
example_0_percent, example_100_percent,
row_version, created, created_by, updated, updated_by
) VALUES (
v_cat6_id,
'Enthält die AGB eine salvatorische Klausel für den Fall der Unwirksamkeit einzelner Bestimmungen?',
'SCORE', 70, 'HINWEIS',
'Keine salvatorische Klausel vorhanden; Unwirksamkeit einzelner Klauseln könnte gesamte AGB gefährden.',
'„Sollten einzelne Bestimmungen dieser AGB unwirksam sein, bleibt die Wirksamkeit der übrigen Bestimmungen unberührt."',
1, SYSDATE, 'SYSTEM', SYSDATE, 'SYSTEM'
);
COMMIT;
DBMS_OUTPUT.PUT_LINE('Erfolgreich angelegt:');
DBMS_OUTPUT.PUT_LINE(' Dokumententyp-ID : ' || v_doc_type_id);
DBMS_OUTPUT.PUT_LINE(' Katalog-ID : ' || v_catalog_id);
DBMS_OUTPUT.PUT_LINE(' Kategorie 1 ID : ' || v_cat1_id || ' (Einbeziehung & Transparenz)');
DBMS_OUTPUT.PUT_LINE(' Kategorie 2 ID : ' || v_cat2_id || ' (Haftung & Gewährleistung)');
DBMS_OUTPUT.PUT_LINE(' Kategorie 3 ID : ' || v_cat3_id || ' (Zahlungs- & Lieferbedingungen)');
DBMS_OUTPUT.PUT_LINE(' Kategorie 4 ID : ' || v_cat4_id || ' (Laufzeit & Kündigung)');
DBMS_OUTPUT.PUT_LINE(' Kategorie 5 ID : ' || v_cat5_id || ' (Datenschutz & Compliance)');
DBMS_OUTPUT.PUT_LINE(' Kategorie 6 ID : ' || v_cat6_id || ' (Verbotene Klauseln §307309 BGB)');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('FEHLER: ' || SQLERRM);
RAISE;
END;
/

View File

@@ -0,0 +1,737 @@
-- =============================================================================
-- ORDS REST Services: Dokumenten-Check
-- Modul: frigosped.dc
-- Base-Path: /api/dc/
-- =============================================================================
-- Voraussetzungen:
-- - Oracle Database 23c mit installiertem ORDS (>= 23.x)
-- - Script wird als Schema-Owner ausgeführt (hat ORDS_METADATA-Rolle)
-- - Datenbankschema bereits angelegt via "Datenmodell DocumentCheck.sql"
--
-- Deployment:
-- sqlplus user/pass@db @"Scripts/ORDS REST Services.sql"
--
-- Das Skript ist idempotent: bestehende Modul-Definition wird gelöscht und
-- neu erstellt. Bestehende Daten bleiben unberührt.
--
-- Endpunkt-Übersicht:
-- 1. GET /api/dc/catalogs/ Alle Fragenkataloge
-- 2. GET /api/dc/catalogs/:id/questions/ Fragen eines Katalogs (flach)
-- 3. GET /api/dc/projects/:id Projektdetail
-- 4. POST /api/dc/projects/:id/start → IN_PROGRESS
-- 5. PUT /api/dc/projects/:id/progress Fortschritt aktualisieren
-- 6. POST /api/dc/projects/:id/complete → COMPLETED
-- 7. GET /api/dc/projects/:id/documents/ Dokument-Liste (kein BLOB)
-- 8. GET /api/dc/documents/:id/file BLOB-Download
-- 9. PUT /api/dc/documents/:id/texts OCR-Text + Übersetzung schreiben
-- 10. POST /api/dc/projects/:id/results Prüfergebnis einfügen
-- 11. DELETE /api/dc/projects/:id/results Alle Ergebnisse löschen
-- =============================================================================
-- =============================================================================
-- BLOCK 1: Modul, Templates und Handler
-- =============================================================================
BEGIN
-- -------------------------------------------------------------------------
-- Idempotenz: Modul löschen falls vorhanden
-- -------------------------------------------------------------------------
BEGIN
ORDS.DELETE_MODULE(p_module_name => 'frigosped.dc');
EXCEPTION
WHEN OTHERS THEN NULL;
END;
-- -------------------------------------------------------------------------
-- Modul definieren
-- -------------------------------------------------------------------------
ORDS.DEFINE_MODULE(
p_module_name => 'frigosped.dc',
p_base_path => '/api/dc/',
p_items_per_page => 0, -- Pagination deaktiviert (Quarkus steuert selbst)
p_status => 'PUBLISHED',
p_comments => 'Dokumenten-Check Backend-API fuer den Quarkus-Verarbeitungsserver'
);
-- ===========================================================================
-- TEMPLATE 1: catalogs/
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'catalogs/',
p_priority => 0,
p_etag_type => 'HASH',
p_comments => 'Alle Fragenkataloge mit Dokumenttyp-Name'
);
-- 1. GET /api/dc/catalogs/
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'catalogs/',
p_method => 'GET',
p_source_type => ORDS.SOURCE_TYPE_COLLECTION_FEED,
p_items_per_page => 0,
p_comments => 'Gibt alle Kataloge mit Dokumenttyp-Name zurueck',
p_source => q'[
SELECT
c.id,
c.name,
c.description,
c.document_type_id,
dt.name AS document_type_name,
c.row_version,
c.created,
c.updated
FROM dc_question_catalogs c
JOIN dc_document_types dt ON dt.id = c.document_type_id
ORDER BY c.name
]'
);
-- ===========================================================================
-- TEMPLATE 2: catalogs/:catalog_id/questions/
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'catalogs/:catalog_id/questions/',
p_priority => 0,
p_etag_type => 'HASH',
p_comments => 'Alle Fragen eines Katalogs (flach mit Kategorie-Info)'
);
-- 2. GET /api/dc/catalogs/:catalog_id/questions/
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'catalogs/:catalog_id/questions/',
p_method => 'GET',
p_source_type => ORDS.SOURCE_TYPE_COLLECTION_FEED,
p_items_per_page => 0,
p_comments => 'Flacher Join: Kategorie + Frage-Spalten, nach Kategorie/ID sortiert',
p_source => q'[
SELECT
q.id AS question_id,
cat.id AS category_id,
cat.name AS category_name,
cat.description AS category_description,
q.question_text,
q.evaluation_type,
q.threshold,
q.result_handling,
q.example_0_percent,
q.example_100_percent,
q.row_version
FROM dc_questions q
JOIN dc_question_categories cat ON cat.id = q.category_id
WHERE cat.catalog_id = :catalog_id
ORDER BY cat.name, q.id
]'
);
-- ===========================================================================
-- TEMPLATE 3: projects/:project_id (kein Trailing-Slash = Einzel-Ressource)
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id',
p_priority => 0,
p_etag_type => 'HASH',
p_comments => 'Einzelnes Projekt'
);
-- 3. GET /api/dc/projects/:project_id
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id',
p_method => 'GET',
p_source_type => ORDS.SOURCE_TYPE_COLLECTION_ITEM,
p_items_per_page => 1,
p_comments => 'Projektdetail als einzelnes JSON-Objekt; 404 wenn nicht gefunden',
p_source => q'[
SELECT
p.id,
p.name,
p.description,
p.catalog_id,
p.created_by_user,
p.status,
p.progress,
p.completed_at,
p.notification_email,
p.row_version,
p.created,
p.updated
FROM dc_projects p
WHERE p.id = :project_id
]'
);
-- ===========================================================================
-- TEMPLATE 4: projects/:project_id/start
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/start',
p_priority => 0,
p_comments => 'Projekt-Status auf IN_PROGRESS setzen'
);
-- 4. POST /api/dc/projects/:project_id/start
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/start',
p_method => 'POST',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => '200 OK; 409 wenn Status nicht PENDING; 404 wenn Projekt fehlt',
p_source => q'[
DECLARE
v_status dc_projects.status%TYPE;
BEGIN
SELECT status
INTO v_status
FROM dc_projects
WHERE id = :project_id
FOR UPDATE NOWAIT;
IF v_status != 'PENDING' THEN
:status_code := 409;
HTP.P('{"error":"Projekt ist nicht im Status PENDING",'
|| '"current_status":"' || v_status || '"}');
RETURN;
END IF;
UPDATE dc_projects
SET status = 'IN_PROGRESS',
progress = 0
WHERE id = :project_id;
:status_code := 200;
HTP.P('{"project_id":' || :project_id
|| ',"status":"IN_PROGRESS"}');
EXCEPTION
WHEN NO_DATA_FOUND THEN
:status_code := 404;
HTP.P('{"error":"Projekt nicht gefunden","project_id":' || :project_id || '}');
WHEN OTHERS THEN
ROLLBACK;
:status_code := 500;
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
END;
]'
);
-- ===========================================================================
-- TEMPLATE 5: projects/:project_id/progress
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/progress',
p_priority => 0,
p_comments => 'Verarbeitungsfortschritt aktualisieren'
);
-- 5. PUT /api/dc/projects/:project_id/progress
-- Body: {"progress": 45}
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/progress',
p_method => 'PUT',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => 'Setzt progress (0-100); 400 bei ungueltigem Wert',
p_source => q'[
DECLARE
v_progress NUMBER;
v_body CLOB;
BEGIN
v_body := :body_text;
v_progress := TO_NUMBER(JSON_VALUE(v_body, '$.progress'));
IF v_progress IS NULL OR v_progress < 0 OR v_progress > 100 THEN
:status_code := 400;
HTP.P('{"error":"progress muss eine Zahl zwischen 0 und 100 sein"}');
RETURN;
END IF;
UPDATE dc_projects
SET progress = v_progress
WHERE id = :project_id;
IF SQL%ROWCOUNT = 0 THEN
:status_code := 404;
HTP.P('{"error":"Projekt nicht gefunden","project_id":' || :project_id || '}');
RETURN;
END IF;
:status_code := 200;
HTP.P('{"project_id":' || :project_id
|| ',"progress":' || v_progress || '}');
EXCEPTION
WHEN VALUE_ERROR THEN
:status_code := 400;
HTP.P('{"error":"progress muss eine gueltige Zahl sein"}');
WHEN OTHERS THEN
ROLLBACK;
:status_code := 500;
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
END;
]'
);
-- ===========================================================================
-- TEMPLATE 6: projects/:project_id/complete
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/complete',
p_priority => 0,
p_comments => 'Projekt auf COMPLETED setzen'
);
-- 6. POST /api/dc/projects/:project_id/complete
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, completed_at=SYSDATE, progress=100',
p_source => q'[
DECLARE
v_status dc_projects.status%TYPE;
v_now DATE := SYSDATE;
BEGIN
SELECT status
INTO v_status
FROM dc_projects
WHERE id = :project_id
FOR UPDATE NOWAIT;
IF v_status = 'COMPLETED' THEN
:status_code := 409;
HTP.P('{"error":"Projekt ist bereits COMPLETED","project_id":' || :project_id || '}');
RETURN;
END IF;
UPDATE dc_projects
SET status = 'COMPLETED',
completed_at = v_now,
progress = 100
WHERE id = :project_id;
:status_code := 200;
HTP.P('{"project_id":' || :project_id
|| ',"status":"COMPLETED"'
|| ',"completed_at":"' || TO_CHAR(v_now, 'YYYY-MM-DD"T"HH24:MI:SS') || '"'
|| '}');
EXCEPTION
WHEN NO_DATA_FOUND THEN
:status_code := 404;
HTP.P('{"error":"Projekt nicht gefunden","project_id":' || :project_id || '}');
WHEN OTHERS THEN
ROLLBACK;
:status_code := 500;
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
END;
]'
);
-- ===========================================================================
-- TEMPLATE 7: projects/:project_id/documents/
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/documents/',
p_priority => 0,
p_etag_type => 'HASH',
p_comments => 'Dokument-Liste eines Projekts (ohne BLOB-Payload)'
);
-- 7. GET /api/dc/projects/:project_id/documents/
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/documents/',
p_method => 'GET',
p_source_type => ORDS.SOURCE_TYPE_COLLECTION_FEED,
p_items_per_page => 0,
p_comments => 'Metadaten aller Dokumente; has_original_text/has_translated_text als 0/1',
p_source => q'[
SELECT
d.id,
d.filename,
d.mime_type,
d.document_type_id,
d.catalog_id,
d.uploaded_at,
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,
d.created,
d.updated
FROM dc_project_documents d
WHERE d.project_id = :project_id
ORDER BY d.uploaded_at
]'
);
-- ===========================================================================
-- TEMPLATE 8: documents/:doc_id/file
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'documents/:doc_id/file',
p_priority => 0,
p_comments => 'BLOB-Download: original_file mit korrektem Content-Type'
);
-- 8. GET /api/dc/documents/:doc_id/file
-- SOURCE_TYPE_MEDIA: Pflicht-Aliase: content, content_type, filename
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'documents/:doc_id/file',
p_method => 'GET',
p_source_type => ORDS.SOURCE_TYPE_MEDIA,
p_comments => 'Streamt original_file BLOB; ORDS setzt Content-Type aus mime_type-Spalte',
p_source => q'[
SELECT
d.original_file AS content,
d.mime_type AS content_type,
d.filename AS filename,
d.updated AS last_modified
FROM dc_project_documents d
WHERE d.id = :doc_id
]'
);
-- ===========================================================================
-- TEMPLATE 9: documents/:doc_id/texts
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'documents/:doc_id/texts',
p_priority => 0,
p_comments => 'OCR-Ergebnis und Uebersetzung zurueckschreiben'
);
-- 9. PUT /api/dc/documents/:doc_id/texts
-- Body: {"original_text": "## ...", "translated_text": "## ..."}
-- Beide Felder sind optional: NULL-Wert ueberschreibt nicht (nur wenn im Body vorhanden)
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'documents/:doc_id/texts',
p_method => 'PUT',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => 'Schreibt original_text und/oder translated_text als CLOB',
p_source => q'[
DECLARE
v_body CLOB;
v_original_text CLOB;
v_translated CLOB;
v_rows INTEGER;
BEGIN
v_body := :body_text;
-- RETURNING CLOB erlaubt Werte groesser als 32767 Bytes (wichtig fuer OCR-Output)
v_original_text := JSON_VALUE(v_body, '$.original_text' RETURNING CLOB);
v_translated := JSON_VALUE(v_body, '$.translated_text' RETURNING CLOB);
UPDATE dc_project_documents
SET original_text = CASE
WHEN JSON_EXISTS(v_body, '$.original_text')
THEN v_original_text
ELSE original_text
END,
translated_text = CASE
WHEN JSON_EXISTS(v_body, '$.translated_text')
THEN v_translated
ELSE translated_text
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;
]'
);
-- ===========================================================================
-- TEMPLATE 10+11: projects/:project_id/results
-- (POST = Einfuegen, DELETE = Alle loeschen)
-- ===========================================================================
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/results',
p_priority => 0,
p_comments => 'Prüfergebnisse: POST einfuegen, DELETE alle loeschen'
);
-- 10. POST /api/dc/projects/:project_id/results
-- Body: {
-- "question_id": 5,
-- "doc_id": 101,
-- "answer": "Ja, §305 BGB ist erfuellt.",
-- "score": 85,
-- "result_type": "OK",
-- "deviation": 0,
-- "warning": 0,
-- "the_comment": "Klare Einbeziehungsklausel vorhanden"
-- }
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/results',
p_method => 'POST',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => '201 Created mit Location-Header; 400 bei fehlendem question_id/doc_id/result_type',
p_source => q'[
DECLARE
v_body CLOB;
v_question_id NUMBER;
v_doc_id NUMBER;
v_answer CLOB;
v_score NUMBER;
v_result_type VARCHAR2(20);
v_deviation NUMBER;
v_warning NUMBER;
v_comment CLOB;
v_new_id NUMBER;
BEGIN
v_body := :body_text;
v_question_id := TO_NUMBER(JSON_VALUE(v_body, '$.question_id'));
v_doc_id := TO_NUMBER(JSON_VALUE(v_body, '$.doc_id'));
v_answer := JSON_VALUE(v_body, '$.answer' RETURNING CLOB);
v_score := TO_NUMBER(JSON_VALUE(v_body, '$.score'));
v_result_type := JSON_VALUE(v_body, '$.result_type');
v_deviation := NVL(TO_NUMBER(JSON_VALUE(v_body, '$.deviation')), 0);
v_warning := NVL(TO_NUMBER(JSON_VALUE(v_body, '$.warning')), 0);
v_comment := JSON_VALUE(v_body, '$.the_comment' RETURNING CLOB);
-- Pflichtfelder prüfen
IF v_question_id IS NULL OR v_doc_id IS NULL THEN
:status_code := 400;
HTP.P('{"error":"question_id und doc_id sind Pflichtfelder"}');
RETURN;
END IF;
IF v_result_type NOT IN ('OK', 'UNKLAR', 'NOK') THEN
:status_code := 400;
HTP.P('{"error":"result_type muss OK, UNKLAR oder NOK sein",'
|| '"received":"' || NVL(v_result_type, 'NULL') || '"}');
RETURN;
END IF;
INSERT INTO dc_results (
project_id,
question_id,
doc_id,
answer,
score,
result_type,
deviation,
warning,
the_comment,
evaluated_at
) VALUES (
:project_id,
v_question_id,
v_doc_id,
v_answer,
v_score,
v_result_type,
v_deviation,
v_warning,
v_comment,
SYSDATE
)
RETURNING id INTO v_new_id;
-- Location-Header gemaess REST-Konvention
ORDS.SET_RESPONSE_HEADER(
'Location',
'/api/dc/projects/' || :project_id || '/results/' || v_new_id
);
:status_code := 201;
HTP.P('{"id":' || v_new_id
|| ',"project_id":' || :project_id
|| ',"question_id":' || v_question_id
|| ',"doc_id":' || v_doc_id
|| ',"result_type":"' || v_result_type || '"'
|| ',"score":' || NVL(TO_CHAR(v_score), 'null')
|| '}');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
:status_code := 500;
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
END;
]'
);
-- 11. DELETE /api/dc/projects/:project_id/results
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'projects/:project_id/results',
p_method => 'DELETE',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => 'Loescht alle Ergebnisse eines Projekts (fuer Re-Processing)',
p_source => q'[
DECLARE
v_deleted INTEGER;
BEGIN
DELETE FROM dc_results
WHERE project_id = :project_id;
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;
/
-- =============================================================================
-- BLOCK 2: Auth / Privilege (fuer Produktion)
-- =============================================================================
-- In der Entwicklung kann dieser Block auskommentiert bleiben das Modul
-- ist dann ohne Auth-Pruefung erreichbar (STATUS = PUBLISHED genuegt).
--
-- Fuer Produktion einkommentieren und danach den OAuth2-Client anlegen:
--
-- SELECT client_id, client_secret
-- FROM user_ords_clients
-- WHERE name = 'quarkus_dc_backend';
--
-- Den client_id/client_secret in die Quarkus application.properties eintragen.
-- =============================================================================
/*
BEGIN
-- Rolle anlegen (falls noch nicht vorhanden)
BEGIN
ORDS.CREATE_ROLE(p_role_name => 'dc_api_user');
EXCEPTION
WHEN OTHERS THEN NULL;
END;
-- Privilege loeschen falls vorhanden
BEGIN
ORDS.DROP_PRIVILEGE(p_name => 'dc.api.privilege');
EXCEPTION
WHEN OTHERS THEN NULL;
END;
-- Privilege definieren: mappt Rolle auf alle /api/dc/-Endpunkte
ORDS.DEFINE_PRIVILEGE(
p_privilege_name => 'dc.api.privilege',
p_roles => ORDS_TYPES.T_ORDS_VARCHARS('dc_api_user'),
p_patterns => ORDS_TYPES.T_ORDS_VARCHARS('/api/dc/*'),
p_module_name => 'frigosped.dc',
p_label => 'Dokumenten-Check API',
p_description => 'Zugriff auf alle /api/dc/-Endpunkte',
p_comments => 'Wird dem OAuth2-Client quarkus_dc_backend zugewiesen'
);
COMMIT;
END;
/
-- OAuth2-Client fuer den Quarkus-Server (einmalig ausfuehren)
BEGIN
OAUTH.CREATE_CLIENT(
p_name => 'quarkus_dc_backend',
p_grant_type => 'CLIENT_CREDENTIALS',
p_owner => 'Frigosped',
p_description => 'Quarkus-Verarbeitungsserver Service-Account',
p_redirect_uri => NULL,
p_support_email => 'admin@frigosped.de',
p_privilege_names => 'dc.api.privilege'
);
COMMIT;
END;
/
*/
-- =============================================================================
-- Test-Aufrufe (curl)
-- =============================================================================
-- Ersetze <BASE> und <SCHEMA> mit deinen ORDS-Werten, z.B.:
-- BASE="https://apex.example.com/ords/FRIGOSPED_APP"
--
-- Ohne Auth (Entwicklung):
--
-- # 1. Alle Kataloge
-- curl -sS "$BASE/api/dc/catalogs/" | jq .
--
-- # 2. Fragen fuer Katalog 1
-- curl -sS "$BASE/api/dc/catalogs/1/questions/" | jq .
--
-- # 3. Projektdetail
-- curl -sS "$BASE/api/dc/projects/42" | jq .
--
-- # 4. Projekt starten
-- curl -sS -X POST "$BASE/api/dc/projects/42/start" | jq .
--
-- # 5. Fortschritt setzen
-- curl -sS -X PUT -H "Content-Type: application/json" \
-- -d '{"progress":35}' "$BASE/api/dc/projects/42/progress" | jq .
--
-- # 6. Projekt abschliessen
-- curl -sS -X POST "$BASE/api/dc/projects/42/complete" | jq .
--
-- # 7. Dokumente auflisten
-- curl -sS "$BASE/api/dc/projects/42/documents/" | jq .
--
-- # 8. Datei herunterladen
-- curl -sS -o original.pdf "$BASE/api/dc/documents/101/file"
--
-- # 9. OCR-Text + Uebersetzung zurueckschreiben
-- curl -sS -X PUT -H "Content-Type: application/json" \
-- -d '{"original_text":"## Vertrag\n\nAbsatz 1...",
-- "translated_text":"## Vertrag (DE)\n\nAbsatz 1..."}' \
-- "$BASE/api/dc/documents/101/texts" | jq .
--
-- # 10. Ergebnis einfuegen
-- curl -sS -X POST -H "Content-Type: application/json" \
-- -d '{"question_id":5,"doc_id":101,"answer":"Ja, §305 erfuellt.",
-- "score":85,"result_type":"OK","deviation":0,"warning":0,
-- "the_comment":"Klare Einbeziehungsklausel vorhanden"}' \
-- "$BASE/api/dc/projects/42/results" | jq .
--
-- # 11. Alle Ergebnisse loeschen (Re-Processing)
-- curl -sS -X DELETE "$BASE/api/dc/projects/42/results" | jq .
-- =============================================================================