BookmarkSubscribeRSS Feed

SAS Retail Pricing API Use Case

Started ‎08-26-2022 by
Modified ‎08-26-2022 by
Views 3,508

My name is Patrick Godfrey and I'm a computer science student at Oregon State University. This summer, I have the pleasure of working at SAS with the Retail team. My project is to create use case examples for some of our pricing APIs. This article walks through the end-to-end process and how I interacted with the API. 

 

Use case

 

The SAS Retail team is developing a pricing optimization solution that focuses on markdown pricing. This solution takes a product scope, geographic scope and a range of dates and returns pricing plans for the given products. Completing the use case using the SAS front-end application is a fairly routine process; however, some customers may want to interact with the APIs directly in order to automate the workflow or integrate it into their own systems.

 

Reviewing the current documentation, there are individual examples available to customers. However, I find it an effective tool to demonstrate how to string the APIs together to perform a series of tasks that one would do in the GUI. This is where I spent the majority of my summer internship.

 

I'll cover the following use cases in this article:

Create => Execute => Extract Examples

Update Lifecycle Examples

 

The Code

The entire code set I'll discuss in this article is available in the SAS REST API end-to-end use cases repository on GitHub, which is a collection of use cases presented in a variety of languages. My language of choice is Python, so naturally, you'll find my repository under the Python directory.

 

The primary file for interacting with the Pricing solution API is pricing_optimization.py. This file contains most of the access points for the API. The file for creating and validating schemas is schema_collection.py. This file has functions that compile schemas for use in pricing_optimization.py. Finally, there is the conf.ini file, which holds the base URI and username and password.

 

We'll use the username / passsord values from the conf.ini file to get an access token and start a session. The Authorization class is invoked when using the PricingPlan class. Hence, users interact primarily with the methods in the PricingPlan class for API interaction.

 

Authenticating to the Environment and Retrieving Access Token

In order to create a session, we must start by invoking the PricingPlan class. The class takes one value, the name of a profile from the conf.ini file. These profiles can be changed, but they have four important values as represented in the following code.

 

 

 

 

[PRICEOPTIMIZATION] # profile name
url: http://sasserver.sas.com/ # fully qualified SAS Viya URL
username: foo # SAS Viya username
password: bar # SAS Viya password

 

 

 

 

For example, if the conf.ini file contains a profile called ‘PRICEOPTIMIZATION’, we create a session using the configuration:

 

 

 

 

session = pricing_optimization.PricingPlan('PRICEOPTIMIZATION')

 

 

 

 

When we initialize a session, it will take the parameters from the conf.ini file.

  • The endpoint accessed is {url}/SASLogon/oauth/token
  • The payload is grant_type=password&username=foo&password=bar

 

Note: This is not the most secure means of storing user credentials. Check with your SAS administrator for authentication requirements.

 

Pricing Plan Creation

 

Pricing plan schema

To create a pricing plan, the method PricingPlan.create_plan() uses data following the schema below.

 

 

 

 

{
    "name": "str",
    "version": int,
    "startDate": "str",
    "endDate": "str",
    "product": {
        "members": ["str"],
        "hierarchy": int,
        "dimensionCode": "PRODUCT"
    },
    "location": {
        "members": ["str"],
        "hierarchy": int,
        "dimensionCode": "GEOGRAPHY"
    },
    "requests" : [    
        {
            "version": int,
            "requestName": "pricingPlanCreationRequestType",
            "userDefinedName": "str",
            "description": "str",
            "parameters": {
                "locationSplitLevelName": "str",
                "planVersions": [0,1],
                "productSplitLevelName": "str"
            }
        }                                                       
    ]
}

 

 

 

 

Included in the examples are helper functions that facilitate the creation of formatted JSON / Dictionary objects. The create_plan_schema() function is discussed in step 1.a.

 

Populate schema using schema_collection.py method

The PricingPlan.create_plan() method uses a JSON or Dictionary object directly. One may also employ the schema_collection.create_plan_schema() and create an object for use in the plan creation method. The parameters are as follows.

 

 

 

 

schema = {"name": planName,
          "version": planVersion,
          "startDate": startDate,
          "endDate": endDate,
          "product": {
              "members": prodID,
              "hierarchy": prodHierarchy,
              "dimensionCode": prodDimensionCode
          },
          "location": {
              "members": locID,
              "hierarchy": locHierarchy,
              "dimensionCode": locDimensionCode
          },
          "requests": [
              {
                  "version": 0,
                  "requestName": "pricingPlanCreationRequestType",
                  "userDefinedName": userDefinedName,
                  "description": description,
                  "parameters": {
                      "locationSplitLevelName": locSplitLevel,
                      "planVersions": [0, 1],
                      "productSplitLevelName": prodSplitLevel
                  }
              }
          ]
          }

 

 

 

 

