170 lines
5.5 KiB
SQL
170 lines
5.5 KiB
SQL
-- =============================================================================
|
|
-- 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;
|
|
/
|