Our Problem

Các ngành kinh tế tại Việt Nam được phân chia theo Quyết định số 27/2018/QĐ-TTg với mã ngành tương ứng. Bộ mã ngành này được sử dụng trong nhiều báo cáo cũng như nghiên cứu kinh tế. Nhiều bộ dữ liệu về doanh nghiệp như VES (Vietnam Enterprise Survey), SME (Medium Enterprise Survey) cung cấp cả thông tin của doanh nghiệp được khảo sát theo mã ngành cấp 5. Và để đối chiếu ngược là doanh nghiệp khảo sát thuộc ngành nào chúng ta phải đối với với Quyết định số 27/2018/QĐ-TTg. Để làm rõ vấn đề, trước hết chúng ta lấy ví dụ với bộ VES (download tại đây), giải nén và đọc bộ dữ liệu này:

#===================================================================
#  Data Processing Project with real-world data: VES 2015 data set
#===================================================================

# Clear work space: 
rm(list = ls())

# Import data: 

haven::read_dta("E:/dn2015.dta") -> dn2015

# Load dplyr gackage: 
library(dplyr)

# Number of columns/rows: 
dim(dn2015)
## [1] 455300    222

Bộ dữ liệu này là lớn với 455300 quan sát và 222 cột biến. Theo quy định của các cơ quan quản lí tại Việt Nam thì:

  1. Mỗi một DN chỉ có một mã số thuế duy nhất và mã số thuế này là độc nhất.
  2. Một mã số thuế hợp lệ dùng 10 chữ số.

Tuy nhiên một số MST (mã số thuế) trong bộ dữ liệu chỉ có 5 chữ số. Mặt khác, một số DN có MST trùng nhau. Chúng ta sẽ loại bỏ các quan sát này ra khỏi bộ dữ liệu ban đầu như sau:

#---------- Remove firms with duplicated tax code ---------------

dn2015 %>% 
  group_by(ma_thue) %>% 
  count() %>% 
  ungroup() %>% 
  filter(n > 1) %>% 
  pull(ma_thue) -> tax_codes_duplicated

dn2015 %>% filter(!ma_thue %in% tax_codes_duplicated) -> dn2015

#--------- Remove firms with tax code != 10 digits ----------------

library(stringr)

dn2015 %>% filter(str_count(ma_thue) == 10) -> dn2015

Theo Quyết định số 27/2018/QĐ-TTg thì mã ngành cấp 5 của doanh nghiệp sẽ phải có 5 chữ số. Tuy nhiên một số quan sát thì mã ngành chỉ có 4 chữ số. Ta có thể suy luận rằng nguyên nhân là do một số ngành có mã với số 0 đứng ở vị trí đầu tiên đã bị “biến mất” trong quá trình vào-lưu-gửi dữ liệu. Vì vậy chúng ta có thể khôi phục chứ số 0 này cho các quan sát với mã ngành chỉ có 4 chữ số như sau:

# Add 0 for 4-digits sector codes: 

dn2015 %>% 
  mutate(nganh_kd = as.character(nganh_kd)) %>% 
  mutate(nganh_kd  = case_when(str_count(nganh_kd) == 4 ~ str_c("0", nganh_kd), TRUE ~ nganh_kd)) -> dn2015

Chúng ta có thể xem qua dữ liệu sau khi xử lí:

library(knitr)

dn2015 %>% 
  head() %>% 
  select(1:7, contains("nganh")) %>% 
  kable(caption = "Table 1: Some Observations")
Table 1: Some Observations
tinh capso macs ma_thue huyen xa tthd tennganhkd nganh_kd
01 1 152 0100110528 001 00028 1 X©y Dùng 42200
01 1 465 0100100583 007 00283 1 DÖt Kim 14100
01 1 565 0100100752 020 00640 1 Sx Pin 27200
01 1 1825 0102071754 009 00355 1 Sx C¸c Sp Gç 16291
01 1 104752 0100886857 019 00625 1 X©y Dùng 42200
01 1 277 0100103785 006 00232 1 X©y Dùng 42200

Có thể thấy mã 42200 tương ứng với “Xây Dựng”. Tuy nhiên cột biến tennganhkd bị lỗi về font chữ (cho gần 450k quan sát) nên một hướng xử lí phù hợp là mapping với bảng dữ liệu mã ngành được cung cấp bởi Quyết định số 27/2018/QĐ-TTg. Nhưng nếu đi theo hướng này, chúng ta phải lấp đầy dữ liệu missing cho, ví dụ, cột 1. Trước hết chúng ta lấy dữ liệu về mã ngành của Quyết định số 27/2018/QĐ-TTg bằng đoạn R codes dưới đây:

# --------------------------------------------------------------------------------
# Extract sector code from https://dangkykinhdoanh.gov.vn/vn/Pages/NganhNghe.aspx
#---------------------------------------------------------------------------------

