DESKRIPSI

Data historis suatu perusahaan yang diolah dengan baik, dapat memberi peluang baru agar perusahaan tersebut bisa beroperasi dengan lebih optimal. Hal tersebut yang ingin dicapai oleh perusahaan pesan-antar makanan, yakni ingin mengoptimasi aktivitas penjualan dan pelayanan mereka. Perusahaan ini memiliki banyak cabang di beberapa kota dan mempunyai data historis yang mencatat seluruh aktivitas pesanan dalam pola mingguan. Data diperoleh dari Kaggle : https://www.kaggle.com/ghoshsaptarshi/av-genpact-hack-dec2018?select=train.csv sebagai bagian dari Machine Learning Hackathon yang diselenggarakan oleh Analytics Vidhya & Genpact pada Desember 2018.

Setidaknya ada 4 data yang disediakan untuk dieksplor dan dibuat model machine learning: train, test, mealinfo, dan fcenter. Keempatnya bisa diolah masalah utama yang ingin dipecahkan, yaitu memprediksi jumlah penjualan selama 10 minggu mendatang (minggu ke 146 hingga 155). Uniknya dalam dataset yang tersedia, tidak ada variabel yang secara langsung mencatat historis waktu pada tiap observasi. Informasi waktu (mingguan), tercatat dalam bentuk urutan minggu kesekian. Ini adalah salah satu hal yang menarik, apakah pendekatan machine learning forecasting mampu membaca tipe dataset seperti ini? Atau perlu menggunakan model lain?

Eksplorasi dataset secara mendalam bisa memberi informasi penting terkait perlu tidaknya sebuah menu dipromosikan melaui email atau platform digital. Di sisi lain, hasil prediksi jumlah pesanan (variabel : num_orders) dari pemodelan machine learning akan digunakan untuk mengoptimasi rencana persediaan bahan makanan dan penempatan pekerja untuk tiap cabang pesan-antar.

DATASET

DATA TRAIN

Data train menyimpan data historik terkait aktivitas delivery untuk semua cabang. Adanya data train adalah supaya model machine learning yang nanti dibuat bisa mempelajari banyak data.

Keterangan data train :

  1. id: Kode Unik

  2. week: Nomer minggu

  3. center_id: Id unik untuk pusat pemenuhan

  4. meal_id: Id unik untuk makan

  5. checkout_price: Harga akhir termasuk diskon, pajak dan pengeriman

  6. base_price: harga terbaik dari makanan

  7. emailer_for_promotion: Email dikirim untuk promosi

  8. homepage_featured: Makanan unggulan

  9. num_orders: Jumlah pesanan (target)

Dimensi Data

dim(train)
[1] 456548      9

Data Test

Data test punya variabel yang sama dengan train, hanya berbeda di variabel target yang nantinya perlu diisi dengan hasil prediksi menggunakan machine learning.

Keterangan data test:

  1. id: indentitas unik

  2. week: nomer minggu

  3. center_id: Id unik untuk pusat pemenuhan

  4. meal_id: Id unik untuk makanan

  5. checkout_price: Harga akhir termasuk diskoon, pajak, dan ongkos kirim

  6. base_price: Harga dasar makanan

  7. emailer_for_promotion: Email dikirim untuk promosi

  8. homepage_featured: Makanan ditampilkan di beranda

Dimensi data :

dim(test)
[1] 32573     8

Data Meal Info

Data mealinfo mencatat seluruh informasi tentang makanan yang disajikan untuk kemudian diantar ke tempat tujuan.

Keterangan data mealinfo:

  1. meal_id: Id unik untuk makan

  2. category: type of meal (beverages/snacks/soup/etc)

  3. cuisine: Jenis makana (minuman/makanan/ringan/sup/dan lain-lain)

Dimensi data

dim(mealinfo)
[1] 51  3

Data Fulfilment Center Info

Data fcenter mencatat informasi dari setiap cabang yang melayani suatu pesanan.

Keterangan data fcenter:

  1. center_id: Id unik untuk pusat pemenuhan

  2. city_code: kode unik untuk kota

  3. region_code: kode unik untuk wilayah

  4. center_type: tipe pusat anonim

  5. op_area: wilayah operasi (di km^2)

Dimensi data:

dim(fcenter)
[1] 77  5

Preporcessing Data

Pada tahap ini, melakukan:

Data cleansing, melihat dan menyesuaikan tipe data, cek NA/missing value, dan duplicated value.

Data merging , menggabungkan beberapa dataset agar bisa dieksplorasi dan diolah lebih jauh.

Data cleansing, melihat dan menyesuaikan tipe data, cek NA/missing value, dan duplicated value.

DATA TRAIN

Data Cleansing

glimpse(train)
Rows: 456,548
Columns: 9
$ id                    <int> 1379560, 1466964, 1346989, 1338232, 1448490, 1270037, 1191377, 1499~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ center_id             <int> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,~
$ meal_id               <int> 1885, 1993, 2539, 2139, 2631, 1248, 1778, 1062, 2707, 1207, 1230, 2~
$ checkout_price        <dbl> 136.83, 136.83, 134.86, 339.50, 243.50, 251.23, 183.36, 182.36, 193~
$ base_price            <dbl> 152.29, 135.83, 135.86, 437.53, 242.50, 252.23, 184.36, 183.36, 192~
$ emailer_for_promotion <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0~
$ homepage_featured     <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0~
$ num_orders            <int> 177, 270, 189, 54, 40, 28, 190, 391, 472, 676, 823, 972, 162, 420, ~

Transforming Data Type

glimpse(train)
Rows: 456,548
Columns: 9
$ id                    <fct> 1379560, 1466964, 1346989, 1338232, 1448490, 1270037, 1191377, 1499~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ center_id             <fct> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,~
$ meal_id               <fct> 1885, 1993, 2539, 2139, 2631, 1248, 1778, 1062, 2707, 1207, 1230, 2~
$ checkout_price        <dbl> 136.83, 136.83, 134.86, 339.50, 243.50, 251.23, 183.36, 182.36, 193~
$ base_price            <dbl> 152.29, 135.83, 135.86, 437.53, 242.50, 252.23, 184.36, 183.36, 192~
$ emailer_for_promotion <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0~
$ num_orders            <int> 177, 270, 189, 54, 40, 28, 190, 391, 472, 676, 823, 972, 162, 420, ~

Missing Value

colSums(is.na(train))
                   id                  week             center_id               meal_id 
                    0                     0                     0                     0 
       checkout_price            base_price emailer_for_promotion     homepage_featured 
                    0                     0                     0                     0 
           num_orders 
                    0 

Tidak ada missing value pada data train.

Duplicated

Tidak ada duplicated value pada data train.

Data Merging : datatrain

Saya perlu menggabungkan (merge) beberapa dataset yang disediakan menjadi satu data tabular yang siap diproses. Data train dan test masing-masing akan digabung dengan mealinfo (berdasarkan meal_id) dan fcenter (berdasarkan center_id).

Data Structure

glimpse(datatrain)
Rows: 456,548
Columns: 15
$ center_id             <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,~
$ meal_id               <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 2304, 2322, 1230, 2577, 1~
$ id                    <fct> 1347181, 1016940, 1378864, 1223760, 1260561, 1072972, 1169567, 1254~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ checkout_price        <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.50, 631.53, 485.03, 320~
$ base_price            <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.33, 630.53, 485.03, 386~
$ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ num_orders            <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717, 324, 501, 95, 298, 47~
$ category              <chr> "Beverages", "Other Snacks", "Extras", "Pizza", "Beverages", "Other~
$ cuisine               <chr> "Italian", "Thai", "Thai", "Continental", "Continental", "Thai", "C~
$ city_code             <int> 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 59~
$ region_code           <int> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,~
$ center_type           <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B~
$ op_area               <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.~

Transforming Data Type

glimpse(datatrain)
Rows: 456,548
Columns: 15
$ center_id             <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,~
$ meal_id               <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 2304, 2322, 1230, 2577, 1~
$ id                    <fct> 1347181, 1016940, 1378864, 1223760, 1260561, 1072972, 1169567, 1254~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ checkout_price        <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.50, 631.53, 485.03, 320~
$ base_price            <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.33, 630.53, 485.03, 386~
$ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ num_orders            <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717, 324, 501, 95, 298, 47~
$ category              <chr> "Beverages", "Other Snacks", "Extras", "Pizza", "Beverages", "Other~
$ cuisine               <chr> "Italian", "Thai", "Thai", "Continental", "Continental", "Thai", "C~
$ city_code             <fct> 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 59~
$ region_code           <fct> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,~
$ center_type           <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B~
$ op_area               <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.~

Missing Value

