Article

Automating Maximo Java to Python conversion using AI

Learn how to automate the conversion of Maximo Java customizations into Python automation scripts using watsonx.ai

By

Vidisha Limbola

In Maximo, customers often customize Java code to meet their specific business needs. However, deploying these customizations can cause downtime, disrupting workflows and reducing productivity.

To address this, IBM introduced automation scripts in Maximo 7.5. These scripts allow users to implement custom logic without requiring system downtime, ensuring seamless deployment and continuous availability. This approach enhances efficiency, minimizes disruptions, and provides a more flexible way to customize Maximo.

Benefits of automation scripts

  • Customization without Java – Customize Maximo without Java skills; automation scripts can be written in multiple languages.
  • Flexible integration – Extend Maximo’s functionality without modifying the core system.
  • Easy maintenance – Update and manage scripts directly in Maximo without the need for redeployment.

Maximo Code Assistant: Simplifying code conversion

Many organizations use custom Java code in Maximo, but converting it into automation scripts can be time-consuming and complex. To simplify this process, we developed Maximo Code Assistant, an AI-powered tool that automatically converts Java code into automation scripts with minimal manual effort.

The Maximo Code Assistant is an open-source tool designed to simplify the conversion of Maximo Java customizations into Python automation scripts. It leverages IBM watsonx.ai to automate the transformation process, ensuring accuracy and efficiency. This repository provides the framework, conversion rules, and examples needed to streamline Maximo automation development.

This tool accelerates automation adoption by reducing conversion time, optimizing code, and ensuring seamless deployment in Maximo.

We use watsonx.ai, IBM’s advanced AI platform, to train large language models (LLMs) for high-accuracy code conversion. Developers can use pre-trained models and advanced prompting techniques to generate precise automation scripts tailored to Maximo’s environment.

To download and setup the Maximo Automation Script Converter framework, see the Github repository.

Framework overview

The framework enables accurate Java-to-automation script conversion using deep Maximo knowledge and AI-powered processing.

Key features

  • Uses the Maximo API to fetch object and attribute details for precise code generation.
  • Trained with Maximo custom code and automation script examples for reliable conversion.
  • Supports .py and .java file formats.

Maximo Code Assistant Framework

Conversion process

  1. The system accepts a folder with customized Java classes and extracts all .java files.
  2. Identifies qualified class names from Java files, used as parameters for Maximo API calls.
  3. The Maximo API fetches object names and attributes to provide essential conversion context.
  4. The watsonx.ai model translates Java classes into Python automation scripts, preserving logic and structure.
  5. The LLM output is combined with Maximo API data to create a ready-to-use automation script.
  6. The generated .py files are stored in a dedicated folder, ready for validation and deployment.
  7. Developers validate scripts, create a launch point, associate relevant attributes/objects, and specify the event type (run action, validate, init). This information is included in script comments for clarity.

Optimizing prompting for LLM training

To train the LLM in watsonx.ai, we explored different approaches. Initially, we provided examples of Maximo Java customizations alongside their corresponding automation scripts. We also defined conversion rules and tested various models, but the results were unsatisfactory.

To improve accuracy, we refined our prompt by incorporating clear conversion rules and multiple Java-to-Python examples. We also included combined examples of customized Java classes and their corresponding automation scripts to reinforce the rules.

Using this optimized prompt, we tested multiple models and found that mixtral-8x7b-instruct-v01 delivered the most accurate results.

Prompt for Maximo Java to Python conversion

Role

You are a skilled Maximo developer experienced in automation scripts. Your task is to analyze the given Maximo Java customization class and convert it into an equivalent Python automation script. The script should maintain the logic and structure of the Java code while following the specified conversion rules. Ensure the script is optimized, clean, and includes meaningful comments for clarity.

Conversion rules for Maximo Java to Python automation scripts

1. Remove class definitions

Maximo automation scripts don’t require class definitions. Remove Java class structures and place the logic directly in the script.

Example:

  • Java

public class FldWorkTypeCust extends FldWorkType {
    public FldWorkTypeCust(MboValue mbv) throws MXException {
        super(mbv);
    }

    @Override
    public void action() throws MXException {
        super.action();
    }
}
  • Python
# Main script logic starts here
worktype = mbo_value.getString("WORKTYPE")