library(rvest)

link <- "https://dangkykinhdoanh.gov.vn/vn/Pages/NganhNghe.aspx"

link %>% 
  read_html() %>% 
  html_nodes(xpath = '//*[@id="ctl00_SPWebPartManager1_g_15a30b99_50d8_49e5_8f11_de265cb949e8"]/div/div[2]/table') %>% 
  html_table() %>% 
  .[[1]] -> df_sector_code

# Rename for columns: 

names(df_sector_code) <- c(str_c("code_level", 1:5), "sector_name")

# Remove/ filter data: 

df_sector_code %>% 
  slice(-1) %>% 
  filter(code_level1 != "21") %>% 
  mutate(sector_name_latin = stringi::stri_trans_general(sector_name, "Latin-ASCII")) -> df_sector_code

Có 21 ngành lớn (Cấp 1) theo quy định của chính phủ như ta thấy (chỉ hiển thị 6 ngành cấp 1):

df_sector_code %>% 
  filter(str_count(code_level1) != 0) %>% 
  select(code_level1, sector_name_latin) %>% 
  head() %>% 
  kable(caption = "Table 2: Some Level-1 Sectors")
Table 2: Some Level-1 Sectors
code_level1 sector_name_latin
A NONG NGHIEP, LAM NGHIEP VA THUY SAN
B KHAI KHOANG
C CONG NGHIEP CHE BIEN, CHE TAO
D SAN XUAT VA PHAN PHOI DIEN, KHI DOT, NUOC NONG, HOI NUOC VA DIEU HOA KHONG KHI
E CUNG CAP NUOC; HOAT DONG QUAN LY VA XU LY RAC THAI, NUOC THAI
F XAY DUNG

Solution 1: Using fill()

Để lấp đầy dữ liệu trống cho cột 1 chúng ta có thể sử dụng tidyr::fill() như sau:

df_sector_code %>% 
  mutate(code_level1 = case_when(str_count(code_level1) == 0 ~ NA_character_, TRUE ~ code_level1)) %>% 
  tidyr::fill(code_level1, .direction = "down") %>% 
  filter(str_detect(sector_name_latin, "[a-z]")) -> df_sector_code_filled

So sánh:

# Original data: 

df_sector_code %>% 
  select(1:5) %>% 
  head() %>% 
  kable(caption = "Table 3: Original Data")
Table 3: Original Data
code_level1 code_level2 code_level3 code_level4 code_level5
A
01
011
0111 01110
0112 01120
0113 01130
# Filled data: 
df_sector_code_filled %>% 
  select(1:5) %>% 
  head() %>% 
  kable(caption = "Table 4: Filled Data")
Table 4: Filled Data
code_level1 code_level2 code_level3 code_level4 code_level5
A 01
A 011
A 0111 01110
A 0112 01120
A 0113 01130
A 0114 01140

Solution 2: For Loop

Reinvent the wheel là một câu thành ngữ của người phương Tây ngụ ý rằng bạn không cần phải tìm ra một tool nữa để giải quyết một vấn đề khi mà đã có ai đó phát minh ra trước đó. Tuy nhiên, “phát minh lại cái bánh xe” cũng là một bài luyện tập tốt cho mục đích rèn luyện lập trình bằng ngôn ngữ R. Dưới đây là giải pháp cho vấn đề khi không sử dụng hàm fill() mà là sử dụng vòng lặp For Loop:

df_sector_code %>% mutate(ID = row.names(.)) -> df_sector_code

df_sector_code %>% filter(str_count(code_level1) == 1) -> df_sector_code_mini

df_sector_code_mini$ID %>% as.numeric() -> number_codes 

number_codes - lag(number_codes) -> number_codes

na.omit(number_codes) -> number_codes

number_codes <- c(number_codes, 2)

rep(df_sector_code_mini$sector_code_level1, times = 1, each = number_codes[1])
## NULL
n <- length(number_codes)

sector_codes <- c()

for (j in 1:n) {
  
  sector_codes <- c(sector_codes, rep(df_sector_code_mini$code_level1[j], times = 1, each = number_codes[j]))
  
}

df_sector_code %>% 
  mutate(code_level1 = sector_codes) %>% 
  filter(str_detect(sector_name_latin, "[a-z]")) -> df_sector_code_loop

Dữ liệu sau khi xử lí bằng For Loop:

df_sector_code_loop %>% 
  select(1:5) %>% 
  head() %>% 
  kable(caption = "Table 5: Filled Data using For Loop")
Table 5: Filled Data using For Loop
code_level1 code_level2 code_level3 code_level4 code_level5
A 01
A 011
A 0111 01110
A 0112 01120
A 0113 01130
A 0114 01140

Dữ liệu mã ngành sau khi được xử lí missing được mapping với VES 2015 như sau:

df_sector_code_filled %>% 
  filter(str_count(code_level5) != 0) %>% 
  select(sector_name_latin, nganh_kd = code_level5) %>% 
  full_join(dn2015) -> df3

Kết quả cuối cùng (xem qua một số cột - dòng):

set.seed(1)

df3 %>% 
  select(ma_thue, kqkd1, nganh_kd, sector_name_latin) %>% 
  sample_n(10) %>% 
  kable(caption = "Table 6: Data after Pre-processing")
Table 6: Data after Pre-processing
ma_thue kqkd1 nganh_kd sector_name_latin
0312551531 2124 15120 San xuat vali, tui xach va cac loai tuong tu, san xuat yen dem
0106896869 14 46412 Ban buon tham, dem, chan, man, rem, ga trai giuong, goi va hang det khac
0312334551 7417 41000 NA
4200715179 360 71101 Hoat dong kien truc
0313384467 0 46499 Ban buon do dung khac cho gia dinh chua duoc phan vao dau
0313280676 678 15200 San xuat giay, dep
0800812265 122 55103 Nha khach, nha nghi kinh doanh dich vu luu tru ngan ngay
0104251985 89286 41000 NA
0106245472 588 46329 Ban buon thuc pham khac
0101381601 5954 10800 San xuat thuc an gia suc, gia cam va thuy san

Một số tên ngành là missing vì một số mã ngành vào thời điểm năm 2015 là không có mặt trong Quyết định số 27/2018/QĐ-TTg mới nhất. Việc này có thể có hai lí do: (1) nhiều hơn hai mã ngành của năm 2015 được gộp lại chung thành một, và (2) theo hướng ngược lại là một mã ngành của năm 2015 sau này được phân loại chi tiết hơn thành nhiều hơn thành 2, 3 ngành nhỏ hơn.

Conclusion

Data Pre-processing/Data Wrangling là khâu mất nhiều thời gian của một dự án phân tích dữ liệu. Post này không thể cover hết mọi khía cạnh của quá trình này.