The required variables for the create_plan_schema method are: version, startDate, endDate, product, location, locationSplitLevelName, and productSplitLevelName

 

The optional variables for the create_plan_schema method (and their default values) are: name, userDefinedName, and description.

 

Note: If a schema doesn’t meet formatting guidelines, an error will occur. However, even a properly formatted object may be rejected by the pricing solution if there are errors in the data provided. For example, it is impossible to create a pricing plan for an item category or geographic category that doesn’t exist.

 

Create pricing plans

If the data is valid, then the pricing solution creates pricing plans for the given parameters and will return a JSON of the pricing plan creation task. For example, this is a body sent to the pricing solution.

 

 

 

 

{
    "name": "Creating new plans",
    "version": 0,
    "startDate": "2019-04-08",
    "endDate": "2019-05-05",
    "product": {
        "members": ["T1_87"],
        "hierarchy": 1,
        "dimensionCode": "PRODUCT"
    },
    "location": {
        "members": ["T1_15-UK"],
        "hierarchy": 1,
        "dimensionCode": "GEOGRAPHY"
    },
    "requests": [
        {
            "version": 0,
            "requestName": "pricingPlanCreationRequestType",
            "userDefinedName": "plan creation",
            "description": "",
            "parameters": {
                "locationSplitLevelName": "Market",
                "planVersions": [
                    0,
                    1
                ],
                "productSplitLevelName": "Style"
            }
        }
    ]
}

 

 

 

 

And the response:

 

 

 

 

{
    "version": 0,
    "links": [
        {
            "method": "GET",
            "rel": "up",
            "href": "/retailAnalytics/creationRequestTypes",
            "uri": "/retailAnalytics/creationRequestTypes",
            "responseType": "application/vnd.sas.collection",
            "responseItemType": "application/vnd.sas.retail.creation.request.type.summary"
        },
        {
            "method": "POST",
            "rel": "jobs",
            "href": "/retailAnalytics/creationRequests/jobs",
            "uri": "/retailAnalytics/creationRequests/jobs",
            "type": "application/vnd.sas.retail.creation.request.job.detail",
            "responseType": "application/vnd.sas.retail.creation.request.job.detail"
        }
    ],
    "startDate": "2019-04-08",
    "endDate": "2019-05-05",
    "product": {
        "members": [
            "T1_87"
        ],
        "hierarchy": "1",
        "dimensionCode": "PRODUCT"
    },
    "location": {
        "members": [
            "T1_15-UK"
        ],
        "hierarchy": "1",
        "dimensionCode": "GEOGRAPHY"
    },
    "id": "39137cad-92bf-450c-811d-10cf2da60df8",
    "name": "Creating new plans",
    "requests": [
        {
            "version": 0,
            "links": [
                {
                    "method": "POST",
                    "rel": "up",
                    "href": "/retailAnalytics/creationRequests/jobs",
                    "uri": "/retailAnalytics/creationRequests/jobs",
                    "type": "application/vnd.sas.retail.creation.request.job.detail+json;charset=UTF-8",
                    "responseType": "application/vnd.sas.retail.creation.request.job.detail+json;charset=UTF-8"
                },
                {
                    "method": "GET",
                    "rel": "tasks",
                    "href": "/retailAnalytics/creationRequests/jobs/39137cad-92bf-450c-811d-10cf2da60df8/requests/cb124fdf-01d1-4cee-afdb-86f3495cb522/tasks",
                    "uri": "/retailAnalytics/creationRequests/jobs/39137cad-92bf-450c-811d-10cf2da60df8/requests/cb124fdf-01d1-4cee-afdb-86f3495cb522/tasks",
                    "responseType": "application/vnd.sas.collection",
                    "responseItemType": "application/vnd.sas.retail.creation.request.task.detail+json;charset=UTF-8"
                }
            ],
            "id": "cb124fdf-01d1-4cee-afdb-86f3495cb522",
            "requestName": "pricingPlanCreationRequestType",
            "userDefinedName": "plan creation",
            "parameters": {
                "locationSplitLevelName": "Market",
                "planVersions": [
                    0,
                    1
                ],
                "productSplitLevelName": "Style"
            },
            "state": "pending"
        }
    ]
}

 

 

 

 

State of the Task

In the create plan response, towards the bottom, we observe the state of the task is “pending.”