2. Variable initialization based on data type

Convert Java variable assignments to Python equivalents using Maximo methods.

String variables:

  • Java
String woStatus = this.getMboValue().getString();
  • Python
wo_status = mbo_value.getString("STATUS")

Integer Variables:

  • Java:
int wopriority = this.getMboValue().getInt();
  • Python
wopriority = mbo.getInt("WOPRIORITY")

Date variables:

  • Java
Date invoiceDate = invoice.getDate("invoicedate");
  • Python
invoicedate = mbo.getDate("invoicedate")

3. Setting values and field flags

Import the required package:

  • Java
import psdi.mbo.MboConstants;
  • Python
from psdi.mbo import MboConstants

Example

  • Java
woMbo.setValue("ACTMATCOST", totalCost, NOVALIDATION_AND_NOACTION);
  • Python
mbo.setValue("ACTMATCOST", total_cost, MboConstants.NOVALIDATION_AND_NOACTION)

Setting a field value:

  • Java
woMbo.setValue("ACTMATCOST", totalCost, NOVALIDATION_AND_NOACTION);
  • Python
mbo.setValue("ACTMATCOST", total_cost, MboConstants.NOVALIDATION_AND_NOACTION)

Setting a field flag:

  • Java
woRemote.setFieldFlag(worktype, READONLY, true);
  • Python
mbo.setFieldFlag(worktype, MboConstants.READONLY, True)

4. Throwing errors

Use Maximo’s service method for error handling.

Import the required package:

  • Java
import psdi.util.MXException;
  • Python
from psdi.util import MXException

Example

  • Java
throw new MXApplicationException("custerrgrp", "custerrkey");
  • Python
service.error("custerrgrp", "custerrkey")

5. Getting system information (e.g., system date)

Import the required package:

  • Java
import psdi.server.MXServer;
  • Python
from psdi.server import MXServer

Example

  • Java
curdate = MXServer.getMXServer().getDate();
  • Python
cur_date = MXServer.getMXServer().getDate()

6. Handling errors with parameters

If the Java code uses parameters in error handling, convert it to Python as follows:

  • Java
Object[] params = {"Invoice Date"};
throw new MXApplicationException("asset", "AsOfDateNotValid", params);
  • Python
params = ["Invoice Date"]
service.error("asset", "AsOfDateNotValid", params)

7. Changing status

Use the Python equivalent method for changing an object’s status.

  • Java
((PORemote) po).changeStatus("APPR", MXServer.getMXServer().getDate(), "Auto-approved by script");
  • Python
mbo.changeStatus("APPR", MXServer.getMXServer().getDate(), "Auto-approved by script")

8. Super method calls

Skip Java super method calls. Simply implement the required logic directly.

  • Java
super.action();
  • Python
# No equivalent needed, directly implement logic

9. No Need for mbo_value.getMbo()

Remove explicit calls to getMbo() when working with mbo_value.

  • Java
MboRemote woMbo = this.getMboValue().getMbo();
String woStatus = this.getMboValue().getString();
  • Python
wo_status = mbo.getString("STATUS")

Task: Convert Maximo Java code to Python automation script

Objective

Convert the following Maximo Java class into a Python automation script using the defined conversion rules. Ensure the script maintains the logic and functionality of the original Java class while improving readability, using descriptive variable names, and including comments for clarity.

Input: Maximo Java code

package cust.app;

import java.rmi.RemoteException;
import java.util.Date;

import psdi.app.financial.FldPartialGLAccount;
import psdi.app.invoice.FldInvoiceInvoiceDate;
import psdi.app.ticket.FldTkTicketId;
import psdi.app.workorder.FldWOPriority;
import psdi.app.workorder.FldWorkType;
import psdi.mbo.Mbo;
import psdi.mbo.MboRemote;
import psdi.mbo.MboSetRemote;
import psdi.mbo.MboValue;
import psdi.mbo.MboValueAdapter;
import psdi.plusd.app.workorder.PlusDWORemote;
import psdi.server.MXServer;
import psdi.util.MXApplicationException;
import psdi.util.MXException;

public class CombinedCustomClass extends MboValueAdapter {

    public CombinedCustomClass(MboValue mbv) {
        super(mbv);
    }

