SAP ABAP to Claude AI Integration: Building Intelligent Production Reports
Emre Göçmen
Author

Analyzing SAP production data and sending automated reports to managers is a common requirement. In this article, we'll develop a complete ABAP application that analyzes production data using Claude API and sends the results via email.
Our application consists of four main classes:
- lcl_production_data: Collects and summarizes production data
- lcl_claude_api: Communicates with Claude API
- lcl_mail_sender: Manages email sending
- lcl_application: Coordinates the entire process
Let's proceed step by step.
Step 1: Program Definition and Type Declarations
First, we define our program and create the data types we'll need. We define separate structures for production data, summary information, and API response.
*&---------------------------------------------------------------------*
*& Report ZPP_PRODUCTION_AI_REPORT
*&---------------------------------------------------------------------*
*& Analyzes production data with Claude API and sends email
*&---------------------------------------------------------------------*
REPORT zpp_production_ai_report.
*----------------------------------------------------------------------*
* Type Definitions
*----------------------------------------------------------------------*
TYPES:
BEGIN OF gty_production,
aufnr TYPE aufnr,
matnr TYPE matnr,
maktx TYPE maktx,
werks TYPE werks_d,
gamng TYPE gamng,
gmein TYPE meins,
budat TYPE budat,
lmnga TYPE lmnga,
xmnga TYPE xmnga,
rmnga TYPE rmnga,
END OF gty_production,
gty_t_production TYPE STANDARD TABLE OF gty_production WITH EMPTY KEY,
BEGIN OF gty_summary,
budat TYPE budat,
plan_qty TYPE gamng,
actual_qty TYPE lmnga,
scrap_qty TYPE xmnga,
rework_qty TYPE rmnga,
efficiency TYPE decfloat16,
scrap_rate TYPE decfloat16,
END OF gty_summary,
gty_t_summary TYPE STANDARD TABLE OF gty_summary WITH EMPTY KEY,
BEGIN OF gty_content,
type TYPE string,
text TYPE string,
END OF gty_content,
gty_t_content TYPE STANDARD TABLE OF gty_content WITH EMPTY KEY,
BEGIN OF gty_api_response,
id TYPE string,
type TYPE string,
model TYPE string,
role TYPE string,
content TYPE gty_t_content,
stop_reason TYPE string,
END OF gty_api_response.
gty_production: Used for raw production data we'll fetch from AFKO, AFPO, and AFRU tables. Contains production order number, material, planned quantity, actual quantity, scrap, and rework quantities.
gty_summary: Holds summary data calculated on a daily basis. Efficiency and scrap rate are calculated in this structure.
gty_api_response: Used to parse JSON response from Claude API. We'll read the text field from the content array in the API response.
Step 2: Production Data Class
This class fetches production data from SAP and calculates daily summaries. It receives date range in the constructor and immediately starts data fetching and summary calculation.
*----------------------------------------------------------------------*
* Production Data Class
*----------------------------------------------------------------------*
CLASS lcl_production_data DEFINITION FINAL.
PUBLIC SECTION.
METHODS constructor
IMPORTING
iv_date_from TYPE datum
iv_date_to TYPE datum.
METHODS get_data
RETURNING VALUE(rt_data) TYPE gty_t_production.
METHODS get_summary
RETURNING VALUE(rt_summary) TYPE gty_t_summary.
PRIVATE SECTION.
DATA mv_date_from TYPE datum.
DATA mv_date_to TYPE datum.
DATA mt_data TYPE gty_t_production.
DATA mt_summary TYPE gty_t_summary.
METHODS select_data.
METHODS calculate_summary.
ENDCLASS.
CLASS lcl_production_data IMPLEMENTATION.
METHOD constructor.
mv_date_from = iv_date_from.
mv_date_to = iv_date_to.
select_data( ).
calculate_summary( ).
ENDMETHOD.
METHOD select_data.
SELECT afko~aufnr,
afpo~matnr,
makt~maktx,
afpo~pwerk AS werks,
afko~gamng,
afko~gmein,
afru~budat,
afru~lmnga,
afru~xmnga,
afru~rmnga
FROM afko
INNER JOIN afpo
ON afpo~aufnr = afko~aufnr
INNER JOIN afru
ON afru~aufnr = afko~aufnr
LEFT OUTER JOIN makt
ON makt~matnr = afpo~matnr
AND makt~spras = @sy-langu
WHERE afru~budat BETWEEN @mv_date_from AND @mv_date_to
AND afru~stokz = @abap_false
INTO CORRESPONDING FIELDS OF TABLE @mt_data.
ENDMETHOD.
METHOD calculate_summary.
DATA ls_summary TYPE gty_summary.
CLEAR mt_summary.
LOOP AT mt_data ASSIGNING FIELD-SYMBOL(<ls_data>)
GROUP BY <ls_data>-budat
ASSIGNING FIELD-SYMBOL(<lo_group>).
CLEAR ls_summary.
ls_summary-budat = <lo_group>.
LOOP AT GROUP <lo_group> ASSIGNING FIELD-SYMBOL(<ls_item>).
ADD <ls_item>-gamng TO ls_summary-plan_qty.
ADD <ls_item>-lmnga TO ls_summary-actual_qty.
ADD <ls_item>-xmnga TO ls_summary-scrap_qty.
ADD <ls_item>-rmnga TO ls_summary-rework_qty.
ENDLOOP.
IF ls_summary-plan_qty > 0.
ls_summary-efficiency = ls_summary-actual_qty / ls_summary-plan_qty * 100.
ENDIF.
IF ls_summary-actual_qty > 0.
ls_summary-scrap_rate = ls_summary-scrap_qty / ls_summary-actual_qty * 100.
ENDIF.
APPEND ls_summary TO mt_summary.
ENDLOOP.
SORT mt_summary BY budat.
ENDMETHOD.
METHOD get_data.
rt_data = mt_data.
ENDMETHOD.
METHOD get_summary.
rt_summary = mt_summary.
ENDMETHOD.
ENDCLASS.
select_data method: Joins AFKO (production order header), AFPO (production order item), AFRU (production confirmation), and MAKT (material description) tables to fetch data. The stokz = false filter gets non-cancelled confirmations.
calculate_summary method: Groups data by date using GROUP BY syntax. Sums planned, actual, scrap, and rework quantities for each day. Then calculates efficiency and scrap rate.
Step 3: Claude API Client Class
This class communicates with Claude API over HTTP. API key, endpoint, and model information are defined as constants.
*----------------------------------------------------------------------*
* Claude API Client Class
*----------------------------------------------------------------------*
CLASS lcl_claude_api DEFINITION FINAL.
PUBLIC SECTION.
CONSTANTS gc_api_key TYPE string VALUE 'sk-ant-api03-XXXXXXXXXXXXXXXXXXXXXXXX'.
CONSTANTS gc_api_url TYPE string VALUE 'https://api.anthropic.com/v1/messages'.
CONSTANTS gc_api_version TYPE string VALUE '2023-06-01'.
CONSTANTS gc_model TYPE string VALUE 'claude-sonnet-4-5-20250929'.
CLASS-METHODS analyze
IMPORTING
it_summary TYPE gty_t_summary
RETURNING
VALUE(rv_result) TYPE string.
PRIVATE SECTION.
CLASS-METHODS create_request_body
IMPORTING
it_summary TYPE gty_t_summary
RETURNING
VALUE(rv_body) TYPE string.
CLASS-METHODS parse_response
IMPORTING
iv_json TYPE string
RETURNING
VALUE(rv_text) TYPE string.
ENDCLASS.
CLASS lcl_claude_api IMPLEMENTATION.
METHOD analyze.
DATA lo_http_client TYPE REF TO if_http_client.
DATA lv_response TYPE string.
DATA lv_status TYPE i.
cl_http_client=>create_by_url(
EXPORTING
url = gc_api_url
ssl_id = 'ANONYM'
IMPORTING
client = lo_http_client
EXCEPTIONS
OTHERS = 1 ).
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_sy_create_object_error.
ENDIF.
lo_http_client->propertytype_logon_popup = if_http_client=>co_disabled.
lo_http_client->request->set_method( if_http_request=>co_request_method_post ).
lo_http_client->request->set_header_field(
name = 'Content-Type'
value = 'application/json' ).
lo_http_client->request->set_header_field(
name = 'x-api-key'
value = gc_api_key ).
lo_http_client->request->set_header_field(
name = 'anthropic-version'
value = gc_api_version ).
lo_http_client->request->set_cdata( create_request_body( it_summary ) ).
lo_http_client->send(
EXCEPTIONS
OTHERS = 1 ).
IF sy-subrc <> 0.
lo_http_client->close( ).
RAISE EXCEPTION TYPE cx_sy_create_object_error.
ENDIF.
lo_http_client->receive(
EXCEPTIONS
OTHERS = 1 ).
IF sy-subrc <> 0.
lo_http_client->close( ).
RAISE EXCEPTION TYPE cx_sy_create_object_error.
ENDIF.
lv_status = lo_http_client->response->get_header_field( '~status_code' ).
lv_response = lo_http_client->response->get_cdata( ).
lo_http_client->close( ).
IF lv_status = 200.
rv_result = parse_response( lv_response ).
ELSE.
rv_result = |HTTP Error: { lv_status }|.
ENDIF.
ENDMETHOD.
METHOD create_request_body.
DATA lv_json TYPE string.
lv_json = /ui2/cl_json=>serialize(
data = it_summary
pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
REPLACE ALL OCCURRENCES OF '\' IN lv_json WITH '\\'.
REPLACE ALL OCCURRENCES OF '"' IN lv_json WITH '\"'.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf IN lv_json WITH '\n'.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>newline IN lv_json WITH '\n'.
rv_body =
`{` &&
`"model":"` && gc_model && `",` &&
`"max_tokens":4096,` &&
`"messages":[{` &&
`"role":"user",` &&
`"content":"Analyze the following production data. ` &&
`Identify efficiency, scrap rate and areas requiring attention. ` &&
`Present results in executive summary format:\n\n` && lv_json && `"` &&
`}]` &&
`}`.
ENDMETHOD.
METHOD parse_response.
DATA ls_response TYPE gty_api_response.
/ui2/cl_json=>deserialize(
EXPORTING
json = iv_json
CHANGING
data = ls_response ).
READ TABLE ls_response-content INDEX 1 INTO DATA(ls_content).
IF sy-subrc = 0.
rv_text = ls_content-text.
ENDIF.
ENDMETHOD.
ENDCLASS.
analyze method: Creates HTTP client, sets required headers, and sends POST request. If response is successful, extracts content using parse_response.
create_request_body method: Converts summary data to JSON and creates request body in Claude API format. Special characters are escaped.
parse_response method: Reads and returns the text field from the content array in API response.
Step 4: Mail Sender Class
This class handles email sending using the CL_BCS class.
*----------------------------------------------------------------------*
* Mail Sender Class
*----------------------------------------------------------------------*
CLASS lcl_mail_sender DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS send
IMPORTING
iv_subject TYPE clike
iv_body TYPE string
it_emails TYPE string_table
RAISING
cx_send_req_bcs.
ENDCLASS.
CLASS lcl_mail_sender IMPLEMENTATION.
METHOD send.
DATA lt_body TYPE soli_tab.
DATA lo_document TYPE REF TO cl_document_bcs.
DATA lo_request TYPE REF TO cl_bcs.
DATA lo_sender TYPE REF TO cl_sapuser_bcs.
DATA lo_recipient TYPE REF TO if_recipient_bcs.
DATA lv_email TYPE adr6-smtp_addr.
SPLIT iv_body AT cl_abap_char_utilities=>newline INTO TABLE lt_body.
lo_document = cl_document_bcs=>create_document(
i_type = 'RAW'
i_text = lt_body
i_subject = CONV so_obj_des( iv_subject ) ).
lo_request = cl_bcs=>create_persistent( ).
lo_request->set_document( lo_document ).
lo_sender = cl_sapuser_bcs=>create( sy-uname ).
lo_request->set_sender( lo_sender ).
LOOP AT it_emails INTO DATA(ls_email).
lv_email = ls_email.
lo_recipient = cl_cam_address_bcs=>create_internet_address( lv_email ).
lo_request->add_recipient( lo_recipient ).
ENDLOOP.
lo_request->set_send_immediately( abap_true ).
lo_request->send( ).
COMMIT WORK AND WAIT.
ENDMETHOD.
ENDCLASS.
send method: Splits mail content into lines, creates document, adds recipients, and sends immediately. COMMIT WORK AND WAIT ensures the operation completes.
Step 5: Main Application Class
This class coordinates the entire process. It executes data collection, analysis, and email sending steps in sequence.
*----------------------------------------------------------------------*
* Main Application Class
*----------------------------------------------------------------------*
CLASS lcl_application DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS run
IMPORTING
iv_date_from TYPE datum
iv_date_to TYPE datum
it_emails TYPE string_table.
ENDCLASS.
CLASS lcl_application IMPLEMENTATION.
METHOD run.
DATA lo_production TYPE REF TO lcl_production_data.
DATA lt_summary TYPE gty_t_summary.
DATA lv_analysis TYPE string.
DATA lv_subject TYPE string.
" Collect production data
lo_production = NEW lcl_production_data(
iv_date_from = iv_date_from
iv_date_to = iv_date_to ).
lt_summary = lo_production->get_summary( ).
IF lt_summary IS INITIAL.
MESSAGE 'No data found for selected date range' TYPE 'S' DISPLAY LIKE 'W'.
RETURN.
ENDIF.
" Analyze with Claude API
TRY.
lv_analysis = lcl_claude_api=>analyze( lt_summary ).
CATCH cx_root INTO DATA(lx_error).
MESSAGE lx_error->get_text( ) TYPE 'E'.
RETURN.
ENDTRY.
" Send email
lv_subject = |Production Report: { iv_date_from DATE = USER } - { iv_date_to DATE = USER }|.
TRY.
lcl_mail_sender=>send(
iv_subject = lv_subject
iv_body = lv_analysis
it_emails = it_emails ).
MESSAGE 'Report sent successfully' TYPE 'S'.
CATCH cx_send_req_bcs INTO DATA(lx_mail).
MESSAGE lx_mail->get_text( ) TYPE 'E'.
ENDTRY.
ENDMETHOD.
ENDCLASS.
run method: Executes three main steps in sequence. Error checking is performed at each step and informational messages are displayed to the user.
Step 6: Selection Screen and Program Start
Finally, we define the user interface and program entry point.
*----------------------------------------------------------------------*
* Selection Screen
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.
PARAMETERS p_from TYPE datum.
PARAMETERS p_to TYPE datum.
SELECTION-SCREEN END OF BLOCK b1.
SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-002.
PARAMETERS p_mail1 TYPE ad_smtpadr.
PARAMETERS p_mail2 TYPE ad_smtpadr.
PARAMETERS p_mail3 TYPE ad_smtpadr.
SELECTION-SCREEN END OF BLOCK b2.
*----------------------------------------------------------------------*
* Program Start
*----------------------------------------------------------------------*
START-OF-SELECTION.
DATA lt_emails TYPE string_table.
IF p_mail1 IS NOT INITIAL.
APPEND CONV string( p_mail1 ) TO lt_emails.
ENDIF.
IF p_mail2 IS NOT INITIAL.
APPEND CONV string( p_mail2 ) TO lt_emails.
ENDIF.
IF p_mail3 IS NOT INITIAL.
APPEND CONV string( p_mail3 ) TO lt_emails.
ENDIF.
IF lt_emails IS INITIAL.
MESSAGE 'At least one email address is required' TYPE 'E'.
ENDIF.
lcl_application=>run(
iv_date_from = p_from
iv_date_to = p_to
it_emails = lt_emails ).
The selection screen contains fields for date range and three email addresses. At program start, entered email addresses are transferred to a table and lcl_application=>run method is called.
Text Elements
Define the following text elements in SE38 transaction:
| ID | Text |
|---|---|
| 001 | Date Range |
| 002 | Email Recipients |
Prerequisites
SSL Certificate
Add api.anthropic.com certificate to SSL Client (Anonymous) node in STRUST transaction.
API Key
Get your API key from Anthropic Console and set it in gc_api_key constant.
Background Job Setup
In SM36 transaction:
| Field | Value |
|---|---|
| Job Name | ZPP_DAILY_REPORT |
| Job Class | C |
| Program | ZPP_PRODUCTION_AI_REPORT |
| Variant | DAILY |
| Start Time | Daily at 18:00 |
API Details
| Parameter | Value |
|---|---|
| Endpoint | https://api.anthropic.com/v1/messages |
| Model | claude-sonnet-4-5-20250929 |
| API Version | 2023-06-01 |
| Max Tokens | 4096 |
Conclusion
In this article, we developed a complete ABAP application that analyzes SAP production data using Claude API and sends results via email. The application is written with object-oriented design principles and is suitable for running as a background job.
The same architecture can be easily adapted for different data sets such as quality notifications, stock movements, or sales data.
Comments
No comments yet.
Be the first to comment.