Checking the status of a creation tasks requires three values: jobID, requestID and taskID. The first two are provided in the initial response from a successful plan creation. Use these in a separate request to the pricing solution API to return the taskID.

 

We'll extract the ids by parsing the returned pricing plan creation JSON using jobID = object['id'] and requestID = object['requests'][0]['id'].

This JSON can be passed to the method PricingPlan.check_task_finished("creationRequests", JSON) which will take these IDs from the JSON.

 

Retrieving Task ID

Next, we invoke a helper method __get_task_id() and send a request to the pricing solution API using the jobID and the requestID. This returns  JSON for the check_task_finished() method from __get_task_id().

 

 

 

 

{
    "links": [
        {
            "method": "POST",
            "rel": "jobs",
            "href": "/retailAnalytics/executionRequests/jobs",
            "uri": "/retailAnalytics/executionRequests/jobs",
            "type": "application/vnd.sas.retail.execution.request.job.detail",
            "responseType": "application/vnd.sas.retail.execution.request.job.detail"
        }
    ],
    "name": "/tasks",
    "accept": "application/vnd.sas.collection+json",
    "count": 1,
    "items": [
        {
            "creationTimeStamp": "2022-06-28T12:47:08.000Z",
            "modifiedTimeStamp": "2022-06-28T12:47:08.000Z",
            "createdBy": "sastest1",
            "modifiedBy": "sastest1",
            "version": 1,
            "links": [
                {
                    "method": "GET",
                    "rel": "state",
                    "href": "/retailAnalytics/executionRequests/jobs/0d585303-ae41-47d1-8ebe-38cdff0fa1d2/requests/60dddf27-f373-4a46-b630-e38310f0cbdf/tasks/d6f12716-397c-42de-a427-53fb3969c996/state",
                    "uri": "/retailAnalytics/executionRequests/jobs/0d585303-ae41-47d1-8ebe-38cdff0fa1d2/requests/60dddf27-f373-4a46-b630-e38310f0cbdf/tasks/d6f12716-397c-42de-a427-53fb3969c996/state",
                    "responseType": "text"
                }
            ],
            "startDate": "2019-04-08",
            "endDate": "2019-05-05",
            "product": {
                "members": [
                    "T1_87"
                ],
                "hierarchy": "1",
                "dimensionCode": "PRODUCT"
            },
            "location": {
                "members": [
                    "T1_15-UK"
                ],
                "hierarchy": "1",
                "dimensionCode": "GEOGRAPHY"
            },
            "id": "d6f12716-397c-42de-a427-53fb3969c996",
            "taskName": "T1_87-T1_15-UK-task"
        }
    ],
    "version": 2
}

 

 

 

 

This will return a JSON containing the taskID, which we extract with taskID = response['items'][0]['id'].

 

Other Methods of Getting Task ID

You may have noticed a second option available. Under requests[0]['links'][0], the user is provided with a URI containing the jobID and requestID, the responseType and the responseItemType all together. Although my solution doesn't use these links, they are valid for use to create a valid request to our APIs.

 

Examining Status

Using the jobID, requestID, and taskID, we invoke the helper method __get_task_state(). We'll use these to call the task status API. If the jobID, requestID, and taskID are valid, then the pricing solution will return a string, either “running”, “completed”, or “failed” (in the case of a failed task).

 

Execution of the Pricing Plan

 

Execution Schema

To execute a plan, the method PricingPlan.create_execution() needs data. The data and fields are almost entirely identical to the plan creation method, save for two fields: requestName and paramters.

 

 

 

 