    @Override
    public void action() throws MXException, RemoteException {
        Mbo mbo = this.getMboValue().getMbo();
        String attributeName = this.getMboValue().getName();
        Date sysDate = MXServer.getMXServer().getDate();

        if (attributeName.equalsIgnoreCase("STATUS")) {
            handleWorkOrderStatus(mbo);
        } else if (attributeName.equalsIgnoreCase("WORKTYPE")) {
            handleWorkType(mbo);
        } else if (attributeName.equalsIgnoreCase("GLACCOUNT")) {
            handleGLAccount(mbo, sysDate);
        } else if (attributeName.equalsIgnoreCase("WOPRIORITY")) {
            handleWOPriority(mbo);
        } else if (attributeName.equalsIgnoreCase("INVOICEDATE")) {
            handleInvoiceDate(mbo, sysDate);
        } else if (attributeName.equalsIgnoreCase("TICKETID")) {
            handleTicketId(mbo);
        }
    }

    private void handleWorkOrderStatus(Mbo mbo) throws RemoteException, MXException {
        String status = this.getMboValue().getString();
        if (status.equalsIgnoreCase("COMP")) {
            MboSetRemote materialSet = mbo.getMboSet("SHOWACTUALMATERIAL");
            MboRemote material = materialSet.moveFirst();
            double totalCost = 0;

            while (material != null) {
                totalCost += material.getDouble("LINECOST");
                material = materialSet.moveNext();
            }
            mbo.setValue("ACTMATCOST", totalCost, Mbo.NOVALIDATION_AND_NOACTION);
        }
    }

    private void handleWorkType(Mbo mbo) throws RemoteException, MXException {
        String workType = this.getMboValue().getString();
        if (workType.equalsIgnoreCase("EM")) {
            mbo.setValue("WOPRIORITY", "1", Mbo.NOVALIDATION_AND_NOACTION);
            mbo.setFieldFlag("WORKTYPE", Mbo.READONLY, true);
        }
    }

    private void handleGLAccount(Mbo mbo, Date sysDate) throws RemoteException, MXException {
        String glAccount = this.getMboValue().getString();
        String workType = mbo.getString("WORKTYPE");

        if (glAccount != null && workType.equalsIgnoreCase("PM")) {
            ((PlusDWORemote) mbo).changeStatus("APPR", sysDate, "Work order Status moved to APPR");
        }
    }

    private void handleWOPriority(Mbo mbo) throws RemoteException, MXException {
        int priority = this.getMboValue().getInt();
        if (priority > 5) {
            throw new MXApplicationException("custerrgrp", "custerrkey");
        }
    }

    private void handleInvoiceDate(Mbo mbo, Date curDate) throws RemoteException, MXException {
        Date invoiceDate = mbo.getDate("invoicedate");
        if (invoiceDate.before(curDate)) {
            Object[] params = { "Invoice Date" };
            throw new MXApplicationException("asset", "AsOfDateNotValid", params);
        }
    }

    private void handleTicketId(Mbo mbo) throws RemoteException, MXException {
        String app = mbo.getThisMboSet().getApp();
        if (app != null && app.equalsIgnoreCase("SR")) {
            mbo.setValue("REPORTEDPRIRITY", 2, Mbo.NOACCESSCHECK);
        }
    }
}

Output: Python automation script

from psdi.util import MXException, MXApplicationException
from psdi.mbo import MboConstants
from psdi.server import MXServer