LS0tDQp0aXRsZTogIkZpbGwgaW4gTWlzc2luZyBWYWx1ZXMgd2l0aCBQcmV2aW91cyBWYWx1ZTogQ2FzZSBvZiBWRVMgKFZpZXRuYW0gRW50ZXJwcmlzZSBTdXJ2ZXkpIERhdGEgU2V0Ig0KYXV0aG9yOiAnQXV0aG9yOiBOZ3V5ZW4gQ2hpIER1bmcnDQpzdWJ0aXRsZTogRGF0YSBQcmUtcHJvY2Vzc2luZyBTZXJpZXMNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4NCiAgICB0aGVtZTogZmxhdGx5DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUUpDQoNCmBgYA0KDQoNCg0KIVtdKEM6XFxVc2Vyc1xcQWRtaW5cXERvY3VtZW50c1xcc2VjdG9yX2NvZGUucG5nKQ0KDQoNCg0KYGBge3J9DQoNCmBgYA0KDQojIE91ciBQcm9ibGVtDQoNCkPDoWMgbmfDoG5oIGtpbmggdOG6vyB04bqhaSBWaeG7h3QgTmFtIMSRxrDhu6NjIHBow6JuIGNoaWEgdGhlbyBbUXV54bq/dCDEkeG7i25oIHPhu5EgMjcvMjAxOC9RxJAtVFRnXShodHRwczovL2RhbmdreWtpbmhkb2FuaC5nb3Yudm4vSW1hZ2VzL2VkaXRvci9maWxlcy8yN18yMDE4X1ElQzQlOTBfVFRnX25nYW5obmdoZWtpbmh0ZS5wZGYpIHbhu5tpIG3DoyBuZ8OgbmggdMawxqFuZyDhu6luZy4gQuG7mSBtw6MgbmfDoG5oIG7DoHkgxJHGsOG7o2Mgc+G7rSBk4bulbmcgdHJvbmcgbmhp4buBdSBiw6FvIGPDoW8gY8WpbmcgbmjGsCAgbmdoacOqbiBj4bupdSBraW5oIHThur8uIE5oaeG7gXUgYuG7mSBk4buvIGxp4buHdSB24buBIGRvYW5oIG5naGnhu4dwIG5oxrAgVkVTIChWaWV0bmFtIEVudGVycHJpc2UgU3VydmV5KSwgIFNNRSAoTWVkaXVtIEVudGVycHJpc2UgU3VydmV5KSBjdW5nIGPhuqVwIGPhuqMgdGjDtG5nIHRpbiBj4bunYSBkb2FuaCBuZ2hp4buHcCDEkcaw4bujYyBraOG6o28gc8OhdCB0aGVvIG3DoyBuZ8OgbmggY+G6pXAgNS4gVsOgIMSR4buDIMSR4buRaSBjaGnhur91IG5nxrDhu6NjIGzDoCBkb2FuaCBuZ2hp4buHcCBraOG6o28gc8OhdCB0aHXhu5ljIG5nw6BuaCBuw6BvIGNow7puZyB0YSBwaOG6o2kgxJHhu5FpIHbhu5tpIHbhu5tpIFF1eeG6v3QgxJHhu4tuaCBz4buRIDI3LzIwMTgvUcSQLVRUZy4gxJDhu4MgbMOgbSByw7UgduG6pW4gxJHhu4EsIHRyxrDhu5tjIGjhur90IGNow7puZyB0YSBs4bqleSB2w60gZOG7pSB24bubaSBi4buZIFZFUyAoZG93bmxvYWQgW3ThuqFpIMSRw6J5XShodHRwczovL3d3dy5tZWRpYWZpcmUuY29tL2ZpbGUvbGt6dzk5eTdrM28xYWExL2RuMjAxNS5yYXIvZmlsZSkpLCBnaeG6o2kgbsOpbiB2w6AgxJHhu41jIGLhu5kgZOG7ryBsaeG7h3UgbsOgeTogDQoNCg0KYGBge3J9DQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgRGF0YSBQcm9jZXNzaW5nIFByb2plY3Qgd2l0aCByZWFsLXdvcmxkIGRhdGE6IFZFUyAyMDE1IGRhdGEgc2V0DQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQojIENsZWFyIHdvcmsgc3BhY2U6IA0Kcm0obGlzdCA9IGxzKCkpDQoNCiMgSW1wb3J0IGRhdGE6IA0KDQpoYXZlbjo6cmVhZF9kdGEoIkU6L2RuMjAxNS5kdGEiKSAtPiBkbjIwMTUNCg0KIyBMb2FkIGRwbHlyIGdhY2thZ2U6IA0KbGlicmFyeShkcGx5cikNCg0KIyBOdW1iZXIgb2YgY29sdW1ucy9yb3dzOiANCmRpbShkbjIwMTUpDQpgYGANCg0KDQpC4buZIGThu68gbGnhu4d1IG7DoHkgbMOgIGzhu5tuIHbhu5tpIDQ1NTMwMCBxdWFuIHPDoXQgdsOgIDIyMiBj4buZdCBiaeG6v24uIFRoZW8gcXV5IMSR4buLbmggY+G7p2EgY8OhYyBjxqEgcXVhbiBxdeG6o24gbMOtIHThuqFpIFZp4buHdCBOYW0gdGjDrDogDQoNCjEuIE3hu5dpIG3hu5l0IEROIGNo4buJIGPDsyBt4buZdCBtw6Mgc+G7kSB0aHXhur8gZHV5IG5o4bqldCB2w6AgbcOjIHPhu5EgdGh14bq/IG7DoHkgbMOgIMSR4buZYyBuaOG6pXQuIA0KMi4gTeG7mXQgbcOjIHPhu5EgdGh14bq/IGjhu6NwIGzhu4cgZMO5bmcgMTAgY2jhu68gc+G7kS4gDQoNClR1eSBuaGnDqm4gbeG7mXQgc+G7kSBNU1QgKG3DoyBz4buRIHRodeG6vykgdHJvbmcgYuG7mSBk4buvIGxp4buHdSBjaOG7iSBjw7MgNSBjaOG7ryBz4buRLiBN4bq3dCBraMOhYywgbeG7mXQgc+G7kSBETiBjw7MgTVNUIHRyw7luZyBuaGF1LiBDaMO6bmcgdGEgc+G6vSBsb+G6oWkgYuG7jyBjw6FjIHF1YW4gc8OhdCBuw6B5IHJhIGto4buPaSBi4buZIGThu68gbGnhu4d1IGJhbiDEkeG6p3UgbmjGsCBzYXU6IA0KDQoNCmBgYHtyfQ0KIy0tLS0tLS0tLS0gUmVtb3ZlIGZpcm1zIHdpdGggZHVwbGljYXRlZCB0YXggY29kZSAtLS0tLS0tLS0tLS0tLS0NCg0KZG4yMDE1ICU+JSANCiAgZ3JvdXBfYnkobWFfdGh1ZSkgJT4lIA0KICBjb3VudCgpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgZmlsdGVyKG4gPiAxKSAlPiUgDQogIHB1bGwobWFfdGh1ZSkgLT4gdGF4X2NvZGVzX2R1cGxpY2F0ZWQNCg0KZG4yMDE1ICU+JSBmaWx0ZXIoIW1hX3RodWUgJWluJSB0YXhfY29kZXNfZHVwbGljYXRlZCkgLT4gZG4yMDE1DQoNCiMtLS0tLS0tLS0gUmVtb3ZlIGZpcm1zIHdpdGggdGF4IGNvZGUgIT0gMTAgZGlnaXRzIC0tLS0tLS0tLS0tLS0tLS0NCg0KbGlicmFyeShzdHJpbmdyKQ0KDQpkbjIwMTUgJT4lIGZpbHRlcihzdHJfY291bnQobWFfdGh1ZSkgPT0gMTApIC0+IGRuMjAxNQ0KYGBgDQoNClRoZW8gUXV54bq/dCDEkeG7i25oIHPhu5EgMjcvMjAxOC9RxJAtVFRnIHRow6wgbcOjIG5nw6BuaCBj4bqlcCA1IGPhu6dhIGRvYW5oIG5naGnhu4dwIHPhur0gcGjhuqNpIGPDsyA1IGNo4buvIHPhu5EuIFR1eSBuaGnDqm4gbeG7mXQgc+G7kSBxdWFuIHPDoXQgdGjDrCBtw6MgbmfDoG5oIGNo4buJIGPDsyA0IGNo4buvIHPhu5EuIFRhIGPDsyB0aOG7gyBzdXkgbHXhuq1uIHLhurFuZyBuZ3V5w6puIG5ow6JuIGzDoCBkbyBt4buZdCBz4buRIG5nw6BuaCBjw7MgbcOjIHbhu5tpIHPhu5EgMCDEkeG7qW5nIOG7nyB24buLIHRyw60gxJHhuqd1IHRpw6puIMSRw6MgYuG7iyAiYmnhur9uIG3huqV0IiB0cm9uZyBxdcOhIHRyw6xuaCB2w6BvLWzGsHUtZ+G7rWkgZOG7ryBsaeG7h3UuIFbDrCB24bqteSBjaMO6bmcgdGEgY8OzIHRo4buDIGtow7RpIHBo4bulYyBjaOG7qSBz4buRIDAgbsOgeSBjaG8gY8OhYyBxdWFuIHPDoXQgduG7m2kgbcOjIG5nw6BuaCBjaOG7iSBjw7MgNCBjaOG7ryBz4buRIG5oxrAgc2F1OiANCg0KYGBge3J9DQojIEFkZCAwIGZvciA0LWRpZ2l0cyBzZWN0b3IgY29kZXM6IA0KDQpkbjIwMTUgJT4lIA0KICBtdXRhdGUobmdhbmhfa2QgPSBhcy5jaGFyYWN0ZXIobmdhbmhfa2QpKSAlPiUgDQogIG11dGF0ZShuZ2FuaF9rZCAgPSBjYXNlX3doZW4oc3RyX2NvdW50KG5nYW5oX2tkKSA9PSA0IH4gc3RyX2MoIjAiLCBuZ2FuaF9rZCksIFRSVUUgfiBuZ2FuaF9rZCkpIC0+IGRuMjAxNQ0KYGBgDQoNCkNow7puZyB0YSBjw7MgdGjhu4MgeGVtIHF1YSBk4buvIGxp4buHdSBzYXUga2hpIHjhu60gbMOtOiANCg0KYGBge3J9DQpsaWJyYXJ5KGtuaXRyKQ0KDQpkbjIwMTUgJT4lIA0KICBoZWFkKCkgJT4lIA0KICBzZWxlY3QoMTo3LCBjb250YWlucygibmdhbmgiKSkgJT4lIA0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDE6IFNvbWUgT2JzZXJ2YXRpb25zIikNCmBgYA0KDQpDw7MgdGjhu4MgdGjhuqV5IG3DoyA0MjIwMCB0xrDGoW5nIOG7qW5nIHbhu5tpICJYw6J5IEThu7FuZyIuIFR1eSBuaGnDqm4gY+G7mXQgYmnhur9uIHRlbm5nYW5oa2QgYuG7iyBs4buXaSB24buBIGZvbnQgY2jhu68gKGNobyBn4bqnbiA0NTBrIHF1YW4gc8OhdCkgbsOqbiBt4buZdCBoxrDhu5tuZyB44butIGzDrSBwaMO5IGjhu6NwIGzDoCBtYXBwaW5nIHbhu5tpIGLhuqNuZyBk4buvIGxp4buHdSBtw6MgbmfDoG5oIMSRxrDhu6NjIGN1bmcgY+G6pXAgYuG7n2kgUXV54bq/dCDEkeG7i25oIHPhu5EgMjcvMjAxOC9RxJAtVFRnLiBOaMawbmcgbuG6v3UgxJFpIHRoZW8gaMaw4bubbmcgbsOgeSwgY2jDum5nIHRhIHBo4bqjaSBs4bqlcCDEkeG6p3kgZOG7ryBsaeG7h3UgbWlzc2luZyBjaG8sIHbDrSBk4bulLCBj4buZdCAxLiBUcsaw4bubYyBo4bq/dCBjaMO6bmcgdGEgbOG6pXkgZOG7ryBsaeG7h3UgduG7gSBtw6MgbmfDoG5oIGPhu6dhIFF1eeG6v3QgxJHhu4tuaCBz4buRIDI3LzIwMTgvUcSQLVRUZyBi4bqxbmcgxJFv4bqhbiBSIGNvZGVzIGTGsOG7m2kgxJHDonk6IA0KDQpgYGB7cn0NCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgRXh0cmFjdCBzZWN0b3IgY29kZSBmcm9tIGh0dHBzOi8vZGFuZ2t5a2luaGRvYW5oLmdvdi52bi92bi9QYWdlcy9OZ2FuaE5naGUuYXNweA0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpsaWJyYXJ5KHJ2ZXN0KQ0KDQpsaW5rIDwtICJodHRwczovL2RhbmdreWtpbmhkb2FuaC5nb3Yudm4vdm4vUGFnZXMvTmdhbmhOZ2hlLmFzcHgiDQoNCmxpbmsgJT4lIA0KICByZWFkX2h0bWwoKSAlPiUgDQogIGh0bWxfbm9kZXMoeHBhdGggPSAnLy8qW0BpZD0iY3RsMDBfU1BXZWJQYXJ0TWFuYWdlcjFfZ18xNWEzMGI5OV81MGQ4XzQ5ZTVfOGYxMV9kZTI2NWNiOTQ5ZTgiXS9kaXYvZGl2WzJdL3RhYmxlJykgJT4lIA0KICBodG1sX3RhYmxlKCkgJT4lIA0KICAuW1sxXV0gLT4gZGZfc2VjdG9yX2NvZGUNCg0KIyBSZW5hbWUgZm9yIGNvbHVtbnM6IA0KDQpuYW1lcyhkZl9zZWN0b3JfY29kZSkgPC0gYyhzdHJfYygiY29kZV9sZXZlbCIsIDE6NSksICJzZWN0b3JfbmFtZSIpDQoNCiMgUmVtb3ZlLyBmaWx0ZXIgZGF0YTogDQoNCmRmX3NlY3Rvcl9jb2RlICU+JSANCiAgc2xpY2UoLTEpICU+JSANCiAgZmlsdGVyKGNvZGVfbGV2ZWwxICE9ICIyMSIpICU+JSANCiAgbXV0YXRlKHNlY3Rvcl9uYW1lX2xhdGluID0gc3RyaW5naTo6c3RyaV90cmFuc19nZW5lcmFsKHNlY3Rvcl9uYW1lLCAiTGF0aW4tQVNDSUkiKSkgLT4gZGZfc2VjdG9yX2NvZGUNCmBgYA0KDQpDw7MgMjEgbmfDoG5oIGzhu5tuIChD4bqlcCAxKSB0aGVvIHF1eSDEkeG7i25oIGPhu6dhIGNow61uaCBwaOG7pyBuaMawIHRhIHRo4bqleSAoY2jhu4kgaGnhu4NuIHRo4buLIDYgbmfDoG5oIGPhuqVwIDEpOiANCg0KYGBge3J9DQpkZl9zZWN0b3JfY29kZSAlPiUgDQogIGZpbHRlcihzdHJfY291bnQoY29kZV9sZXZlbDEpICE9IDApICU+JSANCiAgc2VsZWN0KGNvZGVfbGV2ZWwxLCBzZWN0b3JfbmFtZV9sYXRpbikgJT4lIA0KICBoZWFkKCkgJT4lIA0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDI6IFNvbWUgTGV2ZWwtMSBTZWN0b3JzIikNCmBgYA0KDQojIFNvbHV0aW9uIDE6IFVzaW5nIGZpbGwoKQ0KDQrEkOG7gyBs4bqlcCDEkeG6p3kgZOG7ryBsaeG7h3UgdHLhu5FuZyBjaG8gY+G7mXQgMSBjaMO6bmcgdGEgY8OzIHRo4buDIHPhu60gZOG7pW5nIGB0aWR5cjo6ZmlsbCgpYCBuaMawIHNhdTogDQoNCmBgYHtyfQ0KDQpkZl9zZWN0b3JfY29kZSAlPiUgDQogIG11dGF0ZShjb2RlX2xldmVsMSA9IGNhc2Vfd2hlbihzdHJfY291bnQoY29kZV9sZXZlbDEpID09IDAgfiBOQV9jaGFyYWN0ZXJfLCBUUlVFIH4gY29kZV9sZXZlbDEpKSAlPiUgDQogIHRpZHlyOjpmaWxsKGNvZGVfbGV2ZWwxLCAuZGlyZWN0aW9uID0gImRvd24iKSAlPiUgDQogIGZpbHRlcihzdHJfZGV0ZWN0KHNlY3Rvcl9uYW1lX2xhdGluLCAiW2Etel0iKSkgLT4gZGZfc2VjdG9yX2NvZGVfZmlsbGVkDQoNCmBgYA0KDQpTbyBzw6FuaDogDQoNCmBgYHtyfQ0KIyBPcmlnaW5hbCBkYXRhOiANCg0KZGZfc2VjdG9yX2NvZGUgJT4lIA0KICBzZWxlY3QoMTo1KSAlPiUgDQogIGhlYWQoKSAlPiUgDQogIGthYmxlKGNhcHRpb24gPSAiVGFibGUgMzogT3JpZ2luYWwgRGF0YSIpDQoNCiMgRmlsbGVkIGRhdGE6IA0KZGZfc2VjdG9yX2NvZGVfZmlsbGVkICU+JSANCiAgc2VsZWN0KDE6NSkgJT4lIA0KICBoZWFkKCkgJT4lIA0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDQ6IEZpbGxlZCBEYXRhIikNCmBgYA0KDQoNCiMgU29sdXRpb24gMjogRm9yIExvb3ANCg0KW1JlaW52ZW50IHRoZSB3aGVlbF0oaHR0cHM6Ly9kaWN0aW9uYXJ5LmNhbWJyaWRnZS5vcmcvZGljdGlvbmFyeS9lbmdsaXNoL3JlaW52ZW50LXRoZS13aGVlbCkgbMOgIG3hu5l0IGPDonUgdGjDoG5oIG5n4buvIGPhu6dhIG5nxrDhu51pIHBoxrDGoW5nIFTDonkgbmfhu6Ugw70gcuG6sW5nIGLhuqFuIGtow7RuZyBj4bqnbiBwaOG6o2kgdMOsbSByYSBt4buZdCB0b29sIG7hu69hIMSR4buDIGdp4bqjaSBxdXnhur90IG3hu5l0IHbhuqVuIMSR4buBIGtoaSBtw6AgxJHDoyBjw7MgYWkgxJHDsyBwaMOhdCBtaW5oIHJhIHRyxrDhu5tjIMSRw7MuIFR1eSBuaGnDqm4sICJwaMOhdCBtaW5oIGzhuqFpIGPDoWkgYsOhbmggeGUiIGPFqW5nIGzDoCBt4buZdCBiw6BpIGx1eeG7h24gdOG6rXAgdOG7kXQgY2hvIG3hu6VjIMSRw61jaCByw6huIGx1eeG7h24gbOG6rXAgdHLDrG5oIGLhurFuZyBuZ8O0biBuZ+G7ryBSLiBExrDhu5tpIMSRw6J5IGzDoCBnaeG6o2kgcGjDoXAgY2hvIHbhuqVuIMSR4buBIGtoaSBraMO0bmcgc+G7rSBk4bulbmcgaMOgbSBgZmlsbCgpYCBtw6AgbMOgIHPhu60gZOG7pW5nIHbDsm5nIGzhurdwIEZvciBMb29wOiANCg0KDQpgYGB7cn0NCmRmX3NlY3Rvcl9jb2RlICU+JSBtdXRhdGUoSUQgPSByb3cubmFtZXMoLikpIC0+IGRmX3NlY3Rvcl9jb2RlDQoNCmRmX3NlY3Rvcl9jb2RlICU+JSBmaWx0ZXIoc3RyX2NvdW50KGNvZGVfbGV2ZWwxKSA9PSAxKSAtPiBkZl9zZWN0b3JfY29kZV9taW5pDQoNCmRmX3NlY3Rvcl9jb2RlX21pbmkkSUQgJT4lIGFzLm51bWVyaWMoKSAtPiBudW1iZXJfY29kZXMgDQoNCm51bWJlcl9jb2RlcyAtIGxhZyhudW1iZXJfY29kZXMpIC0+IG51bWJlcl9jb2Rlcw0KDQpuYS5vbWl0KG51bWJlcl9jb2RlcykgLT4gbnVtYmVyX2NvZGVzDQoNCm51bWJlcl9jb2RlcyA8LSBjKG51bWJlcl9jb2RlcywgMikNCg0KcmVwKGRmX3NlY3Rvcl9jb2RlX21pbmkkc2VjdG9yX2NvZGVfbGV2ZWwxLCB0aW1lcyA9IDEsIGVhY2ggPSBudW1iZXJfY29kZXNbMV0pDQoNCm4gPC0gbGVuZ3RoKG51bWJlcl9jb2RlcykNCg0Kc2VjdG9yX2NvZGVzIDwtIGMoKQ0KDQpmb3IgKGogaW4gMTpuKSB7DQogIA0KICBzZWN0b3JfY29kZXMgPC0gYyhzZWN0b3JfY29kZXMsIHJlcChkZl9zZWN0b3JfY29kZV9taW5pJGNvZGVfbGV2ZWwxW2pdLCB0aW1lcyA9IDEsIGVhY2ggPSBudW1iZXJfY29kZXNbal0pKQ0KICANCn0NCg0KZGZfc2VjdG9yX2NvZGUgJT4lIA0KICBtdXRhdGUoY29kZV9sZXZlbDEgPSBzZWN0b3JfY29kZXMpICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3Qoc2VjdG9yX25hbWVfbGF0aW4sICJbYS16XSIpKSAtPiBkZl9zZWN0b3JfY29kZV9sb29wDQoNCmBgYA0KDQpE4buvIGxp4buHdSBzYXUga2hpIHjhu60gbMOtIGLhurFuZyBGb3IgTG9vcDogDQoNCmBgYHtyfQ0KZGZfc2VjdG9yX2NvZGVfbG9vcCAlPiUgDQogIHNlbGVjdCgxOjUpICU+JSANCiAgaGVhZCgpICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSA1OiBGaWxsZWQgRGF0YSB1c2luZyBGb3IgTG9vcCIpDQpgYGANCg0KROG7ryBsaeG7h3UgbcOjIG5nw6BuaCBzYXUga2hpIMSRxrDhu6NjIHjhu60gbMOtIG1pc3NpbmcgxJHGsOG7o2MgbWFwcGluZyB24bubaSBWRVMgMjAxNSBuaMawIHNhdTogDQoNCmBgYHtyfQ0KDQpkZl9zZWN0b3JfY29kZV9maWxsZWQgJT4lIA0KICBmaWx0ZXIoc3RyX2NvdW50KGNvZGVfbGV2ZWw1KSAhPSAwKSAlPiUgDQogIHNlbGVjdChzZWN0b3JfbmFtZV9sYXRpbiwgbmdhbmhfa2QgPSBjb2RlX2xldmVsNSkgJT4lIA0KICBmdWxsX2pvaW4oZG4yMDE1KSAtPiBkZjMNCmBgYA0KDQpL4bq/dCBxdeG6oyBjdeG7kWkgY8O5bmcgKHhlbSBxdWEgbeG7mXQgc+G7kSBj4buZdCAtIGTDsm5nKTogDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCg0KZGYzICU+JSANCiAgc2VsZWN0KG1hX3RodWUsIGtxa2QxLCBuZ2FuaF9rZCwgc2VjdG9yX25hbWVfbGF0aW4pICU+JSANCiAgc2FtcGxlX24oMTApICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSA2OiBEYXRhIGFmdGVyIFByZS1wcm9jZXNzaW5nIikNCg0KYGBgDQoNCk3hu5l0IHPhu5EgdMOqbiBuZ8OgbmggbMOgIG1pc3NpbmcgdsOsIG3hu5l0IHPhu5EgbcOjIG5nw6BuaCB2w6BvIHRo4budaSDEkWnhu4NtIG7Eg20gMjAxNSBsw6Aga2jDtG5nIGPDsyBt4bq3dCB0cm9uZyBRdXnhur90IMSR4buLbmggc+G7kSAyNy8yMDE4L1HEkC1UVGcgbeG7m2kgbmjhuqV0LiBWaeG7h2MgbsOgeSBjw7MgdGjhu4MgY8OzIGhhaSBsw60gZG86ICgxKSBuaGnhu4F1IGjGoW4gaGFpIG3DoyBuZ8OgbmggY+G7p2EgbsSDbSAyMDE1IMSRxrDhu6NjIGfhu5lwIGzhuqFpIGNodW5nIHRow6BuaCBt4buZdCwgdsOgICgyKSB0aGVvIGjGsOG7m25nIG5nxrDhu6NjIGzhuqFpIGzDoCBt4buZdCBtw6MgbmfDoG5oIGPhu6dhIG7Eg20gMjAxNSBzYXUgbsOgeSDEkcaw4bujYyBwaMOibiBsb+G6oWkgY2hpIHRp4bq/dCBoxqFuIHRow6BuaCBuaGnhu4F1IGjGoW4gdGjDoG5oIDIsIDMgbmfDoG5oIG5o4buPIGjGoW4uICANCg0KIyBDb25jbHVzaW9uDQoNCkRhdGEgUHJlLXByb2Nlc3NpbmcvRGF0YSBXcmFuZ2xpbmcgbMOgIGtow6J1IG3huqV0IG5oaeG7gXUgdGjhu51pIGdpYW4gY+G7p2EgbeG7mXQgZOG7sSDDoW4gcGjDom4gdMOtY2ggZOG7ryBsaeG7h3UuIFBvc3QgbsOgeSBraMO0bmcgdGjhu4MgY292ZXIgaOG6v3QgbeG7jWkga2jDrWEgY+G6oW5oIGPhu6dhIHF1w6EgdHLDrG5oIG7DoHkuIA0KDQoNCg0KDQoNCg0KDQoNCg==