FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserNext example

Solving a Mosel model using a REST webservice, from Mosel

Description
In this example, the Xpress Executor should first be configured with the blend3c.mos model. Then, you run the blendxe.mos model locally; this uses REST webservice requests to send the blend.csv file to the Xpress Executor and remotely solve the model using this, then downloads and displays the results. This example requires a local installation of Xpress.

executor_rest_mosel.zip[download all files]

Source Files

Data Files





blendxe.mos

(!******************************************************
   Xpress Executor Example Model
   =============================

   file blendxe.mos
   ````````````````
   Demonstrates executing the 'blend3c' model using Xpress Executor
   with the supplied input data & displaying the results.
   Uses mmhttp to call Xpress Executor REST webservice endpoints.
   Assumes that you have an Xpress Executor component already configured
   with the blend3c model.
  
   (c) 2015-2019 Fair Isaac Corporation
       author: J. Farmer, Jul. 2017
*******************************************************!)


model "Blend XE"
 uses "mmhttp", "mmssl", "mmsystem", "mmxml"

 parameters

  ! You should set the DMP_XE_REST_ENDPOINT, DMP_SOLUTION_CLIENT_ID and DMP_SOLUTION_SECRET parameters
  ! to point to the component you want to test.
  
  ! The REST endpoint of the Xpress Executor DMP component
  ! Obtain this by clicking "View Links" for the Xpress Executor component on the DMP UI
  DMP_XE_REST_ENDPOINT=""

  ! The client ID of solution containing the Xpress Executor DMP component
  ! Obtain this through the DMP UI
  DMP_SOLUTION_CLIENT_ID=""

  ! The secret of the solution containing the Xpress Executor DMP component
  ! Obtain this through the DMP UI
  DMP_SOLUTION_SECRET=""

  ! The endpoint from which to request the authorization token.
  ! See DMP help page "Requesting a Bearerer Token" for details on how to obtain this
  DMP_BEARER_TOKEN_URL="https://iam-svc.dms.usw2.ficoanalyticcloud.com/registration/rest/client/token"

  ! The input file for the remote model
  INPUTFILE="../data/blend.csv"

  ! File into which to save the results
  RESULTFILE="blendresults.csv"

 end-parameters

 declarations
  ! XML document containing final status of execution
  executionStatus: xmldoc
  
  ! Array of parameters
  modelParameterNames: set of string
  modelParameters: dynamic array(modelParameterNames) of text

  ! Token used to authorise access to the Xpress Executor component
  bearerToken: text
 end-declarations

 forward procedure authoriseDmpComponent
 forward function startExecution(inputfile:text,modelParameters:array(modelParameterNames) of text) : xmldoc
 forward procedure waitForCompletion(executionStatus:xmldoc)
 forward procedure deleteExecution(executionStatus:xmldoc)
 forward function getURLFromPath(path:text) : text
 forward function getNodeValue(doc:xmldoc,path:text) : text
 forward function getNodeValue(doc:xmldoc,path:string) : text
 forward function fetchPathFromComponent(path:text,destfile:text) : integer
 
 public declarations
  ! Temporary variables used in assembling HTTP requests
  public httpPostBody: text
  public httpResponseBody: text
  public inputText: text
  public runLog: text
 end-declarations
 
 ! Configure Mosel to use TLS1.2 ciphers with HTTPS to allow us to talk to DMP
 setparam("https_ciphers","TLSv1.2+HIGH:\!SSLv2:\!aNULL:\!eNULL:\!3DES:@STRENGTH")

 ! Log into DMP
 writeln("Requesting authorization token from DMP")
 authoriseDmpComponent

 ! Execute our model
 writeln("Initiating model execution")
 modelParameters("INPUTFILE"):="input"     ! In Xpress Executor, the inputs we provide will be provided to the model in a file called "input"
 modelParameters("RESULTFILE"):="result"   ! In Xpress Executor, we can only access model results from a file called "result"
 executionStatus := startExecution(INPUTFILE,modelParameters)

 ! Model will be executing asynchronously; wait for it to complete.
 writeln("Waiting for completion of execution")
 waitForCompletion(executionStatus)
 
 ! Check execution was successful
 writeln("Processing model results")
 ! In event of failure, echo the remote model status, exit code & run log to aid with troubleshooting
 if getNodeValue(executionStatus,"/jsv/status")<>"OK" or getNodeValue(executionStatus,"/jsv/exitCode")<>"0" then
  writeln("Execution failed!")
  writeln("Execution status: ", getNodeValue(executionStatus,"/jsv/status"))
  writeln("Execution exit code: ", getNodeValue(executionStatus,"/jsv/exitCode"))
  writeln
  writeln("Execution log:")
  ! Fetch the remote execution log as it may contain error messages from the model
  if fetchPathFromComponent( getNodeValue(executionStatus,"/jsv/runLogPath"), "text:runLog" )<>200 then
   writeln("Execution log not available")
  end-if
  writeln(runLog)
  exit(1)
 end-if
 
 ! Download results file
 if fetchPathFromComponent( getNodeValue(executionStatus,"/jsv/resultPath"), RESULTFILE )<>200 then
  writeln("Model results are not available!")
  exit(1)
 end-if
 
 ! Parse results and display to user
 declarations
  ORES=1..2
  use: array(ORES) of real
 end-declarations
 initializations from "mmsheet.csv:"+RESULTFILE
  use as "[A1:B2]"
 end-initializations
 writeln
 writeln("Solution:")
 forall(o in ORES)  writeln(" use(",o,"): ",use(o))

 ! Delete execution from component
 deleteExecution( executionStatus )


 ! Use the client ID and secret from the solution to request a token that we can use to
 ! authorise access to the Xpress Executor component
 procedure authoriseDmpComponent
  declarations
   payloadDoc: xmldoc
   responseCode: integer
  end-declarations
  
  ! Assemble payload to send to DMP
  reset(payloadDoc)
  rootElem := addnode(payloadDoc, 0, XML_ELT, "jsv")
  setattr(payloadDoc, rootElem, "jst", "obj")
  clientIdElem := addnode(payloadDoc, rootElem, XML_ELT, "clientId")
  setattr(payloadDoc, clientIdElem, "jst", "str")
  setvalue(payloadDoc, clientIdElem, DMP_SOLUTION_CLIENT_ID)
  secretElem := addnode(payloadDoc, rootElem, XML_ELT, "secret")
  setattr(payloadDoc, secretElem, "jst", "str")
  setvalue(payloadDoc, secretElem, DMP_SOLUTION_SECRET)

  ! Post to server
  jsonsave(payloadDoc, "text:httpPostBody")
  reset(payloadDoc)
  responseCode := httppost( DMP_BEARER_TOKEN_URL, "text:httpPostBody", "text:httpResponseBody", "Content-Type: application/json" );
  reset(httpPostBody)
  
  ! Check response
  if responseCode<>200 then
    writeln("ERROR: Failed to authorise with DMP (HTTP response: "+responseCode+")")
    writeln(httpResponseBody)
    exit(1)
  end-if
  
  ! Store bearer token
  bearerToken := httpResponseBody
  reset(httpResponseBody)
 end-procedure
 
 
 ! Start execution of model, using the given file of input data.  Returns initial execution status
 function startExecution(inputfile:text,modelParameters:array(modelParameterNames) of text) : xmldoc
  declarations
   payloadDoc: xmldoc
   responseDoc: xmldoc
   responseCode: integer
   headers: text
  end-declarations
  
  ! Assemble payload to send to DMP
  reset(payloadDoc)
  rootElem := addnode(payloadDoc, 0, XML_ELT, "jsv")
  setattr(payloadDoc, rootElem, "jst", "obj")
  
  ! Set parameters
  paramsElem := addnode(payloadDoc, rootElem, XML_ELT, "parameters")
  setattr(payloadDoc, paramsElem, "jst", "obj")
  forall( paramName in modelParameterNames | exists(modelParameters(paramName)) ) do
   paramElem := addnode(payloadDoc, paramsElem, XML_ELT, "jsv")
   setattr(payloadDoc, paramElem, "name", paramName)
   setattr(payloadDoc, paramElem, "jst", "str")
   setvalue(payloadDoc, paramElem, modelParameters(paramName))
  end-do
  
  ! Set input data (use inputText for textual input, inputBase64 for binary input (after encoding it)
  fcopy( inputfile, "text:inputText" )
  if getsysstat<>0 then
   writeln("Error reading ",inputfile)
   exit(1)
  end-if
  inputDataElem := addnode(payloadDoc, rootElem, XML_ELT, "inputText")
  setattr(payloadDoc, inputDataElem, "jst", "str")
  setvalue(payloadDoc, inputDataElem, inputText )
  reset(inputText)
  
  ! Assemble HTTP headers
  headers += "Content-Type: application/json\n"
  headers += "Accepts: application/json\n"
  headers += "Authorization: Bearer "+bearerToken+"\n"
  
  ! Post to server
  jsonsave(payloadDoc, "mmsystem.text:httpPostBody")
  reset(payloadDoc)
  responseCode := httppost( DMP_XE_REST_ENDPOINT, "text:httpPostBody", "text:httpResponseBody", headers );
  reset(httpPostBody)
  
  ! Check response
  if responseCode<>200 then
    writeln("ERROR: Failed to start model execution (HTTP response: "+responseCode+")")
    writeln(httpResponseBody)
    exit(1)
  end-if
  
  ! Parse response JSON
  jsonload(responseDoc, "text:httpResponseBody", 0)
  reset(httpResponseBody)
  returned := responseDoc
  
 end-function
 

 ! Return URL of given path on Xpress Executor component
 function getURLFromPath(path:text) : text
  declarations
   url: text
  end-declarations
  ! Strip the path off the endpoint URL
  url := DMP_XE_REST_ENDPOINT
  lastSlash := 0
  forall (i in 1..3) do
   nextSlash := findtext(url,"/",lastSlash+1)
   if nextSlash=0 then
    writeln("Failed to parse URL!")
    exit(1)
   end-if
   lastSlash := nextSlash
  end-do
  deltext(url, lastSlash, getsize(url))
  ! Append given path
  url += path
  ! And done
  returned := url
 end-function
 
 
 ! Return content of given XPATH expression in given document.  Aborts model if was not found.
 function getNodeValue(doc:xmldoc,path:string) : text
  declarations
   nodeId: integer
  end-declarations
  
  nodeId := getnode(doc,path)
  if nodeId=-1 then
   writeln("Failed to find node '",path,"' in document")
   exit(1)
  end-if
  
  returned := getvalue(doc,nodeId)
 end-function
 function getNodeValue(doc:xmldoc,path:text) : text
  returned := getNodeValue(doc,string(path))
 end-function


 ! Fetch the given path from the Xpress Executor component and save content to the given destination file. Returns response code.
 function fetchPathFromComponent(path:text,destfile:text) : integer
  declarations
   responseCode: integer
   headers: text
   url: text
  end-declarations

  reset(headers)
  headers += "Accepts: application/json\n"
  headers += "Authorization: Bearer "+bearerToken+"\n"
  url := getURLFromPath( path )

  responseCode := httpget( url, string(destfile), headers )
  returned := responseCode
 end-function


 
 ! Wait for model execution to complete; updates the supplied execution status document
 procedure waitForCompletion(executionStatus: xmldoc)
  declarations
   responseCode: integer
  end-declarations

  ! Loop while status indicates execution not completed
  status := getNodeValue(executionStatus,"/jsv/status")
  while (status="NOT_COMPLETED" or status="NOT_LOADED") do
   ! Wait for half a second
   sleep(500)

   ! Refresh status
   responseCode := fetchPathFromComponent( getNodeValue(executionStatus,"/jsv/statusPath"), "text:httpResponseBody" )
   if responseCode<>200 then
    writeln("ERROR: Encountered HTTP error code ",responseCode," when querying execution status")
    writeln(httpResponseBody)  ! Write out response body, in case it contains an error message.
    exit(1)
   end-if
   jsonload(executionStatus, "text:httpResponseBody", 0)
   reset(httpResponseBody)
   status := getNodeValue(executionStatus,"/jsv/status")
  end-do
  
 end-procedure


 ! Delete the execution from the service
 procedure deleteExecution(executionStatus: xmldoc)
  declarations
   responseCode: integer
   headers: text
  end-declarations

  reset(headers)
  headers += "Accepts: application/json\n"
  headers += "Authorization: Bearer "+bearerToken+"\n"

  responseCode := httpdel( getURLFromPath(getNodeValue(executionStatus,"/jsv/statusPath")), "null:", headers )
  if responseCode<>200 then
   writeln("Warning: Received HTTP response ",responseCode," when deleting execution")
  end-if
 end-procedure


end-model

Back to examples browserNext example