Emre Göçmen Blog

SAP ABAP to Claude AI Integration: Building Intelligent Production Reports

5 min. read
413 views
0 comments

Emre Göçmen

Author

SAP ABAP to Claude AI Integration: Building Intelligent Production Reports

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:


IDText
001Date Range
002Email 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:


FieldValue
Job NameZPP_DAILY_REPORT
Job ClassC
ProgramZPP_PRODUCTION_AI_REPORT
VariantDAILY
Start TimeDaily at 18:00


API Details


ParameterValue
Endpointhttps://api.anthropic.com/v1/messages
Modelclaude-sonnet-4-5-20250929
API Version2023-06-01
Max Tokens4096


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

0

You must be logged in to comment.

No comments yet.

Be the first to comment.

Emre Göçmen

Author & Developer

I write about my experiences as a SAP ABAP & Full Stack developer.

Category

SAP

SAP

Subscribe to Newsletter

Subscribe to my newsletter to get notified about new articles.