Background

This is a simple project in R demonstrating how to calculate the average customer lifetime value (CLV) for an example Amazon business using Amazon-provided business reports.

CLV is a useful metric that lets a business estimate the average amount that a customer spends on the business over the course of time. Some businesses have products or business models with strong customer repurchase rates (think for example a supplement company or an apparel company with strong brand loyalty), whereas others will have a less frequent relationship with their customers. Knowing the lifetime value of a customer could help a business decide whether the cost of acquiring a new customer justifies its current marketing spend. It could also help the business create particular financial projections or serve as a metric for customer loyalty.

Set up initial libraries:

library(tidyverse)
library(tidyr)
library(dplyr)
library(janitor)
library(readr)

Obtain the relevant reports:

The appropriate sales report needed for this project can be obtained from one’s Amazon Seller Central account: Reports –> Fulfillment –> Amazon Fulfilled Shipments

Download reports covering at least 1 year of sales. While the project below uses ~1.5 years of reports (the maximum available), it may be easier to stick to 1 year of sales data for ease in interpreting the CLV number.

For our project, we obtained reports from 12/12/2020 - 5/31/2022. Reports were generated for 1 month intervals. There were 18 final output files, and each file was given a date-descriptive name such as: 6_16_21_to_7_16_21.csv 7_17_21_to_8_16_21.csv 8_17_21_to_9_16_21.csv

Each file contain the following columns: Amazon Order Id Merchant Order Id Shipment ID Shipment Item Id Amazon Order Item Id Merchant Order Item Id Purchase Date Payments Date Shipment Date Reporting Date Buyer Email Buyer Name Buyer Phone Number Merchant SKU Title Shipped Quantity Currency Item Price Item Tax Shipping Price Shipping Tax Gift Wrap Price Gift Wrap Tax Ship Service Level Recipient Name Shipping Address 1 Shipping Address 2 Shipping Address 3 Shipping City Shipping State Shipping Postal Code Shipping Country Code Shipping Phone Number Billing Address 1 Billing Address 2 Billing Address 3 Billing City Billing State bill-postal-code bill-country Item Promo Discount Shipment Promo Discount Carrier Tracking Number Estimated Arrival Date FC Fulfillment Channel Sales Channel

As of this post (2022), several of the columns are no longer populated with data by Amazon out of concerns for customer privacy, including buyer name, phone number and address.

The 18 CSV files are joined together under a variable named df:

temp <- list.files(pattern="*.csv")
myfiles <- lapply(temp, read_csv)
df <- bind_rows(myfiles)

We have a table that includes 60,030 entries and 48 columns.

Clean the titles to make them easier to work with:

df <- clean_names(df)

View(df)

Export the joined single file for convenient use in the future (change the path to one of your own choosing):

write_csv(df, "/Users/YourName/Desktop/FileName.csv")

Converting item_price column to a single currency:

The Amazon seller account we are working with sells to customers in the US, Canada and Mexico, and therefore the order amounts are given in USD, MXN and CAD, which can confuse our summation calculations unless they are converted to a single currency.

We first confirm that only USD, MXN and CAD are represented in the dataset:

df %>% filter(currency !="CAD" & currency !="MXN" & currency !="USD")

Let’s use the case_when and round functions to convert our values into a USD value, which will be put in a new column in a new dataframe we will call new_df. Note that the conversion values given below reflect the rates at the time of writing this. Search online for the latest conversion values.

new_df <- df %>% 
  mutate(item_price_USD = case_when(
    df$currency == "USD" ~ df$item_price,
    df$currency == "CAD" ~ round(df$item_price * 0.794585, digits = 2),
    df$currency == "MXN" ~ round(df$item_price * 0.051111, digits = 2)))
View(new_df)

Find a unique identifier for customers in the reports:

As noted above, several columns are no longer populated with data by Amazon out of concerns for customer privacy, including buyer name, phone number and address. Without this unique identifier information, we will need to be creative to determine each unique customer. We can do so by referring to the buyer email address, an Amazon-generated email address that is not the actual buyer’s email address, but an Amazon-server-tied email address that appears to be consistent across orders when the same user purchases.

To verify, try searching a few of the provided Amazon customer email in our table to see if they are used for more than one order. This would indicate that the emails are linked to a unique customers, and not created for each order. When you identify an email that is linked to more than one order, you can verify it is the same customer because the customer’s address city and zip code will match, this address information being the limited customer-specific data that the Amazon report DOES provide.

To test, try a few emails from the table and replace them with the the email in this code snippet:

Let’s identify the number of unique buyers over the time period of our data (1.5 years) by counting the unique email addresses:

unique_buyers <- length(unique(new_df$buyer_email))
unique_buyers
[1] 37796

We have been able to determine that there are 37796 unique customers represented in our table.

Find the total sales over this time period by summing the item_price_USD column:

total_sales <- sum(new_df$item_price_USD)
total_sales
[1] 876787.1

Over this time period our example Amazon account has sold 876,787.10 worth of products.

Because data in our report is given on each item sold and not each order, let us derive the number of unique orders over this period:

unique_orders <- length(unique(new_df$amazon_order_id))
unique_orders
[1] 47788

We find that our example account had made 47788 unique orders during the period of 1.5 years.

Determining the average order value:

avg_order_value <- round(total_sales/unique_orders, digits=2)
avg_order_value
[1] 18.35

We find that the average order value for this account is $18.35.

Calculating the average order frequency for our customers (total orders / unique customers):

order_freq <- round(unique_orders/unique_buyers, digits=2)
order_freq
[1] 1.26

We find that the customers of this Amazon business have an average order frequency of 1.26 orders over the period of 1.5 years.

Calculating the average customer value (average order value / order frequency):

avg_cust_value = round(avg_order_value*order_freq, digit=2)
avg_cust_value
[1] 23.12

The average customer value for the given data is 23.12, indicating that this is the amount we expect our average customer to spend over a period of 1.5 years. If data for only 1 year was selected, then this figure represents the customer’s value over just one year. To determine the lifetime value of our customer, we make a reasonable estimate given our knowledge of the business that the average customer engages with this business for 3 years. That would make the total average lifetime customer value equal to $46.24. Our estimate of 3 years will vary from business to business, and business stakeholders will be in the best position to make this determination