colSums(is.na(datatrain))
            center_id               meal_id                    id                  week 
                    0                     0                     0                     0 
       checkout_price            base_price emailer_for_promotion     homepage_featured 
                    0                     0                     0                     0 
           num_orders              category               cuisine             city_code 
                    0                     0                     0                     0 
          region_code           center_type               op_area 
                    0                     0                     0 

Check Outliers

length(boxplot(datatrain$num_orders)$out)
[1] 32937

length(boxplot(log(datatrain$num_orders))$out)
[1] 306

Data outlier pada target variabel mencapai puluhan ribu. Untuk itu, beberapa variabel numerik–termasuk target–perlu ditransformasi menggunakan scale/log. Tampak perbedaan jumlah data outlier yang cukup signifikan setelah target variabel ditransformasi menggunakan log. Lebih jauh lagi, beberapa variabel numerik lainnya juga akan diberi perlakuan sama. Untuk sekarang sekarang kita sudah memiliki datatrain yang siap untuk diekplor dan divisualisasikan agar mendapat informasi/insight yang berguna. Berikutnya akan dibuat model machine learning supaya bisa menghasilkan prediksi yang tepat pada variabel num_orders.

DATA TEST

glimpse(test)
Rows: 32,573
Columns: 8
$ id                    <int> 1028232, 1127204, 1212707, 1082698, 1400926, 1284113, 1197966, 1132~
$ week                  <int> 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 14~
$ center_id             <int> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,~
$ meal_id               <int> 1885, 1993, 2539, 2631, 1248, 1778, 1062, 2707, 1207, 1230, 2322, 2~
$ checkout_price        <dbl> 158.11, 160.11, 157.14, 162.02, 163.93, 190.15, 191.09, 242.56, 360~
$ base_price            <dbl> 159.11, 159.11, 159.14, 162.02, 163.93, 190.15, 192.09, 240.56, 360~
$ emailer_for_promotion <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0~

Transforming Data Type

glimpse(test)
Rows: 32,573
Columns: 8
$ id                    <fct> 1028232, 1127204, 1212707, 1082698, 1400926, 1284113, 1197966, 1132~
$ week                  <int> 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 14~
$ center_id             <fct> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,~
$ meal_id               <fct> 1885, 1993, 2539, 2631, 1248, 1778, 1062, 2707, 1207, 1230, 2322, 2~
$ checkout_price        <dbl> 158.11, 160.11, 157.14, 162.02, 163.93, 190.15, 191.09, 242.56, 360~
$ base_price            <dbl> 159.11, 159.11, 159.14, 162.02, 163.93, 190.15, 192.09, 240.56, 360~
$ emailer_for_promotion <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0~

Missing Value

colSums(is.na(test))
                   id                  week             center_id               meal_id 
                    0                     0                     0                     0 
       checkout_price            base_price emailer_for_promotion     homepage_featured 
                    0                     0                     0                     0 

Tidak ada missing value pada data test.

Duplicated

Tidak ada duplicated value pada data test.

Data Merging : datatrain

Saya perlu menggabungkan (merge) beberapa dataset yang disediakan menjadi satu data tabular yang siap diproses. Data train dan test masing-masing akan digabung dengan mealinfo (berdasarkan meal_id) dan fcenter (berdasarkan center_id).

Data Structure

glimpse(datatrain)
Rows: 456,548
Columns: 15
$ center_id             <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,~
$ meal_id               <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 2304, 2322, 1230, 2577, 1~
$ id                    <fct> 1347181, 1016940, 1378864, 1223760, 1260561, 1072972, 1169567, 1254~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ checkout_price        <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.50, 631.53, 485.03, 320~
$ base_price            <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.33, 630.53, 485.03, 386~
$ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ num_orders            <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717, 324, 501, 95, 298, 47~
$ category              <chr> "Beverages", "Other Snacks", "Extras", "Pizza", "Beverages", "Other~
$ cuisine               <chr> "Italian", "Thai", "Thai", "Continental", "Continental", "Thai", "C~
$ city_code             <int> 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 59~
$ region_code           <int> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,~
$ center_type           <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B~
$ op_area               <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.~

Transforming Data Type

glimpse(datatrain)
Rows: 456,548
Columns: 15
$ center_id             <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,~
$ meal_id               <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 2304, 2322, 1230, 2577, 1~
$ id                    <fct> 1347181, 1016940, 1378864, 1223760, 1260561, 1072972, 1169567, 1254~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ checkout_price        <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.50, 631.53, 485.03, 320~
$ base_price            <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.33, 630.53, 485.03, 386~
$ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ num_orders            <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717, 324, 501, 95, 298, 47~
$ category              <chr> "Beverages", "Other Snacks", "Extras", "Pizza", "Beverages", "Other~
$ cuisine               <chr> "Italian", "Thai", "Thai", "Continental", "Continental", "Thai", "C~
$ city_code             <fct> 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 59~
$ region_code           <fct> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,~
$ center_type           <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B~
$ op_area               <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.~

Missing Value

colSums(is.na(datatrain))
            center_id               meal_id                    id                  week 
                    0                     0                     0                     0 
       checkout_price            base_price emailer_for_promotion     homepage_featured 
                    0                     0                     0                     0 
           num_orders              category               cuisine             city_code 
                    0                     0                     0                     0 
          region_code           center_type               op_area 
                    0                     0                     0 

Check Outliers

length(boxplot(datatrain$num_orders)$out)
[1] 32937

length(boxplot(log(datatrain$num_orders))$out)
[1] 306

Data outlier pada target variabel mencapai puluhan ribu. Untuk itu, beberapa variabel numerik–termasuk target–perlu ditransformasi menggunakan scale/log. Tampak perbedaan jumlah data outlier yang cukup signifikan setelah target variabel ditransformasi menggunakan log. Lebih jauh lagi, beberapa variabel numerik lainnya juga akan diberi perlakuan sama. Untuk sekarang sekarang kita sudah memiliki datatrain yang siap untuk diekplor dan divisualisasikan agar mendapat informasi/insight yang berguna. Berikutnya akan dibuat model machine learning supaya bisa menghasilkan prediksi yang tepat pada variabel num_orders.

Data Structure

Data Cleansing

glimpse(mealinfo)
Rows: 51
Columns: 3
$ meal_id  <int> 1885, 1993, 2539, 1248, 2631, 1311, 1062, 1778, 1803, 1198, 2707, 1847, 1438, 24~
$ category <chr> "Beverages", "Beverages", "Beverages", "Beverages", "Beverages", "Extras", "Beve~
$ cuisine  <chr> "Thai", "Thai", "Thai", "Indian", "Indian", "Thai", "Italian", "Italian", "Thai"~

Transforming Data Type

glimpse(mealinfo)
Rows: 51
Columns: 3
$ meal_id  <fct> 1885, 1993, 2539, 1248, 2631, 1311, 1062, 1778, 1803, 1198, 2707, 1847, 1438, 24~
$ category <fct> Beverages, Beverages, Beverages, Beverages, Beverages, Extras, Beverages, Bevera~
$ cuisine  <fct> Thai, Thai, Thai, Indian, Indian, Thai, Italian, Italian, Thai, Thai, Italian, T~

Missing Value

colSums(is.na(mealinfo))
 meal_id category  cuisine 
       0        0        0 

Tidak ada missing value pada data mealinfo.

Duplicated

Tidak ada duplicated value pada data mealinfo.

Data Merging : datatrain

saya perlu menggabungkan (merge) beberapa dataset yang disediakan menjadi satu data tabular yang siap diproses. Data train dan test masing-masing akan digabung dengan mealinfo (berdasarkan meal_id) dan fcenter (berdasarkan center_id).

Data Structure

glimpse(datatrain)
Rows: 456,548
Columns: 15
$ center_id             <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,~
$ meal_id               <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 2304, 2322, 1230, 2577, 1~
$ id                    <fct> 1347181, 1016940, 1378864, 1223760, 1260561, 1072972, 1169567, 1254~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ checkout_price        <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.50, 631.53, 485.03, 320~
$ base_price            <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.33, 630.53, 485.03, 386~
$ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ num_orders            <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717, 324, 501, 95, 298, 47~
$ category              <fct> Beverages, Other Snacks, Extras, Pizza, Beverages, Other Snacks, Se~
$ cuisine               <fct> Italian, Thai, Thai, Continental, Continental, Thai, Continental, I~
$ city_code             <int> 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 59~
$ region_code           <int> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,~
$ center_type           <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B~
$ op_area               <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.~

Transforming Data Type

