
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ì:
- 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.
- 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
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
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
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
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
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
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==