Serving up Analysis Data as a Web Service REST Endpoint

Examples of Serving Data via REST Request

There may be a need to share MES data with external tools. While we do not recommend going directly to the database tables, there are options to share MES data externally. We recommend using the built-in functions to extract data from the database. 

    Article Coverage

    This KB article demonstrates two ways to serve up analysis data when a REST request is received from an external tool.  

    1. Custom analysis settings - payload determines analysis executed
    2. Pre-made analysis settings - payload executes a predefined analysis 

    We will use Postman for testing. Documentation on Postman is beyond the scope of this KB and should be reviewed on the Postman (www.postman.com).

    Web Services

    The Web Services module from Sepasoft provides the means to create both Consumers and Providers using either REST or SOAP.  This KB will focus on building a REST provider, although a SOAP provider could be built to mimic the same behavior if needed.

    On installation of the Web Services module, you will see the Web Services module appear in the MES Gateway project by default.  You can change this behavior on the gateway if you would like to use a different project to make your configurations.



    Configuration

    From the designer, open web services → Providers.  Right Click on RESTful Endpoints and create a New Folder.  It is best practice to "wrap" your web service endpoints in a version number.  You will be able to make changes and test endpoints without affecting "production" endpoints.  Tools and users relying on your endpoints will then be able to migrate at their convenience.


    Helper Functions

    Information

    There are a few helper functions included with both example scripts which can be migrated to project scripts for reuse.  They were included with each script for example purposes.

    • parseDate - takes in a date in iso ISO 8601format.
    • equipmentExists - checks if the equipment path passed in is valid
    • datasetToDictList - converts the dataset returned from analysis to a python dictionary, respecting ordering to return as JSON to the requester.
    • createAnalysisSetting - validates and creates an analysis settings object to be executed

    Example 1

    Create a structure like so, ending with CustomQuery:

    This path will end up becoming your URL path.  You can verify by right-clicking the endpoint, copying the RESTful endpoint URL, and pasting the text somewhere. 

    For this method, we will use a POST request due to being able to use the request body to send complex data structures.

    Paste the following script in the script block:

    CustomQueryEndpoint

    Python
    #def do_POST(request):  This line is not neccessary to past since it is included with the request when created
    import java
    DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"
    def parseDate(date):
    try:
    dateObj = system.date.parse(date, DATE_FORMAT)
    return dateObj
    except:
    return None

    def equipmentExists(eqPath):
    if eqPath:
    filter = system.mes.object.filter.createFilter()
    filter.setEnableStateName('ENABLED')
    filter.setPrimaryMESObjectPath(eqPath)
    results = system.mes.searchMESObjects(filter)
    if len(results) != 0:
    return True
    return False

    def datasetToDictList(ds, orderBy = None, descOrder=True):
    import operator
    data = []
    for row in range(ds.getRowCount()):
    rowDict = {}
    for col in range(ds.getColumnCount()):
    rowDict.update({ds.getColumnName(col):ds.getValueAt(row,col)})
    data.append(rowDict)

    if orderBy:
    sortedData = sorted(data, key = operator.itemgetter(*orderBy), reverse=descOrder)
    return sortedData
    return data

    def createAnalysisSetting(datapoints,groupBy,orderBy,filterBy,settings):
    analysis_setting = system.mes.analysis.createMESAnalysisSettings("test_setting")
    analysis_setting.addParameter('eqPath')
    expression = " Equipment Path = @eqPath"

    if len(filterBy) is not 0:
    filterBy = expression + " AND " + filterBy

    for key in settings:
    analysis_setting.addSettingValue(key,str(settings[key]))

    if(set(groupBy).issubset(set(datapoints)) or set(orderBy).issubset(set(datapoints)) ):
    analysis_setting.setDataPoints(datapoints)
    analysis_setting.setGroupBy(groupBy)
    analysis_setting.setOrderBy(orderBy)
    analysis_setting.setFilterExpression(filterBy)
    return analysis_setting

    elif( (orderBy[0] == '' or groupBy[0] == '' ) and datapoints[0] != ''):
    analysis_setting.setDataPoints(datapoints)
    analysis_setting.setFilterExpression(filterBy)
    return analysis_setting

    else:
    return None

    req_body = request['body']
    logger = system.util.getLogger('API.CustomQuery')
    logger.info(str(req_body))
    """Example payload
    {
    "eqPath":"New Enterprise\\Site 1\\Area 1\\Packaging Line 1",
    "startDate":"2021-07-24T08:14:1-08:00",
    "endDate":"2021-07-26T10:14:12-08:00",
    "dataPoints":["Line Downtime Reason", "Line State Duration"],
    "groupBy":["Line Downtime Reason"],
    "orderBy":["Line Downtime Reason"],
    "filterBy":"Line Downtime Reason != ''",
    "settings":{
    "row limit":5,
    "descending order":true
    }
    }
    """

    requiredInputs = set(['eqPath','startDate','endDate','dataPoints','groupBy','orderBy','filterBy','settings'])

    if not requiredInputs.issubset(req_body.keys()):
    missing = []
    diff = requiredInputs.difference(req_body.keys())
    for item in diff:
    missing.append(item)
    res_content = {
    'error': 'Missing items',
    'data': missing
    }
    return {'code': 400, 'headers': {}, 'content': res_content}

    eqPath = req_body['eqPath']
    datapoints = req_body['dataPoints']
    groupBy = req_body['groupBy']
    orderBy = req_body['orderBy']
    filterBy = req_body['filterBy']
    settings = req_body["settings"]
    startDate = parseDate(req_body['startDate'])
    endDate = parseDate(req_body['endDate'])

    if not startDate or not endDate:
    res_content = {'error': 'Unparseable Date or Missing Date',}
    return {'code': 400, 'content': res_content}

    if len(datapoints) is 0:
    res_content = {'error': 'Datapoints are required',
    'data':datapoints}
    return {'code': 400, 'content': res_content}

    if not equipmentExists(eqPath):
    res_content = {
    'error': 'Equipment not found',
    'data':eqPath
    }
    return {'code': 400, 'content': res_content}


    params = {'eqPath' : eqPath}
    analysis_setting = createAnalysisSetting(datapoints,groupBy,orderBy,filterBy,settings)
    if (analysis_setting is None):
    res_content = {'error': 'Invalid Group By or Filter By Parameters',
    'data':{ "Group By": str(groupBy), "Order By" :str(orderBy)}}
    return {'code': 400, 'content': res_content}

    result = system.mes.analysis.executeAnalysis(startDate, endDate, analysis_setting,params).getDataset()
    dictList = datasetToDictList(result, orderBy, settings['descending order'])

    res_code = 200
    res_content = {'data': dictList,
    'eqPath':eqPath
    }

    return {'code': res_code, 'content': res_content}
     

    Error Save your project


    This first example endpoint will create analysis settings based on what is passed in via the POST body. 

    To test this endpoint, copy the RESTful Endpoint URL

    Opening our Postman, we can develop a request to test our new endpoint.

    Paste in the copied URL into a new POST request in Postman.  Using Environmental variables is optional but useful for testing different environments.

    In the Body section, paste in raw JSON (replace values with your relevant data):

    request body

    Python
    {

    "eqPath":"Enterprise\\El Dorado Hills\\Packaging Area\\Packaging Line 1",
    "startDate":"2023-01-05T15:04:59Z",
    "endDate":"2023-01-05T23:04:59Z",
    "dataPoints":["Product Code", "OEE Outfeed Count","OEE Infeed Count", "OEE Reject Count"],
    "groupBy":["Product Code"],
    "orderBy":["OEE Reject Count"],
    "filterBy":"Product Code != ''",
    "settings":{
    "descending order":true
    }
    }

    This particular request would give us OEE counts by product code, sorted from the greatest number of rejects to least.

    If this executed correctly, we should receive a 200 HTTP code and response data.

    Here is an example:


    Example 2

    You may want to create multiple endpoints for tools to consume without knowing how to use analysis.  In this example we will create an endpoint that will execute and return analysis where the requester only needs to know minimal data like the equipment path, start date, and end date.

    Create a structure like so, ending with PartsStartedVsIdealHourly:

    This path will end up becoming your URL path.  You can verify by right-clicking the endpoint, copying the RESTful endpoint URL, and pasting the text somewhere. 

    For this method, we will use a GET Request with a few parameters.

    Paste the following script in the script block:

    PartsStartedVsIdealHourly Endpoint

    Python
    #def do_GET(request):   This line is not neccessary to past since it is included with the request when created
    #Helper functions could be moved to project scripts for reuse. Adding here for exportable example.
    import java
    DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"
    def parseDate(date):
    try:
    dateObj = system.date.parse(date, DATE_FORMAT)
    return dateObj
    except:
    return None

    def equipmentExists(eqPath):
    if eqPath:
    filter = system.mes.object.filter.createFilter()
    filter.setEnableStateName('ENABLED')
    filter.setPrimaryMESObjectPath(eqPath)
    results = system.mes.searchMESObjects(filter)
    if len(results) != 0:
    return True
    return False

    def datasetToDictList(ds, orderBy = None, descOrder=True):
    import operator
    data = []
    for row in range(ds.getRowCount()):
    rowDict = {}
    for col in range(ds.getColumnCount()):
    rowDict.update({ds.getColumnName(col):ds.getValueAt(row,col)})
    data.append(rowDict)

    if orderBy:
    sortedData = sorted(data, key = operator.itemgetter(*orderBy), reverse=descOrder)
    return sortedData
    return data


    req_query = request['query']

    """
    Input params:
    -eqPath
    -startDate
    -endDate
    """
    requiredInputs = set(['eqPath','startDate','endDate'])

    if not requiredInputs.issubset(req_query.keys()):
    missing = []
    diff = requiredInputs.difference(req_query.keys())
    for item in diff:
    missing.append(item)
    res_content = {
    'error':'Missing items',
    'data': missing
    }
    return {'code': 400, 'headers': {}, 'content': res_content}

    eqPath = req_query['eqPath']
    startDate = parseDate(req_query['startDate'])
    endDate = parseDate(req_query['endDate'])

    if not startDate or not endDate:
    res_content = {'error': 'Unparseable Date'}
    return {'code': 400, 'headers': {}, 'content': res_content}

    if not equipmentExists(eqPath):
    res_content = {
    'error': 'Equipment not found',
    'data':eqPath
    }
    return {'code': 400, 'headers': {}, 'content': res_content}

    analysisName = 'Parts Started vs Ideal - Hourly'

    params = {'eqPath' : eqPath}

    analysis_setting = system.mes.analysis.createMESAnalysisSettings("API_LineProductionGoal")
    datapoints = [
    "Top of Hour Interval",
    "Infeed Standard Count",
    "OEE Infeed Count"
    ]
    analysis_setting.setDataPoints(datapoints)
    analysis_setting.addParameter('eqPath')
    analysis_setting.setFilterExpression("Equipment Path = @eqPath")
    analysis_setting.setGroupBy("Top of Hour Interval")

    result = system.mes.analysis.executeAnalysis(startDate, endDate, analysis_setting, params).getDataset()
    dictList = datasetToDictList(result)

    res_code = 200
    res_headers = {}
    res_content = {'data': dictList}
    res_content.update({'eqPath':str(eqPath)})

    return {'code': res_code, 'headers': res_headers, 'content': res_content}

    Error Save your project

    Copy your RESTful Endpoint URL from the project browser:

    Create a new GET request in Postman and paste your RESTful endpoint URL in the address bar.

    As you add Params, it will build the url for you.  Environmental variables are optional and beyond the scope of this KB. 

    Use Single backslashes for your equipment path here.  The time format is the same.


    The endpoint should return time series data giving hourly actual count vs target count based on the OEE standard Rate.


    Summary

    These are just two examples of what you can do with the Web Service endpoints while leveraging the Sepasoft modules as the data source.

    This is the best practice method for exposing data to external tools, whether custom or other COTS tools such as PowerBI or Tableau.