try:
    attribute_name = mbo_value.getName()
    sys_date = MXServer.getMXServer().getDate()

    if attribute_name == "STATUS":
        totalCost = 0
        woStatus = mbo.getString("STATUS")
        if woStatus == "COMP":
            childMatUseTransSet = mbo.getMboSet("SHOWACTUALMATERIAL")
            if not childMatUseTransSet.isEmpty():
                childMatUseTransMbo = childMatUseTransSet.moveFirst()
                while childMatUseTransMbo is not None:
                    linecost = childMatUseTransMbo.getDouble("LINECOST")
                    totalCost += linecost
                    childMatUseTransMbo = childMatUseTransSet.moveNext()
                mbo.setValue("ACTMATCOST", totalCost, MboConstants.NOVALIDATION_AND_NOACTION)

    elif attribute_name == "WORKTYPE":
        worktype = mbo.getString("WORKTYPE")
        if worktype == "EM":
            mbo.setValue("WOPRIORITY", "1", MboConstants.NOVALIDATION_AND_NOACTION)
            mbo.setFieldFlag("WORKTYPE", MboConstants.READONLY, True)

    elif attribute_name == "GLACCOUNT":
        woGlAccount = mbo.getString("GLACCOUNT")
        woWorkType = mbo.getString("WORKTYPE")
        if woGlAccount and woWorkType == "PM":
            mbo.changeStatus("APPR", sys_date, "Workorder Status moved to APPR")

    elif attribute_name == "WOPRIORITY":
        wopriority = mbo.getInt("WOPRIORITY")
        if wopriority > 5:
            raise MXApplicationException("custerrgrp", "custerrkey")

    elif attribute_name == "INVOICEDATE":
        curDate = sys_date
        invoiceDate = mbo.getDate("INVOICEDATE")
        if invoiceDate < curDate:
            raise MXApplicationException("asset", "AsOfDateNotValid", ["Invoice Date"])

    elif attribute_name == "TICKETID":
        app = mbo.getThisMboSet().getApp()
        if app and app.upper() == "SR":
            mbo.setValue("REPORTEDPRIORITY", 2, MboConstants.NOACCESSCHECK)

except MXException as e:
    scriptLog.error("Got an MXException: " + str(e))
except Exception as e:
    scriptLog.error("Got an Exception: " + str(e))

Testing the code conversion framework

Input

Provide your customized Java class here.

Test scenario

If we input a Java class that updates the Work Order (WO) status to "APPR" when the GL account is updated for a PM-type WO, the framework processes it as follows:

Framework input

package cust.app.workorder;

import java.rmi.RemoteException;
import java.util.Date;

import psdi.app.financial.FldPartialGLAccount;
import psdi.plusd.app.workorder.PlusDWORemote;
import psdi.mbo.MboValue;
import psdi.server.MXServer;
import psdi.util.MXException;

public class FldGLAccNew extends FldPartialGLAccount {

    public FldGLAccNew(MboValue mbv) {
        super(mbv);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void action() throws RemoteException, MXException {
        super.action();

        PlusDWORemote woMbo = (PlusDWORemote) this.getMboValue().getMbo();

        String woGlAccount = this.getMboValue().getString();
        String woWorkType = this.getMboValue("WORKTYPE").getString();
        Date sysdate = MXServer.getMXServer().getDate();

        if (woGlAccount != null && woWorkType == "PM") {
            woMbo.changeStatus("APPR", sysdate, "Workorder Status moved to APPR");
        }

    }

}

Framework output

#Class name :psdi.app.financial.FldPartialGLAccount
#This automation script has been generated using watsonx.ai.
#The required parameters to create this in Maximo Environment are as follows:
#Provided class name is deployed multiple times here is the list of object names and attribute names [{'attribute_name': 'GLACCOUNT', 'object_name': 'INCIDENT'}]
#LAUNCHPOINT: ATTRIBUTE
#EVENTTYPE: RUN ACTION 

from psdi.util import MXException, MXApplicationException
from psdi.mbo import MboConstants
from psdi.server import MXServer

try:
    mbo_value = mbo.getMboValue("GLACCOUNT")
    mbo_worktype = mbo.getString("WORKTYPE")
    sys_date = MXServer.getMXServer().getDate()

    if mbo_value and mbo_worktype == "PM":
        mbo.changeStatus("APPR", sys_date, "Workorder Status moved to APPR")

except MXException as e:
    scriptLog.error("Got an MXException: " + str(e))
except Exception as e:
    scriptLog.error("Got an Exception: " + str(e))

Conclusion

This approach enables efficient and accurate conversion of Maximo Java code into Python automation scripts. By systematically extracting object names, attributes, and logic, the framework ensures seamless adaptation to Maximo’s Python environment while preserving functionality and minimizing errors.

Currently, this solution is designed and tested for field-level classes in Maximo. It can be extended to support other class types, such as object-level, integration, action, and cron classes, with additional testing and customization.

Acknowledgements

I would like to extend my thanks to Amitabha Kanjilal and Satish Somanna Narasimha for their guidance and support in developing this solution.