"requests": [
    {
        "requestName": "pricingPlanExecutionRequestType",
        ...
        "parameters": {"executionType": "OPTIMIZATION"}
    }

 

 

 

 

Included in the examples are helper functions that facilitate the creation of formatted JSON / Dictionary objects. The schema_collection.py method create_execution_schema() is discussed in the next section.

 

Filling a schema using schema_collection.py method

The PricingPlan.create_execution() method uses a JSON or Dictionary object directly. One may also employ the schema_collection.create_execution _schema() and create an object for use in the plan execution method.

 

 

 

 

schema = {
    "name": execName,
    "version": execVer,
    "startDate": startDate,
    "endDate": endDate,
    "product": {
        "members": prodMembers,
        "hierarchy": prodHierarchy,
        "dimensionCode": prodDimensionCode
    },
    "location": {
        "members": locMembers,
        "hierarchy": locHierarchy,
        "dimensionCode": locDimensionCode
    },
    "requests": [
        {
            "requestName": "pricingPlanExecutionRequestType",
            "userDefinedName": reqDefinedName,
            "description": description,
            "parameters": {"executionType": "OPTIMIZATION"}
        }
    ]
}

 

 

 

 

Executing Pricing Plans 

PricingPlan.create_execution() is given data formatted in the examples above. If the data is valid, then the pricing solution will begin executing pricing plans and subsequently aggregating the data for the given parameters. It will return a JSON including the execution plan creation task. For example, this is a body sent to the pricing solution.

 

 

 

 

{
    "name": "SAS_Test_1_T1_North",
    "version": 0,
    "startDate": "2019-04-08",
    "endDate": "2019-05-05",
    "product": {
        "members": ["T1_87"],
        "hierarchy": 1,
        "dimensionCode": "PRODUCT"
    },
    "location": {
        "members": ["T1_15-UK"],
        "hierarchy": 1,
        "dimensionCode": "GEOGRAPHY"
    },
    "requests": [
        {
            "requestName": "pricingPlanExecutionRequestType",
            "userDefinedName": "testExecution",
            "description": "",
            "parameters": {
                "executionType": "OPTIMIZATION"
            }
        }
    ]
}

 

 

 

 

And the response:

 

 

 

 

{
    "version": 0,
    "links": [
        {
            "method": "GET",
            "rel": "up",
            "href": "/retailAnalytics/executionRequestTypes",
            "uri": "/retailAnalytics/executionRequestTypes",
            "responseType": "application/vnd.sas.collection",
            "responseItemType": "application/vnd.sas.retail.execution.request.type.summary"
        },
        {
            "method": "POST",
            "rel": "jobs",
            "href": "/retailAnalytics/executionRequests/jobs",
            "uri": "/retailAnalytics/executionRequests/jobs",
            "type": "application/vnd.sas.retail.execution.request.job.detail",
            "responseType": "application/vnd.sas.retail.execution.request.job.detail"
        }
    ],
    "startDate": "2019-04-08",
    "endDate": "2019-05-05",
    "product": {
        "members": [
            "T1_87"
        ],
        "hierarchy": "1",
        "dimensionCode": "PRODUCT"
    },
    "location": {
        "members": [
            "T1_15-UK"
        ],
        "hierarchy": "1",
        "dimensionCode": "GEOGRAPHY"
    },
    "id": "c13a4948-faef-4a0c-8409-98d6924300e7",
    "name": "SAS_Test_1_T1_North",
    "requests": [
        {
            "version": 1,
            "links": [
                {
                    "method": "POST",
                    "rel": "up",
                    "href": "/retailAnalytics/executionRequests/jobs",
                    "uri": "/retailAnalytics/executionRequests/jobs",
                    "type": "application/vnd.sas.retail.execution.request.job.detail+json;charset=UTF-8",
                    "responseType": "application/vnd.sas.retail.execution.request.job.detail+json;charset=UTF-8"
                },
                {
                    "method": "GET",
                    "rel": "tasks",
                    "href": "/retailAnalytics/executionRequests/jobs/c13a4948-faef-4a0c-8409-98d6924300e7/requests/0a94add2-9c40-48cb-940d-2973f935a070/tasks",
                    "uri": "/retailAnalytics/executionRequests/jobs/c13a4948-faef-4a0c-8409-98d6924300e7/requests/0a94add2-9c40-48cb-940d-2973f935a070/tasks",
                    "responseType": "application/vnd.sas.collection",
                    "responseItemType": "application/vnd.sas.retail.execution.request.task.detail+json;charset=UTF-8"
                }
            ],
            "id": "0a94add2-9c40-48cb-940d-2973f935a070",
            "requestName": "pricingPlanExecutionRequestType",
            "userDefinedName": "testExecution",
            "parameters": {
                "executionType": "OPTIMIZATION"
            },
            "state": "pending"
        }
    ]
}

 

 

 

 

State of the Task

Similar to the creation task, checking the status of an execution task requires three values: jobID, requestID and taskID. The methods are identical and discussed more in-depth in the creation section. The only difference is that you will change the string you pass to the function PricingPlan.check_task_finished("executionRequests", JSON).

 

Extracting Data of the Pricing Plan

 

Extraction schema and types

Extraction schema are a little bit different compared to the previous schemas discussed.

 

 

 

 

{
    "name": {"str"},
    "tasks": [
        {
            "dataExtractName": {"str"},
            "userDefinedExtractName": {"str"},
            "format": {"str"},
            "locationType": {"str"},
            "location": {"str"},
            "parameters": {"object"}
        }
    ]
}

 

 

 

 

The type of extract performed by the API is determined by the field dataExtractName. Two types of extraction are currently supported: plan extract and scope extract. Like the names suggest, the plan extract focuses on extraction of a singular plan and the scope extract focuses on extraction based on a scope of parameters set in the schema.

 

The userDefinedExtractName field will determine the name of your file after extraction. The format field will determine the file type of your extract: csv, xml, or sasDataSet. The locationType will determine the extract location: filesystem or casLib. The location will determine the file path.

 

Create a schema using schema_collection.py method

The PricingPlan.create_data_exetract_job() method may be given a JSON or Dictionary object directly. One may also use the schema_collection.create_data_extract_schema() and create an object that can be used in the extraction method.

 

 

 

 

    schema = {
        "name": jobName,
        "tasks": [
            {
                "dataExtractName": jobType,
                "userDefinedExtractName": extractName,
                "format": fileType,
                "locationType": locType,
                "location": locToSave,
                "parameters": params
            }
        ]
    }

 

 

 

 

Note: The parameters field must be given a python dict.

 

Extraction

We provide the PricingPlan.create_data_extract_job() method data formatted as seen above. Depending on the type of extraction, the body of your request may look a little different. If the data is valid, then the pricing solution will begin extracting pricing plans based on either the name or scope. Here is an example of a scope extract body:

 

 

 

 

{
    "name": "Sample Scope Extract Job",
    "tasks": [
        {
            "dataExtractName": "scopeExtract",
            "userDefinedExtractName": "intern_presentation2",
            "format": "csv",
            "locationType": "fileSystem",
            "location": "SAS Content / Public / RetailPricing",
            "parameters": {
                "productName": "T1_Summer",
                "geographyName": "T1_North",
                "startDate": "2019-04-08",
                "endDate": "2019-05-05"
            }
        }
    ]
}

 

 

 

 

And the response:

 

 

 

 

{
    "version": 1,
    "links": [
        {
            "method": "GET",
            "rel": "up",
            "href": "/retailAnalytics/dataExtractTypes",
            "uri": "/retailAnalytics/dataExtractTypes",
            "responseType": "application/vnd.sas.collection",
            "responseItemType": "application/vnd.sas.retail.data.extract.type.summary"
        },
        {
            "method": "POST",
            "rel": "jobs",
            "href": "/retailAnalytics/dataExtracts/jobs",
            "uri": "/retailAnalytics/dataExtracts/jobs",
            "type": "application/vnd.sas.retail.data.extract.job.detail",
            "responseType": "application/vnd.sas.retail.data.extract.job.detail"
        }
    ],
    "id": "70bf5bb4-1f81-4d8e-b7e3-12c84f84fcc2",
    "name": "Sample Scope Extract Job",
    "tasks": [
        {
            "creationTimeStamp": "2022-07-20T15:58:07.000Z",
            "modifiedTimeStamp": "2022-07-20T15:58:07.000Z",
            "createdBy": "sasadm",
            "modifiedBy": "sasadm",
            "version": 1,
            "links": [
                {
                    "method": "POST",
                    "rel": "up",
                    "href": "/retailAnalytics/dataExtracts/jobs",
                    "uri": "/retailAnalytics/dataExtracts/jobs",
                    "type": "application/vnd.sas.retail.data.extract.job.detail",
                    "responseType": "application/vnd.sas.retail.data.extract.job.detail"
                },
                {
                    "method": "GET",
                    "rel": "state",
                    "href": "/retailAnalytics/dataExtracts/jobs/70bf5bb4-1f81-4d8e-b7e3-12c84f84fcc2/tasks/492faf98-e50a-4d1d-b7a6-d41bd21d96d6/state",
                    "uri": "/retailAnalytics/dataExtracts/jobs/70bf5bb4-1f81-4d8e-b7e3-12c84f84fcc2/tasks/492faf98-e50a-4d1d-b7a6-d41bd21d96d6/state",
                    "responseType": "text"
                }
            ],
            "id": "492faf98-e50a-4d1d-b7a6-d41bd21d96d6",
            "dataExtractName": "scopeExtract",
            "userDefinedExtractName": "intern_presentation2",
            "format": "csv",
            "locationType": "fileSystem",
            "location": "SAS Content / Public / RetailPricing",
            "state": "pending",
            "parameters": {
                "endDate": "2019-05-05T00:00:00.000Z",
                "productName": "T1_Summer",
                "startDate": "2019-04-08T00:00:00.000Z",
                "geographyName": "T1_North"
            }
        }
    ]
}

 

 

 

 

State of the Task

Unlike the creation and execution steps, a requestID is not necessary to determine the state of a task. We can see that in the "links" key, that the URI provided already provides us a with a task ID and a state path. Therefore, when PricingPlan.check_task_finished("dataExtracts", JSON) completes, it will call the helper method PricingPlan.__get_task_state_no_requestID() instead.

 

Lifecycles

 

The pricing plan is created with several scenarios using preset parameters. From these scenarios, a user can choose one to execute. However, users may want to alter the preset scenarios with their own rules. In order to edit these scenarios and re-execute them, we will first need their lifecycle IDs.

 

Getting Lifecycle IDs

In the PricingPlan class, there are two methods that will help us get lifecycle IDs. The first is PricingPlan.get_all_lifecycle_plans(). This method will return all lifecycle plans for all products that currently exist. It also has the option of querying, in that a query string can be passed to the method. Queries include:

  • productMemberCodes (Multiple codes supported, separated by commas)
  • locationMemberCodes (Multiple codes supported, separated by commas)
  • startDate (YYYY-MM-DD format)
  • endDate (YYYY-MM-DD format)
  • selectedPlanVersionNumbers
  • start (Index of the first lifecycle plan)
  • limit (number of plans returned, default is 100 in the API and 10000 in the method provided, unless a query is added)
  • filter (The criteria for filtering the lifecycle plans. Filterable fields are 'planVersion', 'planStatus', 'createdBy', 'modifiedBy'. Filtering is supported for the following operations - eq, ne, startsWith, endsWith, contains, ge, gt, le, lt, in, or, and filter.)
  • sortBy (Sortable fields are 'name', 'startDate', 'endDate')

Examples of query strings would be:

  • "?productMemberCodes=T1_40,T1_87&start=0&limit=100"
  • "?startDate=2019-04-08&endDate=2019-05-05&start=0&limit=10000"

 

A second method that simplifies the process focuses on the product IDs (productMemberCodes), PricingPlan.get_lifecycles_by_prod_ids(prodIDs). This method takes an array of product IDs and uses those products in a query.

 

Either method is fine for interacting with subsequent methods, but the latter is straightforward and used in the examples. We simply want to capture the JSON response of either method and from there, we can proceed with extracting the IDs.

 

So, if we were to pass a query from product T1_29 (Based on pricing plan created using the product ID T1_29 and the location ID T1_40, encompassing two areas) to the API, the response would be:

 

 

 

 

{
    "links": [
        {
            "method": "GET",
            "rel": "collection",
            "href": "/lifecyclePlans",
            "uri": "/lifecyclePlans",
            "type": "application/vnd.sas.collection",
            "itemType": "application/vnd.sas.summary"
        },
        {
            "method": "GET",
            "rel": "self",
            "href": "/lifecyclePlans?start=0&limit=10000",
            "uri": "/lifecyclePlans?start=0&limit=10000",
            "type": "application/vnd.sas.collection",
            "itemType": "application/vnd.sas.summary"
        }
    ],
    "name": "lifecyclePlan",
    "accept": "application/vnd.sas.summary+json",
    "start": 0,
    "count": 8,
    "items": [
        {
            "creationTimeStamp": "2022-07-19T19:27:17.000Z",
            "modifiedTimeStamp": "2022-07-19T19:32:28.370Z",
            "createdBy": "sasadm",
            "modifiedBy": "sasadm",
            "startDate": "2019-04-08",
            "endDate": "2019-05-05",
            "planVersionDisplayName": "MaxProfit",
            "planVersion": 2,
            "planStatus": "upToDate",
            "id": "57836a90-1ae2-4595-92a2-40df737c0976",
            "type": "lifecyclePlan",
            "name": "T1_Dairy_T1_CHARLOTTE-ROCK HILL METRO_2019-04-08_2019-05-05",
            "description": null,
            "version": 1,
            "links": [
                {
                    "method": "GET",
                    "rel": "self",
                    "href": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976",
                    "uri": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976",
                    "type": "application/vnd.sas.retail.pricing.plan"
                },
                {
                    "method": "PATCH",
                    "rel": "patch",
                    "href": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976",
                    "uri": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976",
                    "type": "application/vnd.sas.retail.pricing.plan"
                },
                {
                    "method": "DELETE",
                    "rel": "delete",
                    "href": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976",
                    "uri": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976"
                },
                {
                    "method": "GET",
                    "rel": "rules",
                    "href": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976/rules",
                    "uri": "/retailPricing/lifecyclePlans/57836a90-1ae2-4595-92a2-40df737c0976/rules",
                    "type": "application/vnd.sas.retail.pricing.lifecycle.plan.rule"
                }
            ]
        },
    {Several more objects...},
    ],
    "limit": 10000,
    "version": 2
}

 

 

 

 

Extracting Lifecycle IDs from the JSON

After successfully receiving a response from either method mentioned in the [Getting Lifecycle IDs] section, we will pass the response to the PricingPlan.get_plan_lifecycle_ids(JSON) method.

 

This method returns a sorted list of lists. Each nested list contains four values. They are:

  • planVersion [Index 0]
  • planVersionDisplayName [Index 1]
  • id [Index 2]
  • modifiedTimeStamp [Index 3]

 

Types of Lifecycle Rules

There are several different types of lifecycle rules. These rules each have their own schemas associated with them. They are:

  • Numeric Rules
  • Boolean Rules
  • Available Date Rules
  • Date Rules
  • Price-Grid Rules
  • Price-Ending Rules

 

Numeric Rules

 

 

 

 

PricingPlan.get_lifecycle_numeric_rules(lifecycleID: str)

 

 

 

 

This method returns all of the numeric rules of associated with the lifecycle ID and their values.

 

 

 

 

PricingPlan.update_lifecycle_numeric_rule(lifecycle: list, newValue, targetRule)

 

 

 

 

This method takes a lifecycle list, mentioned in [Extracting Lifecycle IDs from the JSON], a newValue integer, and the ID of the rule that will be updated. The method will take the values provided and format the data.

 

 

 

 

data = {
            "value": str(newValue),
            "id": str(targetRule)
        }

 

 

 

 

Note: This method does not support batch rule puts.

 

Boolean Rules

 

 

 

 

PricingPlan.get_lifecycle_bool_rules(lifecycleID: str)

 

 

 

 

This method returns all of the Boolean rules of associated with the lifecycle ID and their values.

 

 

 

 

PricingPlan.update_lifecycle_bool_rules(lifecycle: list, data)

 

 

 

 

This method takes a lifecycle list, mentioned in [Extracting Lifecycle IDs from the JSON] and data. The data should contain a list with dictionary objects. The dictionary objects should have two key-value pairs.

  • "value"
  • "id"

This is an example of data passed to the method.

 

 

 

 

bool_data = [
    {
        "value": True,
        "id": 219
    },
    {
        "value": True,
        "id": 506
    }
]

 

 

 

 

Note: This method supports batch puts.

 

Available Date Rules

 

 

 

 

PricingPlan.get_lifecycle_available_date_rules(lifecycleID: str)

 

 

 

 

This method returns all of the available date rules of associated with the lifecycle ID and their values.

 

 

 

 

PricingPlan.update_lifecycle_available_date_rules(lifecycle: list, data)

 

 

 

 

This method takes a lifecycle list, mentioned in [Extracting Lifecycle IDs from the JSON] and data. The data should contain a list with dictionary objects. The dictionary objects should have two key-value pairs.

  • "value"
  • "availableDates"

"availableDates" should contain a list of dictionaries, each containing the key-value pair "startDate" and "endDate". This is an example of data passed to the method.

 

 

 

 

dates_data = [
  {
    "id": 701,
    "availableDates": [
        {
            "startDate": "2019-04-08",
            "endDate": "2019-04-14"
        },
        {
            "startDate": "2019-04-15",
            "endDate": "2019-04-21"
        }   
    ]
  },
  {
    "id": 1101,
    "availableDates": [
        {
            "startDate": "2019-04-22",
            "endDate": "2019-04-28"
        },
        {
            "startDate": "2019-04-29",
            "endDate": "2019-05-05"
        }   
    ]
  }
]

 

 

 

 

Note: This method supports batch puts

 

Date Rules

 

 

 

 

PricingPlan.get_lifecycle_date_rules(lifecycleID: str)

 

 

 

 

This method returns all of the date rules of associated with the lifecycle ID and their values.

 

 

 

 

PricingPlan.update_lifecycle_date_rules(lifecycle: list, data)

 

 

 

 

This method takes a lifecycle list, mentioned in [Extracting Lifecycle IDs from the JSON] and data. The data should contain a list with dictionary objects. The dictionary objects should have four key-value pairs.

  • "id"
  • "name"
  • "description"
  • "value"

This is an example of data passed to the method.

 

 

 

 

date_data = [
  {
      "id": "804",
      "name": "Promotion Cycle Begin Date",
      "description": "Date that indicates when the products are starting the promotion cycle of the pricing cadence",
      "value": "2019-04-10"
  },
  {
      "id": "803",
      "name": "Markdown Cycle Begin Date",
      "description": "Date that indicates when the products are starting the markdown cycle of the pricing cadence",
      "value": "2019-04-15"
   },
    {
        "id": "805",
        "name": "Markdown Cycle end Date",
        "description": "Date that indicates when the products are ending the promotion cycle of the pricing cadence",
        "value": "2019-05-02"
   }
]

 

 

 

 

Note: This method supports batch puts.

 

Price-Grid Rules

 

 

 

 

PricingPlan.get_lifecycle_price_grid_rules(lifecycleID: str)

 

 

 

 

This method returns all of the price-grid rules of associated with the lifecycle ID and their values.

 

 

 

 

Pricingplan.update_lifecycle_price_grid_rule(lifecycle: list, data, targetRule)

 

 

 

 

This method takes a lifecycle list, mentioned in [Extracting Lifecycle IDs from the JSON], formatted data, and the ID of the rule that will be updated. The data should contain a dictionary list with four key-value pairs.

  • "id"
  • "name"
  • "description"
  • "priceGrid"

"priceGrid" should be formatted as a list containing an dictionary with the three key-value pairs.

  • "startRange"
  • "endRange" (-1.0 is no limit)
  • "values"

 

 

 

 

schema_collection.create_price_grid_schema(priceGridRule, startRange: float, endRange: float, gridValues: list, priceGridName: str = "new_price_grid", description: str = "new price grid")

 

 

 

 

Properly formatted data can be created by passing a list of price-grid values to a schema_collection.py method.

 

 

 

 

priceGridValues = [20.0, 35.0, 50.0, 75.0, 90.0]
data = schema_collection.create_price_grid_schema(301, 0.0, -1.0, priceGridValues)
data = {
    "id": 301,
    "name": "new_price_grid",
    "description": "new price grid",
    "priceGrid": [
        {
            "startRange": 0.0,
            "endRange": -1.0,
            "values": [
                20.0,
                35.0,
                50.0,
                75.0,
                90.0
            ]
        }
    ]
}

 

 

 

 

Note: This method does not support batch puts.

 

Price-Ending Rules

 

 

 

 

PricingPlan.get_lifecycle_price_ending_rules(lifecycleID: str)

 

 

 

 

This method returns all of the price-ending rules of associated with the lifecycle ID and their values.

 

 

 

 

PricingPlan.update_lifecycle_price_ending_rule(lifecycle: list, data, targetRule)

 

 

 

 

This method takes a lifecycle list, mentioned in [Extracting Lifecycle IDs from the JSON], formatted data, and the ID of the rule that will be updated. The data should contain a dictionary list with four key-value pairs.

  • "id"
  • "name"
  • "description"
  • "priceEndings"

"priceEndings" should contain a list of dictionaries, these dictionaries should contain three key-value pairs.

  • "startRange"
  • "endRange"
  • "endings"

"endings" should contain a list of dictionaries, these dictionaries should contain two key-value pairs.

  • "digitPlace"
  • "validDigits"

This is an example of data passed to the method.

 

 

 

 

price_endings_data =     {
        "id": "402",
        "name": "Markdown Percent-Off Price Ending",
        "description": "Defines price endings to be used with percentage depth of discount.  The solution will apply the ending price from the grid based on the discount depth",
        "priceEndings": [
        {
            "startRange": 0,
            "endRange": 50,
            "endings": [
            {
                "digitPlace": 0,
                "validDigits": [1,3,5,7]
            },
            {
                "digitPlace": 1,
                "validDigits": [2]
            }
            ]
        },
        {
            "startRange": 50.001,
            "endRange": 99,
            "endings": [
            {
                "digitPlace": 0,
                "validDigits": [2,4,6,8]
            },
            {
                "digitPlace": 1,
                "validDigits": [3]
            }
            ]
        }
    ]
}

 

 

 

 

Note: This method does not support batch puts.

 

Re-Executing Plans

After the desired lifecycle plans have been edited, plans must be re-executed to see the effects.

 

Closing it out

Whew, I know that was a lot. If you made it all the way here, congratulations. Hopefully, the use cases made sense and the code it easy to follow. If you have questions, feel free to add a comment. Thanks for reading!

 

Version history
Last update:
‎08-26-2022 02:23 PM
Updated by:

sas-innovate-white.png

Register Today!

Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9.

 

Early bird rate extended! Save $200 when you sign up by March 31.

Register now!

Free course: Data Literacy Essentials

Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning  and boost your career prospects.

Get Started

Article Labels
Article Tags