Files
Dokumenten-Check/Scripts/ORDS-ai-costs-Endpunkt.sql

170 lines
5.5 KiB
MySQL
Raw Permalink Normal View History

2026-06-10 14:21:59 +02:00
-- =============================================================================
-- ORDS-Endpunkte: KI-Kostenverfolgung (ai-costs)
-- Ergänzt das Modul frigosped.dc um Template 17 (POST + GET).
--
-- Voraussetzung: dc_ai_cost_log-Tabelle und DC_COSTS_PKG bereits vorhanden
-- sqlplus user/pass@db @"Scripts/KI-Kostenverfolgung.sql"
--
-- Deployment:
-- sqlplus user/pass@db @"Scripts/ORDS-ai-costs-Endpunkt.sql"
--
-- Idempotent: bestehende Handler werden zuerst gelöscht.
-- =============================================================================
SET DEFINE OFF
BEGIN
-- Idempotenz: Handler entfernen falls vorhanden
BEGIN
ORDS.DELETE_HANDLER(
p_module_name => 'frigosped.dc',
p_uri_template => 'ai-costs',
p_method => 'POST'
);
EXCEPTION WHEN OTHERS THEN NULL;
END;
BEGIN
ORDS.DELETE_HANDLER(
p_module_name => 'frigosped.dc',
p_uri_template => 'ai-costs',
p_method => 'GET'
);
EXCEPTION WHEN OTHERS THEN NULL;
END;
BEGIN
ORDS.DELETE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_uri_template => 'ai-costs'
);
EXCEPTION WHEN OTHERS THEN NULL;
END;
-- Template anlegen
ORDS.DEFINE_TEMPLATE(
p_module_name => 'frigosped.dc',
p_pattern => 'ai-costs',
p_priority => 0,
p_comments => 'KI-Kostenprotokoll: POST speichert Aufruf, GET liefert Auswertung'
);
-- POST /api/dc/ai-costs
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'ai-costs',
p_method => 'POST',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => '201 Created mit neuer cost_log id',
p_source => '
DECLARE
v_body CLOB;
v_new_id NUMBER;
BEGIN
v_body := :body_text;
INSERT INTO dc_ai_cost_log (
provider, model_name, operation,
project_id, catalog_id,
prompt_tokens, completion_tokens, total_tokens,
cost_eur
) VALUES (
JSON_VALUE(v_body, ''$.provider''),
JSON_VALUE(v_body, ''$.model_name''),
JSON_VALUE(v_body, ''$.operation''),
TO_NUMBER(JSON_VALUE(v_body, ''$.project_id'')),
TO_NUMBER(JSON_VALUE(v_body, ''$.catalog_id'')),
NVL(TO_NUMBER(JSON_VALUE(v_body, ''$.prompt_tokens'')), 0),
NVL(TO_NUMBER(JSON_VALUE(v_body, ''$.completion_tokens'')), 0),
NVL(TO_NUMBER(JSON_VALUE(v_body, ''$.total_tokens'')), 0),
NVL(TO_NUMBER(JSON_VALUE(v_body, ''$.cost_eur'')), 0)
)
RETURNING id INTO v_new_id;
:status_code := 201;
HTP.P(''{"id":'' || v_new_id || ''}'');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
:status_code := 500;
HTP.P(''{"error":"'' || REPLACE(SQLERRM, ''"'', '''''''') || ''"}'' );
END;
'
);
-- GET /api/dc/ai-costs
ORDS.DEFINE_HANDLER(
p_module_name => 'frigosped.dc',
p_pattern => 'ai-costs',
p_method => 'GET',
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
p_comments => 'Kostenauswertung: total, current_month, monatliche Aufschluesselung via DC_COSTS_PKG',
p_source => '
DECLARE
v_total_eur NUMBER;
v_total_tokens NUMBER;
v_total_calls NUMBER;
v_month_eur NUMBER;
v_month_tokens NUMBER;
v_month_calls NUMBER;
v_monthly_json CLOB;
BEGIN
DC_COSTS_PKG.get_summary(
v_total_eur, v_total_tokens, v_total_calls,
v_month_eur, v_month_tokens, v_month_calls
);
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
''month'' VALUE month,
''cost_eur'' VALUE ROUND(cost_eur, 6),
''prompt_tokens'' VALUE prompt_tokens,
''completion_tokens'' VALUE completion_tokens,
''total_tokens'' VALUE total_tokens,
''call_count'' VALUE call_count
)
ORDER BY month DESC
RETURNING CLOB
)
INTO v_monthly_json
FROM (
SELECT
TO_CHAR(called_at, ''YYYY-MM'') AS month,
SUM(cost_eur) AS cost_eur,
SUM(prompt_tokens) AS prompt_tokens,
SUM(completion_tokens) AS completion_tokens,
SUM(total_tokens) AS total_tokens,
COUNT(*) AS call_count
FROM dc_ai_cost_log
WHERE called_at >= ADD_MONTHS(TRUNC(SYSDATE, ''MM''), -11)
GROUP BY TO_CHAR(called_at, ''YYYY-MM'')
);
:status_code := 200;
HTP.P(
''{"total":{''
|| '' "cost_eur":'' || ROUND(NVL(v_total_eur, 0), 6)
|| '', "total_tokens":'' || NVL(v_total_tokens, 0)
|| '', "call_count":'' || NVL(v_total_calls, 0)
|| ''}, "current_month":{''
|| '' "month":"'' || TO_CHAR(SYSDATE, ''YYYY-MM'') || ''"''
|| '', "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, ''[]'')
|| ''}''
);
EXCEPTION
WHEN OTHERS THEN
:status_code := 500;
HTP.P(''{"error":"'' || REPLACE(SQLERRM, ''"'', '''''''') || ''"}'' );
END;
'
);
COMMIT;
END;
/