glimpse(datatrain)
Rows: 456,548
Columns: 15
$ center_id             <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,~
$ meal_id               <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 2304, 2322, 1230, 2577, 1~
$ id                    <fct> 1347181, 1016940, 1378864, 1223760, 1260561, 1072972, 1169567, 1254~
$ week                  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ checkout_price        <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.50, 631.53, 485.03, 320~
$ base_price            <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.33, 630.53, 485.03, 386~
$ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ homepage_featured     <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ num_orders            <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717, 324, 501, 95, 298, 47~
$ category              <fct> Beverages, Other Snacks, Extras, Pizza, Beverages, Other Snacks, Se~
$ cuisine               <fct> Italian, Thai, Thai, Continental, Continental, Thai, Continental, I~
$ city_code             <fct> 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 590, 59~
$ region_code           <fct> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,~
$ center_type           <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B~
$ op_area               <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.~

Missing Value

colSums(is.na(datatrain))
            center_id               meal_id                    id                  week 
                    0                     0                     0                     0 
       checkout_price            base_price emailer_for_promotion     homepage_featured 
                    0                     0                     0                     0 
           num_orders              category               cuisine             city_code 
                    0                     0                     0                     0 
          region_code           center_type               op_area 
                    0                     0                     0 

Check Outliers

length(boxplot(datatrain$num_orders)$out)
[1] 32937

length(boxplot(log(datatrain$num_orders))$out)
[1] 306

Data outlier pada target variabel mencapai puluhan ribu. Untuk itu, beberapa variabel numerik–termasuk target–perlu ditransformasi menggunakan scale/log. Tampak perbedaan jumlah data outlier yang cukup signifikan setelah target variabel ditransformasi menggunakan log. Lebih jauh lagi, beberapa variabel numerik lainnya juga akan diberi perlakuan sama. Untuk sekarang sekarang kita sudah memiliki datatrain yang siap untuk diekplor dan divisualisasikan agar mendapat informasi/insight yang berguna. Berikutnya akan dibuat model machine learning supaya bisa menghasilkan prediksi yang tepat pada variabel num_orders.

EXPLORATORY DATA ANALYSIS

Saya coba buat kolom baru yang menyimpan informasi penjualan sebagai hasil perkalian kolom checkout_price dan num_orders. Kolom sales nantinya akan diolah untuk menghasilkan visualisasi jenis makanan apa saya yang cukup diminati dan berkontribusi paling besar pada kenaikan angka penjualan.

Dari grafik di atas, kita bisa tahu rata-rata penjualan tiap minggunya (minggu ke 1-145) selalu naik turun, ada pola seasonality pada datatrain. Namun keadaan ini belum bisa diinterpretasikan lebih jauh, yang jelas rata-rata penjualan sejauh ini berada di angka 60.000 hingga 70.000 setiap minggu.

Ada 14 jenis (category) makanan yang tersedia pada datatrain, dimana Beverages adalah jenis yang paling sering dipesan oleh pembeli. Posisinya sangat unggul jauh dibanding jenis makanan lain. Keadaan ini bisa dipengaruhi karena kecenderungan orang-orang yang lebih suka mengonsumsi makanan ringan (camilan) ketimbang makanan berat. Namun dengan penjualan Beverages sebanyak lebih dari 120.000 pesanan, apakah berarti jenis makanan ini berkontribusi paling besar dalam meningkatkan angka penjualan?

Nyatanya tidak. Secara visualisasi, cukup jelas category makanan Rice Bowl membawa keuntungan paling besar dari seluruh data penjualan, yaitu sekitar 170.000. Kemudian ada Sandwich di peringkat dua dan Pizza di peringkat tiga. Sedangkan kontribusi Beveragescenderung kecil, sekitar 6.000 saja. Maka dari itu, perusahaan tetap perlu memperhatikan ketersediaan bahan bukan hanya makanan yang paling sering dipesan, tapi juga makanan yang punya angka penjualan tinggi.

Mengingat tingginya permintaan Beverages maka penting bagi perusahaan bisa memenuhi permintaan pembeli dengan baik. Walau kontribusinya tidak terlalu signifikan, Beverages bisa menjadi kunci untuk membangun kepuasan berbelanja bagi pembeli. Maka dari itu, plot di atas menjelaskan cuisine Italian dan Continental sebagai favorit pembeli yang memesan Beverages.