LS0tCnRpdGxlOiAiQ3VzdG9tZXIgTGlmZXRpbWUgVmFsdWUgZnJvbSBBbWF6b24gU2VsbGVyIERhdGEgKEEgUHJvamVjdCBpbiBSKSIKYXV0aG9yOiAiRiBBYmR1bGxhaCIKZGF0ZTogJzIwMjItMDYtMDYnCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIEJhY2tncm91bmQKClRoaXMgaXMgYSBzaW1wbGUgcHJvamVjdCBpbiBSIGRlbW9uc3RyYXRpbmcgaG93IHRvIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBjdXN0b21lciBsaWZldGltZSB2YWx1ZSAoQ0xWKSBmb3IgYW4gZXhhbXBsZSBBbWF6b24gYnVzaW5lc3MgdXNpbmcgQW1hem9uLXByb3ZpZGVkIGJ1c2luZXNzIHJlcG9ydHMuCgpDTFYgaXMgYSB1c2VmdWwgbWV0cmljIHRoYXQgbGV0cyBhIGJ1c2luZXNzIGVzdGltYXRlIHRoZSBhdmVyYWdlIGFtb3VudCB0aGF0IGEgY3VzdG9tZXIgc3BlbmRzIG9uIHRoZSBidXNpbmVzcyBvdmVyIHRoZSBjb3Vyc2Ugb2YgdGltZS4gU29tZSBidXNpbmVzc2VzIGhhdmUgcHJvZHVjdHMgb3IgYnVzaW5lc3MgbW9kZWxzIHdpdGggc3Ryb25nIGN1c3RvbWVyIHJlcHVyY2hhc2UgcmF0ZXMgKHRoaW5rIGZvciBleGFtcGxlIGEgc3VwcGxlbWVudCBjb21wYW55IG9yIGFuIGFwcGFyZWwgY29tcGFueSB3aXRoIHN0cm9uZyBicmFuZCBsb3lhbHR5KSwgd2hlcmVhcyBvdGhlcnMgd2lsbCBoYXZlIGEgbGVzcyBmcmVxdWVudCByZWxhdGlvbnNoaXAgd2l0aCB0aGVpciBjdXN0b21lcnMuIEtub3dpbmcgdGhlIGxpZmV0aW1lIHZhbHVlIG9mIGEgY3VzdG9tZXIgY291bGQgaGVscCBhIGJ1c2luZXNzIGRlY2lkZSB3aGV0aGVyIHRoZSBjb3N0IG9mIGFjcXVpcmluZyBhIG5ldyBjdXN0b21lciBqdXN0aWZpZXMgaXRzIGN1cnJlbnQgbWFya2V0aW5nIHNwZW5kLiBJdCBjb3VsZCBhbHNvIGhlbHAgdGhlIGJ1c2luZXNzIGNyZWF0ZSBwYXJ0aWN1bGFyIGZpbmFuY2lhbCBwcm9qZWN0aW9ucyBvciBzZXJ2ZSBhcyBhIG1ldHJpYyBmb3IgY3VzdG9tZXIgbG95YWx0eS4KCiMjIyMgU2V0IHVwIGluaXRpYWwgbGlicmFyaWVzOgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoamFuaXRvcikKbGlicmFyeShyZWFkcikKYGBgCgojIyMjIE9idGFpbiB0aGUgcmVsZXZhbnQgcmVwb3J0czoKVGhlIGFwcHJvcHJpYXRlIHNhbGVzIHJlcG9ydCBuZWVkZWQgZm9yIHRoaXMgcHJvamVjdCBjYW4gYmUgb2J0YWluZWQgZnJvbSBvbmUncyBBbWF6b24gU2VsbGVyIENlbnRyYWwgYWNjb3VudDoKUmVwb3J0cyAtLT4gRnVsZmlsbG1lbnQgLS0+IEFtYXpvbiBGdWxmaWxsZWQgU2hpcG1lbnRzCgpEb3dubG9hZCByZXBvcnRzIGNvdmVyaW5nIGF0IGxlYXN0IDEgeWVhciBvZiBzYWxlcy4gV2hpbGUgdGhlIHByb2plY3QgYmVsb3cgdXNlcyB+MS41IHllYXJzIG9mIHJlcG9ydHMgKHRoZSBtYXhpbXVtIGF2YWlsYWJsZSksIGl0IG1heSBiZSBlYXNpZXIgdG8gc3RpY2sgdG8gMSB5ZWFyIG9mIHNhbGVzIGRhdGEgZm9yIGVhc2UgaW4gaW50ZXJwcmV0aW5nIHRoZSBDTFYgbnVtYmVyLgoKRm9yIG91ciBwcm9qZWN0LCB3ZSBvYnRhaW5lZCByZXBvcnRzIGZyb20gMTIvMTIvMjAyMCAtIDUvMzEvMjAyMi4gUmVwb3J0cyB3ZXJlIGdlbmVyYXRlZCBmb3IgMSBtb250aCBpbnRlcnZhbHMuIFRoZXJlIHdlcmUgMTggZmluYWwgb3V0cHV0IGZpbGVzLCBhbmQgZWFjaCBmaWxlIHdhcyBnaXZlbiBhIGRhdGUtZGVzY3JpcHRpdmUgbmFtZSBzdWNoIGFzOgo2XzE2XzIxX3RvXzdfMTZfMjEuY3N2CjdfMTdfMjFfdG9fOF8xNl8yMS5jc3YKOF8xN18yMV90b185XzE2XzIxLmNzdgoKRWFjaCBmaWxlIGNvbnRhaW4gdGhlIGZvbGxvd2luZyBjb2x1bW5zOgpBbWF6b24gT3JkZXIgSWQKTWVyY2hhbnQgT3JkZXIgSWQKU2hpcG1lbnQgSUQKU2hpcG1lbnQgSXRlbSBJZApBbWF6b24gT3JkZXIgSXRlbSBJZApNZXJjaGFudCBPcmRlciBJdGVtIElkClB1cmNoYXNlIERhdGUKUGF5bWVudHMgRGF0ZQpTaGlwbWVudCBEYXRlClJlcG9ydGluZyBEYXRlCkJ1eWVyIEVtYWlsCkJ1eWVyIE5hbWUKQnV5ZXIgUGhvbmUgTnVtYmVyCk1lcmNoYW50IFNLVQpUaXRsZQpTaGlwcGVkIFF1YW50aXR5CkN1cnJlbmN5Ckl0ZW0gUHJpY2UKSXRlbSBUYXgKU2hpcHBpbmcgUHJpY2UKU2hpcHBpbmcgVGF4CkdpZnQgV3JhcCBQcmljZQpHaWZ0IFdyYXAgVGF4ClNoaXAgU2VydmljZSBMZXZlbApSZWNpcGllbnQgTmFtZQpTaGlwcGluZyBBZGRyZXNzIDEKU2hpcHBpbmcgQWRkcmVzcyAyClNoaXBwaW5nIEFkZHJlc3MgMwpTaGlwcGluZyBDaXR5ClNoaXBwaW5nIFN0YXRlClNoaXBwaW5nIFBvc3RhbCBDb2RlClNoaXBwaW5nIENvdW50cnkgQ29kZQpTaGlwcGluZyBQaG9uZSBOdW1iZXIKQmlsbGluZyBBZGRyZXNzIDEKQmlsbGluZyBBZGRyZXNzIDIKQmlsbGluZyBBZGRyZXNzIDMKQmlsbGluZyBDaXR5CkJpbGxpbmcgU3RhdGUKYmlsbC1wb3N0YWwtY29kZQpiaWxsLWNvdW50cnkKSXRlbSBQcm9tbyBEaXNjb3VudApTaGlwbWVudCBQcm9tbyBEaXNjb3VudApDYXJyaWVyClRyYWNraW5nIE51bWJlcgpFc3RpbWF0ZWQgQXJyaXZhbCBEYXRlCkZDCkZ1bGZpbGxtZW50IENoYW5uZWwKU2FsZXMgQ2hhbm5lbAoKQXMgb2YgdGhpcyBwb3N0ICgyMDIyKSwgc2V2ZXJhbCBvZiB0aGUgY29sdW1ucyBhcmUgbm8gbG9uZ2VyIHBvcHVsYXRlZCB3aXRoIGRhdGEgYnkgQW1hem9uIG91dCBvZiBjb25jZXJucyBmb3IgY3VzdG9tZXIgcHJpdmFjeSwgaW5jbHVkaW5nIGJ1eWVyIG5hbWUsIHBob25lIG51bWJlciBhbmQgYWRkcmVzcy4KClRoZSAxOCBDU1YgZmlsZXMgYXJlIGpvaW5lZCB0b2dldGhlciB1bmRlciBhIHZhcmlhYmxlIG5hbWVkIGRmOgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGVtcCA8LSBsaXN0LmZpbGVzKHBhdHRlcm49IiouY3N2IikKbXlmaWxlcyA8LSBsYXBwbHkodGVtcCwgcmVhZF9jc3YpCmRmIDwtIGJpbmRfcm93cyhteWZpbGVzKQpgYGAKCldlIGhhdmUgYSB0YWJsZSB0aGF0IGluY2x1ZGVzIDYwLDAzMCBlbnRyaWVzIGFuZCA0OCBjb2x1bW5zLgoKIyMjIyBDbGVhbiB0aGUgdGl0bGVzIHRvIG1ha2UgdGhlbSBlYXNpZXIgdG8gd29yayB3aXRoOgoKYGBge3J9CmRmIDwtIGNsZWFuX25hbWVzKGRmKQoKVmlldyhkZikKYGBgCgojIyMjIEV4cG9ydCB0aGUgam9pbmVkIHNpbmdsZSBmaWxlIGZvciBjb252ZW5pZW50IHVzZSBpbiB0aGUgZnV0dXJlIChjaGFuZ2UgdGhlIHBhdGggdG8gb25lIG9mIHlvdXIgb3duIGNob29zaW5nKToKYGBge3J9CndyaXRlX2NzdihkZiwgIi9Vc2Vycy9Zb3VyTmFtZS9EZXNrdG9wL0ZpbGVOYW1lLmNzdiIpCmBgYAoKIyMjIyBDb252ZXJ0aW5nIGl0ZW1fcHJpY2UgY29sdW1uIHRvIGEgc2luZ2xlIGN1cnJlbmN5OgpUaGUgQW1hem9uIHNlbGxlciBhY2NvdW50IHdlIGFyZSB3b3JraW5nIHdpdGggc2VsbHMgdG8gY3VzdG9tZXJzIGluIHRoZSBVUywgQ2FuYWRhIGFuZCBNZXhpY28sIGFuZCB0aGVyZWZvcmUgdGhlIG9yZGVyIGFtb3VudHMgYXJlIGdpdmVuIGluIFVTRCwgTVhOIGFuZCBDQUQsIHdoaWNoIGNhbiBjb25mdXNlIG91ciBzdW1tYXRpb24gY2FsY3VsYXRpb25zIHVubGVzcyB0aGV5IGFyZSBjb252ZXJ0ZWQgdG8gYSBzaW5nbGUgY3VycmVuY3kuCgpXZSBmaXJzdCBjb25maXJtIHRoYXQgb25seSBVU0QsIE1YTiBhbmQgQ0FEIGFyZSByZXByZXNlbnRlZCBpbiB0aGUgZGF0YXNldDoKYGBge3J9CmRmICU+JSBmaWx0ZXIoY3VycmVuY3kgIT0iQ0FEIiAmIGN1cnJlbmN5ICE9Ik1YTiIgJiBjdXJyZW5jeSAhPSJVU0QiKQpgYGAKCkxldCdzIHVzZSB0aGUgY2FzZV93aGVuIGFuZCByb3VuZCBmdW5jdGlvbnMgdG8gY29udmVydCBvdXIgdmFsdWVzIGludG8gYSBVU0QgdmFsdWUsIHdoaWNoIHdpbGwgYmUgcHV0IGluIGEgbmV3IGNvbHVtbiBpbiBhIG5ldyBkYXRhZnJhbWUgd2Ugd2lsbCBjYWxsIG5ld19kZi4gTm90ZSB0aGF0IHRoZSBjb252ZXJzaW9uIHZhbHVlcyBnaXZlbiBiZWxvdyByZWZsZWN0IHRoZSByYXRlcyBhdCB0aGUgdGltZSBvZiB3cml0aW5nIHRoaXMuIFNlYXJjaCBvbmxpbmUgZm9yIHRoZSBsYXRlc3QgY29udmVyc2lvbiB2YWx1ZXMuCgpgYGB7cn0KbmV3X2RmIDwtIGRmICU+JSAKICBtdXRhdGUoaXRlbV9wcmljZV9VU0QgPSBjYXNlX3doZW4oCiAgICBkZiRjdXJyZW5jeSA9PSAiVVNEIiB+IGRmJGl0ZW1fcHJpY2UsCiAgICBkZiRjdXJyZW5jeSA9PSAiQ0FEIiB+IHJvdW5kKGRmJGl0ZW1fcHJpY2UgKiAwLjc5NDU4NSwgZGlnaXRzID0gMiksCiAgICBkZiRjdXJyZW5jeSA9PSAiTVhOIiB+IHJvdW5kKGRmJGl0ZW1fcHJpY2UgKiAwLjA1MTExMSwgZGlnaXRzID0gMikpKQpWaWV3KG5ld19kZikKYGBgCgojIyMjIEZpbmQgYSB1bmlxdWUgaWRlbnRpZmllciBmb3IgY3VzdG9tZXJzIGluIHRoZSByZXBvcnRzOgpBcyBub3RlZCBhYm92ZSwgc2V2ZXJhbCBjb2x1bW5zIGFyZSBubyBsb25nZXIgcG9wdWxhdGVkIHdpdGggZGF0YSBieSBBbWF6b24gb3V0IG9mIGNvbmNlcm5zIGZvciBjdXN0b21lciBwcml2YWN5LCBpbmNsdWRpbmcgYnV5ZXIgbmFtZSwgcGhvbmUgbnVtYmVyIGFuZCBhZGRyZXNzLiBXaXRob3V0IHRoaXMgdW5pcXVlIGlkZW50aWZpZXIgaW5mb3JtYXRpb24sIHdlIHdpbGwgbmVlZCB0byBiZSBjcmVhdGl2ZSB0byBkZXRlcm1pbmUgZWFjaCB1bmlxdWUgY3VzdG9tZXIuIFdlIGNhbiBkbyBzbyBieSByZWZlcnJpbmcgdG8gdGhlIGJ1eWVyIGVtYWlsIGFkZHJlc3MsIGFuIEFtYXpvbi1nZW5lcmF0ZWQgZW1haWwgYWRkcmVzcyB0aGF0IGlzIG5vdCB0aGUgYWN0dWFsIGJ1eWVyJ3MgZW1haWwgYWRkcmVzcywgYnV0IGFuIEFtYXpvbi1zZXJ2ZXItdGllZCBlbWFpbCBhZGRyZXNzIHRoYXQgYXBwZWFycyB0byBiZSBjb25zaXN0ZW50IGFjcm9zcyBvcmRlcnMgd2hlbiB0aGUgc2FtZSB1c2VyIHB1cmNoYXNlcy4gCgpUbyB2ZXJpZnksIHRyeSBzZWFyY2hpbmcgYSBmZXcgb2YgdGhlIHByb3ZpZGVkIEFtYXpvbiBjdXN0b21lciBlbWFpbCBpbiBvdXIgdGFibGUgdG8gc2VlIGlmIHRoZXkgYXJlIHVzZWQgZm9yIG1vcmUgdGhhbiBvbmUgb3JkZXIuIFRoaXMgd291bGQgaW5kaWNhdGUgdGhhdCB0aGUgZW1haWxzIGFyZSBsaW5rZWQgdG8gYSB1bmlxdWUgY3VzdG9tZXJzLCBhbmQgbm90IGNyZWF0ZWQgZm9yIGVhY2ggb3JkZXIuIFdoZW4geW91IGlkZW50aWZ5IGFuIGVtYWlsIHRoYXQgaXMgbGlua2VkIHRvIG1vcmUgdGhhbiBvbmUgb3JkZXIsIHlvdSBjYW4gdmVyaWZ5IGl0IGlzIHRoZSBzYW1lIGN1c3RvbWVyIGJlY2F1c2UgdGhlIGN1c3RvbWVyJ3MgYWRkcmVzcyBjaXR5IGFuZCB6aXAgY29kZSB3aWxsIG1hdGNoLCB0aGlzIGFkZHJlc3MgaW5mb3JtYXRpb24gYmVpbmcgdGhlIGxpbWl0ZWQgY3VzdG9tZXItc3BlY2lmaWMgZGF0YSB0aGF0IHRoZSBBbWF6b24gcmVwb3J0IERPRVMgcHJvdmlkZS4KClRvIHRlc3QsIHRyeSBhIGZldyBlbWFpbHMgZnJvbSB0aGUgdGFibGUgYW5kIHJlcGxhY2UgdGhlbSB3aXRoIHRoZSB0aGUgZW1haWwgaW4gdGhpcyBjb2RlIHNuaXBwZXQ6CmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmRmICU+JSBmaWx0ZXIoYnV5ZXJfZW1haWw9PSJhYmNkMWFiY2RlNWFiY2RAbWFya2V0cGxhY2UuYW1hem9uLmNvbSIpCmBgYAoKIyMjIyBMZXQncyBpZGVudGlmeSB0aGUgbnVtYmVyIG9mIHVuaXF1ZSBidXllcnMgb3ZlciB0aGUgdGltZSBwZXJpb2Qgb2Ygb3VyIGRhdGEgKDEuNSB5ZWFycykgYnkgY291bnRpbmcgdGhlIHVuaXF1ZSBlbWFpbCBhZGRyZXNzZXM6CmBgYHtyfQp1bmlxdWVfYnV5ZXJzIDwtIGxlbmd0aCh1bmlxdWUobmV3X2RmJGJ1eWVyX2VtYWlsKSkKdW5pcXVlX2J1eWVycwpgYGAKCldlIGhhdmUgYmVlbiBhYmxlIHRvIGRldGVybWluZSB0aGF0IHRoZXJlIGFyZSAzNzc5NiB1bmlxdWUgY3VzdG9tZXJzIHJlcHJlc2VudGVkIGluIG91ciB0YWJsZS4KCiMjIyMgRmluZCB0aGUgdG90YWwgc2FsZXMgb3ZlciB0aGlzIHRpbWUgcGVyaW9kIGJ5IHN1bW1pbmcgdGhlIGl0ZW1fcHJpY2VfVVNEIGNvbHVtbjoKYGBge3J9CnRvdGFsX3NhbGVzIDwtIHN1bShuZXdfZGYkaXRlbV9wcmljZV9VU0QpCnRvdGFsX3NhbGVzCmBgYAoKT3ZlciB0aGlzIHRpbWUgcGVyaW9kIG91ciBleGFtcGxlIEFtYXpvbiBhY2NvdW50IGhhcyBzb2xkIDg3Niw3ODcuMTAgd29ydGggb2YgcHJvZHVjdHMuCgoKIyMjIyBCZWNhdXNlIGRhdGEgaW4gb3VyIHJlcG9ydCBpcyBnaXZlbiBvbiBlYWNoIGl0ZW0gc29sZCBhbmQgbm90IGVhY2ggb3JkZXIsIGxldCB1cyBkZXJpdmUgdGhlIG51bWJlciBvZiB1bmlxdWUgb3JkZXJzIG92ZXIgdGhpcyBwZXJpb2Q6CmBgYHtyfQp1bmlxdWVfb3JkZXJzIDwtIGxlbmd0aCh1bmlxdWUobmV3X2RmJGFtYXpvbl9vcmRlcl9pZCkpCnVuaXF1ZV9vcmRlcnMKYGBgCgpXZSBmaW5kIHRoYXQgb3VyIGV4YW1wbGUgYWNjb3VudCBoYWQgbWFkZSA0Nzc4OCB1bmlxdWUgb3JkZXJzIGR1cmluZyB0aGUgcGVyaW9kIG9mIDEuNSB5ZWFycy4KCiMjIyMgRGV0ZXJtaW5pbmcgdGhlIGF2ZXJhZ2Ugb3JkZXIgdmFsdWU6CmBgYHtyfQphdmdfb3JkZXJfdmFsdWUgPC0gcm91bmQodG90YWxfc2FsZXMvdW5pcXVlX29yZGVycywgZGlnaXRzPTIpCmF2Z19vcmRlcl92YWx1ZQpgYGAKCldlIGZpbmQgdGhhdCB0aGUgYXZlcmFnZSBvcmRlciB2YWx1ZSBmb3IgdGhpcyBhY2NvdW50IGlzICQxOC4zNS4KCiMjIyMgQ2FsY3VsYXRpbmcgdGhlIGF2ZXJhZ2Ugb3JkZXIgZnJlcXVlbmN5IGZvciBvdXIgY3VzdG9tZXJzICh0b3RhbCBvcmRlcnMgLyB1bmlxdWUgY3VzdG9tZXJzKToKYGBge3J9Cm9yZGVyX2ZyZXEgPC0gcm91bmQodW5pcXVlX29yZGVycy91bmlxdWVfYnV5ZXJzLCBkaWdpdHM9MikKb3JkZXJfZnJlcQpgYGAKCldlIGZpbmQgdGhhdCB0aGUgY3VzdG9tZXJzIG9mIHRoaXMgQW1hem9uIGJ1c2luZXNzIGhhdmUgYW4gYXZlcmFnZSBvcmRlciBmcmVxdWVuY3kgb2YgMS4yNiBvcmRlcnMgb3ZlciB0aGUgcGVyaW9kIG9mIDEuNSB5ZWFycy4KCiMjIyMgQ2FsY3VsYXRpbmcgdGhlIGF2ZXJhZ2UgY3VzdG9tZXIgdmFsdWUgKGF2ZXJhZ2Ugb3JkZXIgdmFsdWUgLyBvcmRlciBmcmVxdWVuY3kpOgpgYGB7cn0KYXZnX2N1c3RfdmFsdWUgPSByb3VuZChhdmdfb3JkZXJfdmFsdWUqb3JkZXJfZnJlcSwgZGlnaXQ9MikKYXZnX2N1c3RfdmFsdWUKYGBgCgpUaGUgYXZlcmFnZSBjdXN0b21lciB2YWx1ZSBmb3IgdGhlIGdpdmVuIGRhdGEgaXMgMjMuMTIsIGluZGljYXRpbmcgdGhhdCB0aGlzIGlzIHRoZSBhbW91bnQgd2UgZXhwZWN0IG91ciBhdmVyYWdlIGN1c3RvbWVyIHRvIHNwZW5kIG92ZXIgYSBwZXJpb2Qgb2YgMS41IHllYXJzLiBJZiBkYXRhIGZvciBvbmx5IDEgeWVhciB3YXMgc2VsZWN0ZWQsIHRoZW4gdGhpcyBmaWd1cmUgcmVwcmVzZW50cyB0aGUgY3VzdG9tZXIncyB2YWx1ZSBvdmVyIGp1c3Qgb25lIHllYXIuIFRvIGRldGVybWluZSB0aGUgKmxpZmV0aW1lIHZhbHVlKiBvZiBvdXIgY3VzdG9tZXIsIHdlIG1ha2UgYSByZWFzb25hYmxlIGVzdGltYXRlIGdpdmVuIG91ciBrbm93bGVkZ2Ugb2YgdGhlIGJ1c2luZXNzIHRoYXQgdGhlIGF2ZXJhZ2UgY3VzdG9tZXIgZW5nYWdlcyB3aXRoIHRoaXMgYnVzaW5lc3MgZm9yIDMgeWVhcnMuIFRoYXQgd291bGQgbWFrZSB0aGUgdG90YWwgYXZlcmFnZSBsaWZldGltZSBjdXN0b21lciB2YWx1ZSBlcXVhbCB0byAkNDYuMjQuIE91ciBlc3RpbWF0ZSBvZiAzIHllYXJzIHdpbGwgdmFyeSBmcm9tIGJ1c2luZXNzIHRvIGJ1c2luZXNzLCBhbmQgYnVzaW5lc3Mgc3Rha2Vob2xkZXJzIHdpbGwgYmUgaW4gdGhlIGJlc3QgcG9zaXRpb24gdG8gbWFrZSB0aGlzIGRldGVybWluYXRpb24=