LS0tDQp0aXRsZTogIk1FTVBFUkRJS1NJIFBFTkpVQUxBTiBTRUxBTUEgMTAgTUlOR0dVIE1FTkRBVEFORyINCmF1dGhvcjogIlxVMDAwMUY1RTMgSmFtYWxsdWRpbiINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDcNCiAgICBmaWdfaGVpZ2h0OiA0LjUNCiAgICB0aGVtZTogcmVhZGFibGUNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KDQohW10obWVuZXR1a2FuIHRlcmJhaWsucG5nKQ0KDQoNCg0KIyBERVNLUklQU0kgDQoNCiFbXShERVNLUklQU0kucG5nKQ0KDQoNCkRhdGEgaGlzdG9yaXMgc3VhdHUgcGVydXNhaGFhbiB5YW5nIGRpb2xhaCBkZW5nYW4gYmFpaywgZGFwYXQgbWVtYmVyaSBwZWx1YW5nIGJhcnUgYWdhciBwZXJ1c2FoYWFuIHRlcnNlYnV0IGJpc2EgYmVyb3BlcmFzaSBkZW5nYW4gbGViaWggb3B0aW1hbC4gSGFsIHRlcnNlYnV0IHlhbmcgaW5naW4gZGljYXBhaSBvbGVoIHBlcnVzYWhhYW4gcGVzYW4tYW50YXIgbWFrYW5hbiwgeWFrbmkgaW5naW4gbWVuZ29wdGltYXNpIGFrdGl2aXRhcyBwZW5qdWFsYW4gZGFuIHBlbGF5YW5hbiBtZXJla2EuIFBlcnVzYWhhYW4gaW5pIG1lbWlsaWtpIGJhbnlhayBjYWJhbmcgZGkgYmViZXJhcGEga290YSBkYW4gbWVtcHVueWFpIGRhdGEgaGlzdG9yaXMgeWFuZyBtZW5jYXRhdCBzZWx1cnVoIGFrdGl2aXRhcyBwZXNhbmFuIGRhbGFtIHBvbGEgbWluZ2d1YW4uIA0KRGF0YSBkaXBlcm9sZWggZGFyaSBLYWdnbGUgOiBodHRwczovL3d3dy5rYWdnbGUuY29tL2dob3Noc2FwdGFyc2hpL2F2LWdlbnBhY3QtaGFjay1kZWMyMDE4P3NlbGVjdD10cmFpbi5jc3Ygc2ViYWdhaSBiYWdpYW4gZGFyaSBNYWNoaW5lIExlYXJuaW5nIEhhY2thdGhvbiB5YW5nIGRpc2VsZW5nZ2FyYWthbiBvbGVoIEFuYWx5dGljcyBWaWRoeWEgJiBHZW5wYWN0IHBhZGEgRGVzZW1iZXIgMjAxOC4NCg0KU2V0aWRha255YSBhZGEgNCBkYXRhIHlhbmcgZGlzZWRpYWthbiB1bnR1ayBkaWVrc3Bsb3IgZGFuIGRpYnVhdCBtb2RlbCBtYWNoaW5lIGxlYXJuaW5nOiB0cmFpbiwgdGVzdCwgbWVhbGluZm8sIGRhbiBmY2VudGVyLiBLZWVtcGF0bnlhIGJpc2EgZGlvbGFoIG1hc2FsYWggdXRhbWEgeWFuZyBpbmdpbiBkaXBlY2Foa2FuLCB5YWl0dSBtZW1wcmVkaWtzaSBqdW1sYWggcGVuanVhbGFuIHNlbGFtYSAxMCBtaW5nZ3UgbWVuZGF0YW5nIChtaW5nZ3Uga2UgMTQ2IGhpbmdnYSAxNTUpLiBVbmlrbnlhIGRhbGFtIGRhdGFzZXQgeWFuZyB0ZXJzZWRpYSwgdGlkYWsgYWRhIHZhcmlhYmVsIHlhbmcgc2VjYXJhIGxhbmdzdW5nIG1lbmNhdGF0IGhpc3RvcmlzIHdha3R1IHBhZGEgdGlhcCBvYnNlcnZhc2kuIEluZm9ybWFzaSB3YWt0dSAobWluZ2d1YW4pLCB0ZXJjYXRhdCBkYWxhbSBiZW50dWsgdXJ1dGFuIG1pbmdndSBrZXNla2lhbi4gSW5pIGFkYWxhaCBzYWxhaCBzYXR1IGhhbCB5YW5nIG1lbmFyaWssIGFwYWthaCBwZW5kZWthdGFuIG1hY2hpbmUgbGVhcm5pbmcgZm9yZWNhc3RpbmcgbWFtcHUgbWVtYmFjYSB0aXBlIGRhdGFzZXQgc2VwZXJ0aSBpbmk/IEF0YXUgcGVybHUgbWVuZ2d1bmFrYW4gbW9kZWwgbGFpbj8NCg0KRWtzcGxvcmFzaSBkYXRhc2V0IHNlY2FyYSBtZW5kYWxhbSBiaXNhIG1lbWJlcmkgaW5mb3JtYXNpIHBlbnRpbmcgdGVya2FpdCBwZXJsdSB0aWRha255YSBzZWJ1YWggbWVudSBkaXByb21vc2lrYW4gbWVsYXVpIGVtYWlsIGF0YXUgcGxhdGZvcm0gZGlnaXRhbC4gRGkgc2lzaSBsYWluLCBoYXNpbCBwcmVkaWtzaSBqdW1sYWggcGVzYW5hbiAodmFyaWFiZWwgOiBudW1fb3JkZXJzKSBkYXJpIHBlbW9kZWxhbiBtYWNoaW5lIGxlYXJuaW5nIGFrYW4gZGlndW5ha2FuIHVudHVrIG1lbmdvcHRpbWFzaSByZW5jYW5hIHBlcnNlZGlhYW4gYmFoYW4gbWFrYW5hbiBkYW4gcGVuZW1wYXRhbiBwZWtlcmphIHVudHVrIHRpYXAgY2FiYW5nIHBlc2FuLWFudGFyLg0KDQojIERBVEFTRVQNCg0KIyMgREFUQSBUUkFJTg0KDQohW10oREFUQSBUUkFJTi5wbmcpDQoNCkRhdGEgdHJhaW4gbWVueWltcGFuIGRhdGEgaGlzdG9yaWsgdGVya2FpdCBha3Rpdml0YXMgZGVsaXZlcnkgdW50dWsgc2VtdWEgY2FiYW5nLiBBZGFueWEgZGF0YSB0cmFpbiBhZGFsYWggc3VwYXlhIG1vZGVsIG1hY2hpbmUgbGVhcm5pbmcgeWFuZyBuYW50aSBkaWJ1YXQgYmlzYSBtZW1wZWxhamFyaSBiYW55YWsgZGF0YS4NCg0KDQpgYGB7cn0NCnRyYWluIDwtIHJlYWQuY3N2KCJ0cmFpbi5jc3YiKQ0KaGVhZCh0cmFpbikNCmBgYA0KDQpLZXRlcmFuZ2FuIGRhdGEgdHJhaW4gOiANCg0KMS4gaWQ6IEtvZGUgVW5paw0KDQoyLiB3ZWVrOiBOb21lciBtaW5nZ3UNCg0KMy4gY2VudGVyX2lkOiBJZCB1bmlrIHVudHVrIHB1c2F0IHBlbWVudWhhbg0KDQo0LiBtZWFsX2lkOiBJZCB1bmlrIHVudHVrIG1ha2FuDQoNCjUuIGNoZWNrb3V0X3ByaWNlOiBIYXJnYSBha2hpciB0ZXJtYXN1ayBkaXNrb24sIHBhamFrIGRhbiBwZW5nZXJpbWFuDQoNCjYuIGJhc2VfcHJpY2U6IGhhcmdhIHRlcmJhaWsgZGFyaSBtYWthbmFuDQoNCjcuIGVtYWlsZXJfZm9yX3Byb21vdGlvbjogRW1haWwgZGlraXJpbSB1bnR1ayBwcm9tb3NpDQoNCjguIGhvbWVwYWdlX2ZlYXR1cmVkOiBNYWthbmFuIHVuZ2d1bGFuDQoNCjkuIG51bV9vcmRlcnM6IEp1bWxhaCBwZXNhbmFuICh0YXJnZXQpDQoNCg0KRGltZW5zaSBEYXRhIA0KDQpgYGB7cn0NCmRpbSh0cmFpbikNCmBgYA0KDQojIyBEYXRhIFRlc3QNCg0KIVtdKERBVEEgVEVTVC5wbmcpDQoNCkRhdGEgdGVzdCBwdW55YSB2YXJpYWJlbCB5YW5nIHNhbWEgZGVuZ2FuIHRyYWluLCBoYW55YSBiZXJiZWRhIGRpIHZhcmlhYmVsIHRhcmdldCB5YW5nIG5hbnRpbnlhIHBlcmx1IGRpaXNpIGRlbmdhbiBoYXNpbCBwcmVkaWtzaSBtZW5nZ3VuYWthbiBtYWNoaW5lIGxlYXJuaW5nLg0KDQpgYGB7cn0NCnRlc3QgPC0gcmVhZC5jc3YoInRlc3QuY3N2IikNCmhlYWQodGVzdCkNCmBgYA0KDQpLZXRlcmFuZ2FuIGRhdGEgdGVzdDoNCg0KMS4gaWQ6IGluZGVudGl0YXMgdW5paw0KDQoyLiB3ZWVrOiBub21lciBtaW5nZ3UNCg0KMy4gY2VudGVyX2lkOiBJZCB1bmlrIHVudHVrIHB1c2F0IHBlbWVudWhhbg0KDQo0LiBtZWFsX2lkOiBJZCB1bmlrIHVudHVrIG1ha2FuYW4NCg0KNS4gY2hlY2tvdXRfcHJpY2U6IEhhcmdhIGFraGlyIHRlcm1hc3VrIGRpc2tvb24sIHBhamFrLCBkYW4gb25na29zIGtpcmltDQoNCjYuIGJhc2VfcHJpY2U6IEhhcmdhIGRhc2FyIG1ha2FuYW4NCg0KNy4gZW1haWxlcl9mb3JfcHJvbW90aW9uOiBFbWFpbCBkaWtpcmltIHVudHVrIHByb21vc2kgDQoNCjguIGhvbWVwYWdlX2ZlYXR1cmVkOiBNYWthbmFuIGRpdGFtcGlsa2FuIGRpIGJlcmFuZGEgDQoNCg0KRGltZW5zaSBkYXRhIDogDQoNCmBgYHtyfQ0KZGltKHRlc3QpDQpgYGANCg0KIyMgRGF0YSBNZWFsIEluZm8NCg0KIVtdKERBVEEgTUVBTCBJTkZPLnBuZykNCg0KRGF0YSBtZWFsaW5mbyBtZW5jYXRhdCBzZWx1cnVoIGluZm9ybWFzaSB0ZW50YW5nIG1ha2FuYW4geWFuZyBkaXNhamlrYW4gdW50dWsga2VtdWRpYW4gZGlhbnRhciBrZSB0ZW1wYXQgdHVqdWFuLg0KDQpgYGB7cn0NCm1lYWxpbmZvIDwtIHJlYWQuY3N2KCJtZWFsX2luZm8uY3N2IikNCmhlYWQobWVhbGluZm8pDQpgYGANCg0KS2V0ZXJhbmdhbiBkYXRhIG1lYWxpbmZvOg0KDQoxLiBtZWFsX2lkOiBJZCB1bmlrIHVudHVrIG1ha2FuIA0KDQoyLiBjYXRlZ29yeTogdHlwZSBvZiBtZWFsIChiZXZlcmFnZXMvc25hY2tzL3NvdXAvZXRjKQ0KDQozLiBjdWlzaW5lOiBKZW5pcyBtYWthbmEgKG1pbnVtYW4vbWFrYW5hbi9yaW5nYW4vc3VwL2RhbiBsYWluLWxhaW4pDQoNCkRpbWVuc2kgZGF0YSANCg0KYGBge3J9DQpkaW0obWVhbGluZm8pDQpgYGANCg0KIyMgRGF0YSBGdWxmaWxtZW50IENlbnRlciBJbmZvDQoNCiFbXShEQVRBIElORk8ucG5nKQ0KDQpEYXRhIGZjZW50ZXIgbWVuY2F0YXQgaW5mb3JtYXNpIGRhcmkgc2V0aWFwIGNhYmFuZyB5YW5nIG1lbGF5YW5pIHN1YXR1IHBlc2FuYW4uDQoNCmBgYHtyfQ0KZmNlbnRlciA8LSByZWFkLmNzdigiZnVsZmlsbWVudF9jZW50ZXJfaW5mby5jc3YiKQ0KaGVhZChmY2VudGVyKQ0KYGBgDQoNCktldGVyYW5nYW4gZGF0YSBmY2VudGVyOg0KDQoxLiBjZW50ZXJfaWQ6IElkIHVuaWsgdW50dWsgcHVzYXQgcGVtZW51aGFuDQoNCjIuIGNpdHlfY29kZToga29kZSB1bmlrIHVudHVrIGtvdGENCg0KMy4gcmVnaW9uX2NvZGU6IGtvZGUgdW5payB1bnR1ayB3aWxheWFoIA0KDQo0LiBjZW50ZXJfdHlwZTogdGlwZSBwdXNhdCBhbm9uaW0NCg0KNS4gb3BfYXJlYTogd2lsYXlhaCBvcGVyYXNpIChkaSBrbV4yKQ0KDQpEaW1lbnNpIGRhdGE6DQoNCmBgYHtyfQ0KZGltKGZjZW50ZXIpDQpgYGANCg0KIyBQcmVwb3JjZXNzaW5nIERhdGENCg0KIVtdKFBSRVBPUkNFU1NJTkcucG5nKQ0KDQpgYGB7cn0NCiMgbGlicmF5IHlhbmcgZGkgZ3VuYWthbg0KDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KEdHYWxseSkNCmBgYA0KDQoNClBhZGEgdGFoYXAgaW5pLCBtZWxha3VrYW46DQoNCkRhdGEgY2xlYW5zaW5nLCBtZWxpaGF0IGRhbiBtZW55ZXN1YWlrYW4gdGlwZSBkYXRhLCBjZWsgTkEvbWlzc2luZyB2YWx1ZSwgZGFuIGR1cGxpY2F0ZWQgdmFsdWUuDQoNCkRhdGEgbWVyZ2luZyAsIG1lbmdnYWJ1bmdrYW4gYmViZXJhcGEgZGF0YXNldCBhZ2FyIGJpc2EgZGlla3NwbG9yYXNpIGRhbiBkaW9sYWggbGViaWggamF1aC4NCg0KRGF0YSBjbGVhbnNpbmcsIG1lbGloYXQgZGFuIG1lbnllc3VhaWthbiB0aXBlIGRhdGEsIGNlayBOQS9taXNzaW5nIHZhbHVlLCBkYW4gZHVwbGljYXRlZCB2YWx1ZS4NCg0KDQojIyBEQVRBIFRSQUlOIA0KDQohW10oREFUQSBUUkFJTi5wbmcpDQoNCkRhdGEgQ2xlYW5zaW5nDQoNCmBgYHtyfQ0KZ2xpbXBzZSh0cmFpbikNCmBgYA0KDQpUcmFuc2Zvcm1pbmcgRGF0YSBUeXBlDQoNCmBgYHtyfQ0KdHJhaW4gPC0gdHJhaW4gJT4lIA0KICBtdXRhdGVfYXQodmFycyhpZCwgY2VudGVyX2lkLCBtZWFsX2lkLGVtYWlsZXJfZm9yX3Byb21vdGlvbiwgaG9tZXBhZ2VfZmVhdHVyZWQpLCBhcy5mYWN0b3IpDQpnbGltcHNlKHRyYWluKQ0KYGBgDQoNCk1pc3NpbmcgVmFsdWUNCg0KYGBge3J9DQpjb2xTdW1zKGlzLm5hKHRyYWluKSkNCmBgYA0KDQpUaWRhayBhZGEgbWlzc2luZyB2YWx1ZSBwYWRhIGRhdGEgdHJhaW4uDQoNCkR1cGxpY2F0ZWQNCg0KYGBge3J9DQpkYXRhLmZyYW1lKA0KICB0cmFpbiA9IGxlbmd0aCh0cmFpbiRpZCksDQogIHRyYWluX3VuaXF1ZT1sZW5ndGgodW5pcXVlKHRyYWluJGlkKSkNCikNCmBgYA0KDQpUaWRhayBhZGEgZHVwbGljYXRlZCB2YWx1ZSBwYWRhIGRhdGEgdHJhaW4uDQoNCkRhdGEgTWVyZ2luZyA6IGRhdGF0cmFpbg0KDQpTYXlhIHBlcmx1IG1lbmdnYWJ1bmdrYW4gKG1lcmdlKSBiZWJlcmFwYSBkYXRhc2V0IHlhbmcgZGlzZWRpYWthbiBtZW5qYWRpIHNhdHUgZGF0YSB0YWJ1bGFyIHlhbmcgc2lhcCBkaXByb3Nlcy4gRGF0YSB0cmFpbiBkYW4gdGVzdCBtYXNpbmctbWFzaW5nIGFrYW4gZGlnYWJ1bmcgZGVuZ2FuIG1lYWxpbmZvIChiZXJkYXNhcmthbiBtZWFsX2lkKSBkYW4gZmNlbnRlciAoYmVyZGFzYXJrYW4gY2VudGVyX2lkKS4NCg0KYGBge3J9DQojIGRhdGEgdHJhaW4NCg0KZGF0YXRyYWluIDwtIG1lcmdlKHRyYWluLCBtZWFsaW5mbywgYnkgPSAibWVhbF9pZCIpDQpkYXRhdHJhaW4gPC0gbWVyZ2UoZGF0YXRyYWluLCBmY2VudGVyLCBieSA9ICJjZW50ZXJfaWQiKQ0KDQojIGRhdGEgdGVzdA0KDQpkYXRhdGVzdCA8LSBtZXJnZSh0ZXN0LCBtZWFsaW5mbywgYnkgPSAibWVhbF9pZCIpDQpkYXRhdGVzdCA8LSBtZXJnZShkYXRhdGVzdCwgZmNlbnRlciwgYnkgPSAiY2VudGVyX2lkIikNCg0KIyB1cnV0a2FuIGJlcmRhc2Fya2FuIG1pbmdndQ0KDQpkYXRhdHJhaW4gPC0gZGF0YXRyYWluICU+JSANCiAgYXJyYW5nZSh3ZWVrKQ0KDQpoZWFkKGRhdGF0cmFpbikNCmBgYA0KDQpEYXRhIFN0cnVjdHVyZQ0KDQpgYGB7cn0NCmdsaW1wc2UoZGF0YXRyYWluKQ0KYGBgDQoNClRyYW5zZm9ybWluZyBEYXRhIFR5cGUNCg0KYGBge3J9DQpkYXRhdHJhaW4gPC0gIGRhdGF0cmFpbiAlPiUgDQogIG11dGF0ZV9hdCh2YXJzKGNpdHlfY29kZSwgcmVnaW9uX2NvZGUpLCBhcy5mYWN0b3IpDQpnbGltcHNlKGRhdGF0cmFpbikNCmBgYA0KDQpNaXNzaW5nIFZhbHVlDQoNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShkYXRhdHJhaW4pKQ0KYGBgDQoNCkNoZWNrIE91dGxpZXJzDQoNCmBgYHtyfQ0KIyBvdXRsaWVycyByYXcgZGF0YQ0KDQpsZW5ndGgoYm94cGxvdChkYXRhdHJhaW4kbnVtX29yZGVycykkb3V0KQ0KYGBgDQoNCg0KYGBge3J9DQojIG91dGxpZXJzIHNjYWxlZCBkYXRhDQoNCmxlbmd0aChib3hwbG90KGxvZyhkYXRhdHJhaW4kbnVtX29yZGVycykpJG91dCkNCmBgYA0KDQpEYXRhIG91dGxpZXIgcGFkYSB0YXJnZXQgdmFyaWFiZWwgbWVuY2FwYWkgcHVsdWhhbiByaWJ1LiBVbnR1ayBpdHUsIGJlYmVyYXBhIHZhcmlhYmVsIG51bWVyaWvigJN0ZXJtYXN1ayB0YXJnZXTigJNwZXJsdSBkaXRyYW5zZm9ybWFzaSBtZW5nZ3VuYWthbiBzY2FsZS9sb2cuIFRhbXBhayBwZXJiZWRhYW4ganVtbGFoIGRhdGEgb3V0bGllciB5YW5nIGN1a3VwIHNpZ25pZmlrYW4gc2V0ZWxhaCB0YXJnZXQgdmFyaWFiZWwgZGl0cmFuc2Zvcm1hc2kgbWVuZ2d1bmFrYW4gbG9nLiBMZWJpaCBqYXVoIGxhZ2ksIGJlYmVyYXBhIHZhcmlhYmVsIG51bWVyaWsgbGFpbm55YSBqdWdhIGFrYW4gZGliZXJpIHBlcmxha3VhbiBzYW1hLiBVbnR1ayBzZWthcmFuZyBzZWthcmFuZyBraXRhIHN1ZGFoIG1lbWlsaWtpIGRhdGF0cmFpbiB5YW5nIHNpYXAgdW50dWsgZGlla3Bsb3IgZGFuIGRpdmlzdWFsaXNhc2lrYW4gYWdhciBtZW5kYXBhdCBpbmZvcm1hc2kvaW5zaWdodCB5YW5nIGJlcmd1bmEuIEJlcmlrdXRueWEgYWthbiBkaWJ1YXQgbW9kZWwgbWFjaGluZSBsZWFybmluZyBzdXBheWEgYmlzYSBtZW5naGFzaWxrYW4gcHJlZGlrc2kgeWFuZyB0ZXBhdCBwYWRhIHZhcmlhYmVsIG51bV9vcmRlcnMuDQoNCiMjIERBVEEgVEVTVA0KDQohW10oREFUQSBURVNULnBuZykNCg0KYGBge3J9DQpnbGltcHNlKHRlc3QpDQpgYGANCg0KVHJhbnNmb3JtaW5nIERhdGEgVHlwZQ0KDQpgYGB7cn0NCnRlc3QgPC0gdGVzdCAlPiUgDQogIG11dGF0ZV9hdCh2YXJzKGlkLCBjZW50ZXJfaWQsIG1lYWxfaWQsZW1haWxlcl9mb3JfcHJvbW90aW9uLCBob21lcGFnZV9mZWF0dXJlZCksIGFzLmZhY3RvcikNCmdsaW1wc2UodGVzdCkNCmBgYA0KDQpNaXNzaW5nIFZhbHVlDQoNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYSh0ZXN0KSkNCmBgYA0KDQpUaWRhayBhZGEgbWlzc2luZyB2YWx1ZSBwYWRhIGRhdGEgdGVzdC4NCg0KRHVwbGljYXRlZA0KDQpgYGB7cn0NCmRhdGEuZnJhbWUoDQogIHRlc3QgPSBsZW5ndGgodGVzdCRpZCksDQogIHRlc3RfdW5pcXVlPWxlbmd0aCh1bmlxdWUodGVzdCRpZCkpDQopDQpgYGANCg0KVGlkYWsgYWRhIGR1cGxpY2F0ZWQgdmFsdWUgcGFkYSBkYXRhIHRlc3QuDQoNCkRhdGEgTWVyZ2luZyA6IGRhdGF0cmFpbg0KDQpTYXlhIHBlcmx1IG1lbmdnYWJ1bmdrYW4gKG1lcmdlKSBiZWJlcmFwYSBkYXRhc2V0IHlhbmcgZGlzZWRpYWthbiBtZW5qYWRpIHNhdHUgZGF0YSB0YWJ1bGFyIHlhbmcgc2lhcCBkaXByb3Nlcy4gRGF0YSB0cmFpbiBkYW4gdGVzdCBtYXNpbmctbWFzaW5nIGFrYW4gZGlnYWJ1bmcgZGVuZ2FuIG1lYWxpbmZvIChiZXJkYXNhcmthbiBtZWFsX2lkKSBkYW4gZmNlbnRlciAoYmVyZGFzYXJrYW4gY2VudGVyX2lkKS4NCg0KYGBge3J9DQojIGRhdGEgdHJhaW4NCg0KZGF0YXRyYWluIDwtIG1lcmdlKHRyYWluLCBtZWFsaW5mbywgYnkgPSAibWVhbF9pZCIpDQpkYXRhdHJhaW4gPC0gbWVyZ2UoZGF0YXRyYWluLCBmY2VudGVyLCBieSA9ICJjZW50ZXJfaWQiKQ0KDQojIGRhdGEgdGVzdA0KDQpkYXRhdGVzdCA8LSBtZXJnZSh0ZXN0LCBtZWFsaW5mbywgYnkgPSAibWVhbF9pZCIpDQpkYXRhdGVzdCA8LSBtZXJnZShkYXRhdGVzdCwgZmNlbnRlciwgYnkgPSAiY2VudGVyX2lkIikNCg0KIyB1cnV0a2FuIGJlcmRhc2Fya2FuIG1pbmdndQ0KDQpkYXRhdHJhaW4gPC0gZGF0YXRyYWluICU+JSANCiAgYXJyYW5nZSh3ZWVrKQ0KDQpoZWFkKGRhdGF0cmFpbikNCmBgYA0KDQpEYXRhIFN0cnVjdHVyZQ0KDQpgYGB7cn0NCmdsaW1wc2UoZGF0YXRyYWluKQ0KYGBgDQoNClRyYW5zZm9ybWluZyBEYXRhIFR5cGUNCg0KYGBge3J9DQpkYXRhdHJhaW4gPC0gIGRhdGF0cmFpbiAlPiUgDQogIG11dGF0ZV9hdCh2YXJzKGNpdHlfY29kZSwgcmVnaW9uX2NvZGUpLCBhcy5mYWN0b3IpDQpnbGltcHNlKGRhdGF0cmFpbikNCmBgYA0KDQpNaXNzaW5nIFZhbHVlDQoNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShkYXRhdHJhaW4pKQ0KYGBgDQoNCkNoZWNrIE91dGxpZXJzDQoNCmBgYHtyfQ0KIyBvdXRsaWVycyByYXcgZGF0YQ0KDQpsZW5ndGgoYm94cGxvdChkYXRhdHJhaW4kbnVtX29yZGVycykkb3V0KQ0KYGBgDQoNCmBgYHtyfQ0KIyBvdXRsaWVycyBzY2FsZWQgZGF0YQ0KDQpsZW5ndGgoYm94cGxvdChsb2coZGF0YXRyYWluJG51bV9vcmRlcnMpKSRvdXQpDQpgYGANCg0KRGF0YSBvdXRsaWVyIHBhZGEgdGFyZ2V0IHZhcmlhYmVsIG1lbmNhcGFpIHB1bHVoYW4gcmlidS4gVW50dWsgaXR1LCBiZWJlcmFwYSB2YXJpYWJlbCBudW1lcmlr4oCTdGVybWFzdWsgdGFyZ2V04oCTcGVybHUgZGl0cmFuc2Zvcm1hc2kgbWVuZ2d1bmFrYW4gc2NhbGUvbG9nLiBUYW1wYWsgcGVyYmVkYWFuIGp1bWxhaCBkYXRhIG91dGxpZXIgeWFuZyBjdWt1cCBzaWduaWZpa2FuIHNldGVsYWggdGFyZ2V0IHZhcmlhYmVsIGRpdHJhbnNmb3JtYXNpIG1lbmdndW5ha2FuIGxvZy4gTGViaWggamF1aCBsYWdpLCBiZWJlcmFwYSB2YXJpYWJlbCBudW1lcmlrIGxhaW5ueWEganVnYSBha2FuIGRpYmVyaSBwZXJsYWt1YW4gc2FtYS4gVW50dWsgc2VrYXJhbmcgc2VrYXJhbmcga2l0YSBzdWRhaCBtZW1pbGlraSBkYXRhdHJhaW4geWFuZyBzaWFwIHVudHVrIGRpZWtwbG9yIGRhbiBkaXZpc3VhbGlzYXNpa2FuIGFnYXIgbWVuZGFwYXQgaW5mb3JtYXNpL2luc2lnaHQgeWFuZyBiZXJndW5hLiBCZXJpa3V0bnlhIGFrYW4gZGlidWF0IG1vZGVsIG1hY2hpbmUgbGVhcm5pbmcgc3VwYXlhIGJpc2EgbWVuZ2hhc2lsa2FuIHByZWRpa3NpIHlhbmcgdGVwYXQgcGFkYSB2YXJpYWJlbCBudW1fb3JkZXJzLg0KDQoNCiMjIERhdGEgU3RydWN0dXJlDQoNCiFbXShEQVRBIFNUQVJVUkNBVEUucG5nKQ0KDQpEYXRhIENsZWFuc2luZw0KDQpgYGB7cn0NCmdsaW1wc2UobWVhbGluZm8pDQpgYGANCg0KVHJhbnNmb3JtaW5nIERhdGEgVHlwZQ0KDQpgYGB7cn0NCg0KbWVhbGluZm8gPC0gbWVhbGluZm8gJT4lIA0KICBtdXRhdGVfYXQodmFycyhtZWFsX2lkLCBjYXRlZ29yeSwgY3Vpc2luZSksIGFzLmZhY3RvcikNCmdsaW1wc2UobWVhbGluZm8pDQpgYGANCg0KTWlzc2luZyBWYWx1ZQ0KDQpgYGB7cn0NCg0KY29sU3Vtcyhpcy5uYShtZWFsaW5mbykpDQpgYGANCg0KVGlkYWsgYWRhIG1pc3NpbmcgdmFsdWUgcGFkYSBkYXRhIG1lYWxpbmZvLg0KDQpEdXBsaWNhdGVkDQoNCmBgYHtyfQ0KZGF0YS5mcmFtZSgNCiAgdHJhaW4gPSBsZW5ndGgobWVhbGluZm8kbWVhbF9pZCksDQogIHRyYWluX3VuaXF1ZT1sZW5ndGgodW5pcXVlKG1lYWxpbmZvJG1lYWxfaWQpKQ0KKQ0KYGBgDQoNCg0KVGlkYWsgYWRhIGR1cGxpY2F0ZWQgdmFsdWUgcGFkYSBkYXRhIG1lYWxpbmZvLg0KDQpEYXRhIE1lcmdpbmcgOiBkYXRhdHJhaW4NCg0Kc2F5YSBwZXJsdSBtZW5nZ2FidW5na2FuIChtZXJnZSkgYmViZXJhcGEgZGF0YXNldCB5YW5nIGRpc2VkaWFrYW4gbWVuamFkaSBzYXR1IGRhdGEgdGFidWxhciB5YW5nIHNpYXAgZGlwcm9zZXMuIERhdGEgdHJhaW4gZGFuIHRlc3QgbWFzaW5nLW1hc2luZyBha2FuIGRpZ2FidW5nIGRlbmdhbiBtZWFsaW5mbyAoYmVyZGFzYXJrYW4gbWVhbF9pZCkgZGFuIGZjZW50ZXIgKGJlcmRhc2Fya2FuIGNlbnRlcl9pZCkuDQoNCg0KYGBge3J9DQojIGRhdGEgdHJhaW4NCg0KZGF0YXRyYWluIDwtIG1lcmdlKHRyYWluLCBtZWFsaW5mbywgYnkgPSAibWVhbF9pZCIpDQpkYXRhdHJhaW4gPC0gbWVyZ2UoZGF0YXRyYWluLCBmY2VudGVyLCBieSA9ICJjZW50ZXJfaWQiKQ0KDQojIGRhdGEgdGVzdA0KDQpkYXRhdGVzdCA8LSBtZXJnZSh0ZXN0LCBtZWFsaW5mbywgYnkgPSAibWVhbF9pZCIpDQpkYXRhdGVzdCA8LSBtZXJnZShkYXRhdGVzdCwgZmNlbnRlciwgYnkgPSAiY2VudGVyX2lkIikNCg0KIyB1cnV0a2FuIGJlcmRhc2Fya2FuIG1pbmdndQ0KDQpkYXRhdHJhaW4gPC0gZGF0YXRyYWluICU+JSANCiAgYXJyYW5nZSh3ZWVrKQ0KDQpoZWFkKGRhdGF0cmFpbikNCmBgYA0KDQpEYXRhIFN0cnVjdHVyZQ0KDQpgYGB7cn0NCmdsaW1wc2UoZGF0YXRyYWluKQ0KYGBgDQoNClRyYW5zZm9ybWluZyBEYXRhIFR5cGUNCg0KYGBge3J9DQpkYXRhdHJhaW4gPC0gIGRhdGF0cmFpbiAlPiUgDQogIG11dGF0ZV9hdCh2YXJzKGNpdHlfY29kZSwgcmVnaW9uX2NvZGUpLCBhcy5mYWN0b3IpDQpnbGltcHNlKGRhdGF0cmFpbikNCmBgYA0KDQpNaXNzaW5nIFZhbHVlDQoNCmBgYHtyfQ0KY29sU3Vtcyhpcy5uYShkYXRhdHJhaW4pKQ0KYGBgDQoNCkNoZWNrIE91dGxpZXJzDQoNCmBgYHtyfQ0KIyBvdXRsaWVycyByYXcgZGF0YQ0KDQpsZW5ndGgoYm94cGxvdChkYXRhdHJhaW4kbnVtX29yZGVycykkb3V0KQ0KYGBgDQoNCg0KYGBge3J9DQojIG91dGxpZXJzIHNjYWxlZCBkYXRhDQoNCmxlbmd0aChib3hwbG90KGxvZyhkYXRhdHJhaW4kbnVtX29yZGVycykpJG91dCkNCmBgYA0KDQpEYXRhIG91dGxpZXIgcGFkYSB0YXJnZXQgdmFyaWFiZWwgbWVuY2FwYWkgcHVsdWhhbiByaWJ1LiBVbnR1ayBpdHUsIGJlYmVyYXBhIHZhcmlhYmVsIG51bWVyaWvigJN0ZXJtYXN1ayB0YXJnZXTigJNwZXJsdSBkaXRyYW5zZm9ybWFzaSBtZW5nZ3VuYWthbiBzY2FsZS9sb2cuIFRhbXBhayBwZXJiZWRhYW4ganVtbGFoIGRhdGEgb3V0bGllciB5YW5nIGN1a3VwIHNpZ25pZmlrYW4gc2V0ZWxhaCB0YXJnZXQgdmFyaWFiZWwgZGl0cmFuc2Zvcm1hc2kgbWVuZ2d1bmFrYW4gbG9nLiBMZWJpaCBqYXVoIGxhZ2ksIGJlYmVyYXBhIHZhcmlhYmVsIG51bWVyaWsgbGFpbm55YSBqdWdhIGFrYW4gZGliZXJpIHBlcmxha3VhbiBzYW1hLiBVbnR1ayBzZWthcmFuZyBzZWthcmFuZyBraXRhIHN1ZGFoIG1lbWlsaWtpIGRhdGF0cmFpbiB5YW5nIHNpYXAgdW50dWsgZGlla3Bsb3IgZGFuIGRpdmlzdWFsaXNhc2lrYW4gYWdhciBtZW5kYXBhdCBpbmZvcm1hc2kvaW5zaWdodCB5YW5nIGJlcmd1bmEuIEJlcmlrdXRueWEgYWthbiBkaWJ1YXQgbW9kZWwgbWFjaGluZSBsZWFybmluZyBzdXBheWEgYmlzYSBtZW5naGFzaWxrYW4gcHJlZGlrc2kgeWFuZyB0ZXBhdCBwYWRhIHZhcmlhYmVsIG51bV9vcmRlcnMuDQoNCg0KIyBFWFBMT1JBVE9SWSBEQVRBIEFOQUxZU0lTDQoNCiFbXShEQVRBIEFOQUxJU0lTLnBuZykNCg0KU2F5YSBjb2JhIGJ1YXQga29sb20gYmFydSB5YW5nIG1lbnlpbXBhbiBpbmZvcm1hc2kgcGVuanVhbGFuIHNlYmFnYWkgaGFzaWwgcGVya2FsaWFuIGtvbG9tIGNoZWNrb3V0X3ByaWNlIGRhbiBudW1fb3JkZXJzLiBLb2xvbSBzYWxlcyBuYW50aW55YSBha2FuIGRpb2xhaCB1bnR1ayBtZW5naGFzaWxrYW4gdmlzdWFsaXNhc2kgamVuaXMgbWFrYW5hbiBhcGEgc2F5YSB5YW5nIGN1a3VwIGRpbWluYXRpIGRhbiBiZXJrb250cmlidXNpIHBhbGluZyBiZXNhciBwYWRhIGtlbmFpa2FuIGFuZ2thIHBlbmp1YWxhbi4NCg0KYGBge3J9DQoNCmRhdGF0cmFpbjIgPC0gZGF0YXRyYWluICU+JSANCiAgbXV0YXRlKHNhbGUgPSBjaGVja291dF9wcmljZSAqIG51bV9vcmRlcnMpDQoNCmF2cmdzYWxlcyA8LSBkYXRhdHJhaW4yICU+JSANCiAgZ3JvdXBfYnkod2VlaykgJT4lIA0KICBzdW1tYXJpc2Uoc2FsZSA9IG1lYW4oc2FsZSkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKHRleHQgPSBwYXN0ZSh3ZWVrLCI6Iixyb3VuZChzYWxlLDIpKSkNCg0KcGxvdC5hdnJnc2FsZXMgPC0gZ2dwbG90KGF2cmdzYWxlcykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gd2VlaywNCiAgICAgICAgICAgICAgICAgeSA9IHNhbGUsDQogICAgICAgICAgICAgICAgIGNvbG9yID0gc2FsZSwNCiAgICAgICAgICAgICAgICAgZmlsbCA9ICIjZmYyYTAwIikpICsNCiAgZ2VvbV9hcmVhKGFlcyh4ID0gd2VlaywgeSA9IHNhbGUpLA0KICAgICAgICAgICAgZmlsbCA9ICIjNTNiYmJkIiwgYWxwaGEgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9ICJQZW5qdWxhbiByYXRhLXJhdGEiLA0KICAgICAgIHN1YnRpdGxlID0gIm9uIHdlZWsgMS0xNDUiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dwbG90bHkocGxvdC5hdnJnc2FsZXMsIHRvb2x0aXAgPSAidGV4dCIpJT4lIA0KICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGKQ0KYGBgDQoNCkRhcmkgZ3JhZmlrIGRpIGF0YXMsIGtpdGEgYmlzYSB0YWh1IHJhdGEtcmF0YSBwZW5qdWFsYW4gdGlhcCBtaW5nZ3VueWEgKG1pbmdndSBrZSAxLTE0NSkgc2VsYWx1IG5haWsgdHVydW4sIGFkYSBwb2xhIHNlYXNvbmFsaXR5IHBhZGEgZGF0YXRyYWluLiBOYW11biBrZWFkYWFuIGluaSBiZWx1bSBiaXNhIGRpaW50ZXJwcmV0YXNpa2FuIGxlYmloIGphdWgsIHlhbmcgamVsYXMgcmF0YS1yYXRhIHBlbmp1YWxhbiBzZWphdWggaW5pIGJlcmFkYSBkaSBhbmdrYSA2MC4wMDAgaGluZ2dhIDcwLjAwMCBzZXRpYXAgbWluZ2d1Lg0KDQpgYGB7cn0NCmNhdCA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRhdGF0cmFpbiRjYXRlZ29yeSkpICU+JSANCiAgbXV0YXRlKHRleHQgPSBwYXN0ZShWYXIxLCAiOiIsIEZyZXEpKQ0KDQpwbG90LnRvcGNhdCA8LSBnZ3Bsb3QoY2F0LCBhZXMoRnJlcSwgVmFyMSkpICsNCiAgZ2VvbV9jb2woYWVzKGZpbGwgPSBWYXIxKSkgKw0KICBsYWJzKHRpdGxlID0gIkthdGVnb3JpIE1ha2FuYW4gVGVyZmF2b3JpdCIsDQogICAgICAgeSA9ICJLYXRlZ29yaSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKA0KICAgIGxpbWl0cyA9IGMoMCwgMTMwMDAwKSwgYnJlYWtzID0gc2VxKDAsIDEzMDAwMCwgNDAwMDApDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQogIA0KDQpnZ3Bsb3RseShwbG90LnRvcGNhdCwgdG9vbHRpcCA9ICJ0ZXh0IikgJT4lIA0KICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGKQ0KYGBgDQoNCkFkYSAxNCBqZW5pcyAoY2F0ZWdvcnkpIG1ha2FuYW4geWFuZyB0ZXJzZWRpYSBwYWRhIGRhdGF0cmFpbiwgZGltYW5hIEJldmVyYWdlcyBhZGFsYWggamVuaXMgeWFuZyBwYWxpbmcgc2VyaW5nIGRpcGVzYW4gb2xlaCBwZW1iZWxpLiBQb3Npc2lueWEgc2FuZ2F0IHVuZ2d1bCBqYXVoIGRpYmFuZGluZyBqZW5pcyBtYWthbmFuIGxhaW4uIEtlYWRhYW4gaW5pIGJpc2EgZGlwZW5nYXJ1aGkga2FyZW5hIGtlY2VuZGVydW5nYW4gb3Jhbmctb3JhbmcgeWFuZyBsZWJpaCBzdWthIG1lbmdvbnN1bXNpIG1ha2FuYW4gcmluZ2FuIChjYW1pbGFuKSBrZXRpbWJhbmcgbWFrYW5hbiBiZXJhdC4gTmFtdW4gZGVuZ2FuIHBlbmp1YWxhbiBCZXZlcmFnZXMgc2ViYW55YWsgbGViaWggZGFyaSAxMjAuMDAwIHBlc2FuYW4sIGFwYWthaCBiZXJhcnRpIGplbmlzIG1ha2FuYW4gaW5pIGJlcmtvbnRyaWJ1c2kgcGFsaW5nIGJlc2FyIGRhbGFtIG1lbmluZ2thdGthbiBhbmdrYSBwZW5qdWFsYW4/DQoNCmBgYHtyfQ0KdG9wc2FsZXNjYXQgPC0gZGF0YXRyYWluMiAlPiUgDQogIHNlbGVjdChjYXRlZ29yeSwgY2VudGVyX2lkLCBzYWxlKSAlPiUgDQogIGdyb3VwX2J5KGNhdGVnb3J5KSAlPiUgDQogIHN1bW1hcmlzZShzYWxlID0gbWVhbihzYWxlKSkgJT4lIA0KICBtdXRhdGUodGV4dCA9IHBhc3RlKGNhdGVnb3J5LCAiOiIsIHJvdW5kKHNhbGUsMikpKQ0KICANCnBsb3QudG9wc2FsZXMgPC0gZ2dwbG90KHRvcHNhbGVzY2F0LCBhZXMoc2FsZSwgY2F0ZWdvcnkpKSArDQogIGdlb21fY29sKGFlcyhmaWxsID0gY2F0ZWdvcnkpKSArDQogIGxhYnModGl0bGUgPSAiUGVuanVhbGFuIFRlcmF0YXMgQmVyZGFzYXJrYW4gS2F0ZWdvcmkgTWFrYW5hbiIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpnZ3Bsb3RseShwbG90LnRvcHNhbGVzLCB0b29sdGlwID0gInRleHQiKSAlPiUgDQogIGNvbmZpZyhkaXNwbGF5TW9kZUJhciA9IEYpDQpgYGANCg0KTnlhdGFueWEgdGlkYWsuIFNlY2FyYSB2aXN1YWxpc2FzaSwgY3VrdXAgamVsYXMgY2F0ZWdvcnkgbWFrYW5hbiBSaWNlIEJvd2wgbWVtYmF3YSBrZXVudHVuZ2FuIHBhbGluZyBiZXNhciBkYXJpIHNlbHVydWggZGF0YSBwZW5qdWFsYW4sIHlhaXR1IHNla2l0YXIgMTcwLjAwMC4gS2VtdWRpYW4gYWRhIFNhbmR3aWNoIGRpIHBlcmluZ2thdCBkdWEgZGFuIFBpenphIGRpIHBlcmluZ2thdCB0aWdhLiBTZWRhbmdrYW4ga29udHJpYnVzaSBCZXZlcmFnZXNjZW5kZXJ1bmcga2VjaWwsIHNla2l0YXIgNi4wMDAgc2FqYS4gTWFrYSBkYXJpIGl0dSwgcGVydXNhaGFhbiB0ZXRhcCBwZXJsdSBtZW1wZXJoYXRpa2FuIGtldGVyc2VkaWFhbiBiYWhhbiBidWthbiBoYW55YSBtYWthbmFuIHlhbmcgcGFsaW5nIHNlcmluZyBkaXBlc2FuLCB0YXBpIGp1Z2EgbWFrYW5hbiB5YW5nIHB1bnlhIGFuZ2thIHBlbmp1YWxhbiB0aW5nZ2kuDQoNCmBgYHtyfQ0KYmV2IDwtIGRhdGF0cmFpbltkYXRhdHJhaW4kY2F0ZWdvcnkgPT0gIkJldmVyYWdlcyIsXQ0KYmV2ZXJhZ2VzIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoYmV2JGN1aXNpbmUpKSAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZSh0ZXh0ID0gcGFzdGUoVmFyMSwgIjoiLCBGcmVxKSkNCg0KcGxvdC50b3BjYXQgPC0gZ2dwbG90KGJldmVyYWdlcywgYWVzKEZyZXEsIFZhcjEpKSArDQogIGdlb21fY29sKGFlcyhmaWxsID0gVmFyMSkpICsNCiAgbGFicyh0aXRsZSA9ICJLYXRlZ29yaSBNYXNha2FuIGRhbiBNaW51bWFuIiwNCiAgICAgICB5ID0gIk1hc2FrYW4iKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCiAgDQoNCmdncGxvdGx5KHBsb3QudG9wY2F0LCB0b29sdGlwID0gInRleHQiKSAlPiUgDQogIGNvbmZpZyhkaXNwbGF5TW9kZUJhciA9IEYpDQpgYGANCg0KDQpNZW5naW5nYXQgdGluZ2dpbnlhIHBlcm1pbnRhYW4gQmV2ZXJhZ2VzIG1ha2EgcGVudGluZyBiYWdpIHBlcnVzYWhhYW4gYmlzYSBtZW1lbnVoaSBwZXJtaW50YWFuIHBlbWJlbGkgZGVuZ2FuIGJhaWsuIFdhbGF1IGtvbnRyaWJ1c2lueWEgdGlkYWsgdGVybGFsdSBzaWduaWZpa2FuLCBCZXZlcmFnZXMgYmlzYSBtZW5qYWRpIGt1bmNpIHVudHVrIG1lbWJhbmd1biBrZXB1YXNhbiBiZXJiZWxhbmphIGJhZ2kgcGVtYmVsaS4gTWFrYSBkYXJpIGl0dSwgcGxvdCBkaSBhdGFzIG1lbmplbGFza2FuIGN1aXNpbmUgSXRhbGlhbiBkYW4gQ29udGluZW50YWwgc2ViYWdhaSBmYXZvcml0IHBlbWJlbGkgeWFuZyBtZW1lc2FuIEJldmVyYWdlcy4=