Setiap kantor yang menghargai diri sendiri secara teratur memonitor upah untuk bernavigasi di segmen pasar tenaga kerja yang menarik minatnya. Namun, terlepas dari kenyataan bahwa tugas itu perlu dan penting, tidak semua orang siap membayar layanan pihak ketiga untuk ini.
Dalam hal ini, untuk menyelamatkan SDM dari kebutuhan untuk secara teratur menyortir ratusan lowongan dan resume, lebih efisien untuk menulis aplikasi kecil satu kali yang akan melakukannya sendiri, dan pada output memberikan hasil dalam bentuk dasbor yang indah dengan tabel, grafik, kemampuan untuk menyaring dan mengunggah data. Sebagai contoh, ini:
Anda dapat menonton langsung (dan bahkan menekan tombol) di sini .
Dalam artikel ini, saya akan berbicara tentang bagaimana saya menulis aplikasi seperti itu, dan apa perangkap yang saya temui di sepanjang jalan.
Pernyataan masalah
Diperlukan untuk menulis aplikasi yang akan mengumpulkan data pekerjaan hh.ru dan resume untuk posisi tertentu (Back-end / Front-end / pengembang penuh-tumpukan, DevOps, QA, Manajer Proyek, Analis Sistem, dll.) Di St. Petersburg dan memberikan nilai minimum, rata-rata, dan maksimum dari ekspektasi gaji dan penawaran untuk spesialis tingkat junior, menengah, dan senior untuk masing-masing profesi ini.
Seharusnya memperbarui data kira-kira setiap enam bulan, tetapi tidak lebih dari sebulan sekali.
Prototipe pertama
Ditulis dalam mengkilap murni, dengan tata letak bootstrap yang indah, pada pandangan pertama itu keluar sangat banyak: sederhana, dan yang paling penting - dapat dimengerti. Halaman utama aplikasi berisi yang paling diperlukan: untuk setiap spesialisasi, nilai rata-rata gaji dan harapan gaji (tingkat menengah) tersedia, ada juga tanggal pembaruan data terakhir dan tombol Perbarui. Tab di header - berdasarkan jumlah spesialisasi yang dipertimbangkan - berisi tabel dengan data dan grafik yang lengkap.
Jika pengguna melihat bahwa data belum terlalu lama diperbarui, ia menekan tombol "Perbarui" untuk spesialisasi yang sesuai. Daun aplikasi ke bawah sadar berpikir selama 5 menit, karyawan pergi minum kopi. Setelah kembali, menunggu data yang diperbarui di halaman utama dan pada tab yang sesuai.
Pertanyaan untuk pengujian sendiri: apa yang salah dengan prototipe ini?Paling tidak, untuk memperbarui data pada semua sembilan spesialisasi, pengguna perlu mengklik tombol Perbarui pada setiap ubin - dan sembilan kali.
Mengapa tidak membuat satu tombol "Perbarui" untuk semuanya? Faktanya adalah - dan ini adalah masalah kedua - bahwa untuk setiap permintaan ("perbarui dan proses data pada manajer", "perbarui dan proses data pada QA", dll.) Butuh 5-10 menit , yang dengan sendirinya tidak diizinkan untuk waktu yang lama. Satu permintaan untuk memperbarui semua data akan berubah 5 menit menjadi 45, atau bahkan semua 60. Pengguna tidak bisa menunggu terlalu lama.
Bahkan beberapa fungsi withProgress()
yang membungkus proses pengumpulan dan pemrosesan data dan membuat harapan pengguna lebih bermakna dengan cara ini tidak menghemat banyak situasi.
Masalah ketiga dengan prototipe ini adalah bahwa jika kita menambahkan selusin lagi profesi (well, bagaimana jika) kita akan dihadapkan dengan kenyataan bahwa tempat di header berakhir .
Tiga alasan ini sudah cukup bagi saya untuk benar-benar memikirkan kembali pendekatan untuk membangun aplikasi dan UX. Jika Anda menemukan lebih banyak - jangan ragu untuk berkomentar.
Prototipe ini juga memiliki kekuatan, yaitu:
- Pendekatan umum untuk antarmuka dan logika bisnis: alih-alih salin-tempel, kami menghapus bagian yang sama menjadi fungsi terpisah dengan parameter.
Sebagai contoh, ini adalah bagaimana "ubin" dari satu spesialisasi terlihat di halaman utama:
Kode tile <- function(title, midsal = NA, midsalres = NA, total.res = NA, total.vac = NA, updated = NA) { return( column(width = 4, h2(title), strong(" (middle):"), midsal, br(), strong(" (middle):"), midsalres, br(), strong(" :"), total.res, br(), strong(" : "), total.vac, br(), strong(" : "), updated, br(), br(), actionButton(inputId = paste0(tolower(prof), "Btn"), label = "Update", class = "btn-primary") ) ) }
- Pembentukan dinamis UI hingga pengidentifikasi (inputId) dalam kode, melalui
inputId = paste0(, "Btn")
, lihat contoh di atas. Pendekatan ini terbukti sangat nyaman, karena perlu menginisialisasi dengan selusin kontrol, dikalikan dengan jumlah profesi. - Berhasil :)
Data yang dikumpulkan disimpan dalam file .csv untuk berbagai profesi ( append = TRUE
), dan kemudian dibaca dari sana ketika aplikasi diluncurkan. Ketika data baru muncul, mereka ditambahkan ke file yang sesuai, dan nilai rata-rata dihitung ulang.
Beberapa kata tentang pemisah
Nuansa penting: pemisah standar untuk file csv - koma atau titik koma - tidak terlalu cocok untuk kasus kami, karena Anda sering dapat menemukan lowongan dan resume dengan judul seperti "Shvets, reaper, igrets (duda; html / css)". Karena itu, saya segera memutuskan untuk memilih sesuatu yang lebih eksotis, dan pilihan saya jatuh |
Semuanya berjalan dengan baik sampai waktu berikutnya saya mulai, saya tidak menemukan tanggal di kolom dengan mata uang, dan kemudian kolom bergerak turun dan, akibatnya, grafik penguncian. Saya mulai mengerti. Ternyata, sistem saya rusak oleh seorang gadis cantik - "Analis Data | Analis Bisnis". Sejak itu saya telah menggunakan \x1B
sebagai pembatas, karakter ESC. Masih belum dikecewakan.
Tetapkan atau tidak tetapkan?
Saat mengerjakan proyek ini, fungsi assign menjadi penemuan nyata bagi saya: Anda dapat secara dinamis menghasilkan nama-nama variabel dan bingkai tanggal lainnya, keren!
Tentu saja, saya ingin menyimpan sumber data dalam bingkai data terpisah untuk lowongan yang berbeda. Dan saya tidak ingin menulis "designer.vac = data.frame (...), analyst.vac = data.frame (...)". Oleh karena itu, kode untuk menginisialisasi objek-objek ini ketika saya memulai aplikasi tampak seperti ini:
Tentukan profs <- c("analyst", "designer", "developer", "devops", "manager", "qa") for (name in profs) { if (!exists(paste0(name, ".vac"))) assign(x = paste0(name, ".vac"), value = data.frame( URL = character() # , id = numeric() # id , Name = character() # , City = character() , Published = character() , Currency = character() , From = numeric() # . , To = numeric() # . , Level = character() # jun/mid/sen , Salary = numeric() , stringsAsFactors = FALSE )) }
Namun kegembiraan saya tidak bertahan lama. Tidak mungkin lagi untuk mengakses objek seperti itu di masa depan melalui parameter tertentu, dan ini, memaksa, menyebabkan duplikasi kode. Pada saat yang sama, jumlah objek tumbuh secara eksponensial, dan sebagai hasilnya, menjadi mudah untuk menjadi bingung di dalamnya dan dalam menetapkan panggilan.
Jadi saya harus menggunakan pendekatan yang berbeda, yang akhirnya menjadi jauh lebih sederhana: menggunakan daftar.
Inisialisasi paket bingkai data? Mudah! profs <- list( devops = "devops" , analyst = c("systems+analyst", "business+analyst") , dev.full = "full+stack+developer" , dev.back = "back+end+developer" , dev.front = "front+end+developer" , designer = "ux+ui+designer" , qa = "QA+tester" , manager = "project+manager" , content = c("mathematics+teacher", "physics+teacher") ) for (name in names(profs)) { proflist[[name]] <- data.frame( URL = character() # , id = numeric() # id , Name = character() # , City = character() , Published = character() , Currency = character() , From = numeric() # . , To = numeric() # . , Level = character() # jun/mid/sen , Salary = numeric() , stringsAsFactors = FALSE ) }
Harap perhatikan bahwa alih-alih vektor biasa dengan nama profesi, seperti sebelumnya, saya menggunakan daftar, yang pada saat yang sama menyertakan permintaan pencarian, yang mencari data lowongan dan resume untuk profesi tertentu. Jadi saya berhasil menghilangkan saklar jelek ketika memanggil fungsi pencarian pekerjaan.
Membuat tabel N dan grafik N dari frame data ini dalam satu gerakan? Hm ...Juga, secara umum, tidak sulit. Berikut ini contoh bola dalam vakum untuk server.R:
lapply(seq_along(my.list.of.data.frames), function(x) { output[[paste0(names(my.list.of.data.frames)[x], ".dt")]] <- renderDataTable({ datatable(data = my.list.of.data.frames[[names(my.list.of.data.frames)[x]]]() , style = 'bootstrap', selection = 'none' , escape = FALSE) }) output[[paste0(names(my.list.of.data.frames)[x], ".plot")]] <- renderPlot( ggplot(na.omit(my.list.of.data.frames[[names(my.list.of.data.frames)[x]]]()), aes(...)) ) })
Oleh karena itu kesimpulannya: daftar adalah hal yang sangat nyaman yang memungkinkan Anda untuk mengurangi jumlah kode dan waktu untuk memprosesnya. (Karena itu, tidak menetapkan.)
Dan pada saat itu ketika saya terganggu dari refactoring pada pembicaraan Joe Cheng tentang dashboard , itu datang ...
Memikirkan kembali
Ternyata di R ada paket khusus, dipertajam untuk pembuatan dashboard - shinydashboard . Ini juga menggunakan bootstrap dan membuatnya sedikit lebih mudah untuk mengatur UI dengan sidebar ringkas yang dapat sepenuhnya tersembunyi tanpa conditionalPanel()
, yang memungkinkan pengguna untuk fokus mempelajari data.
Ternyata jika SDM memeriksa data setiap enam bulan sekali, mereka tidak memerlukan tombol Pembaruan. Tidak ada sama sekali. Ini bukan persis "dasbor statis", tetapi dekat dengan itu. Skrip pembaruan data dapat diterapkan sepenuhnya secara terpisah dari aplikasi mengkilap dan menjalankannya sesuai dengan jadwal dengan Penjadwal standar Windows OS Anda.
Ini memecahkan dua masalah sekaligus: menunggu lama (jika Anda secara teratur menjalankan skrip di latar belakang, pengguna bahkan tidak akan melihat pekerjaannya, tetapi hanya selalu melihat data baru) dan tindakan berlebihan yang diperlukan dari pengguna untuk memperbarui data. Dulu mengambil sembilan klik (satu untuk setiap spesialisasi), sekarang dibutuhkan nol. Tampaknya kita telah mencapai perolehan efisiensi, berjuang untuk tak terbatas!
Ternyata kode di berbagai bagian aplikasi dieksekusi dalam jumlah yang tidak sama. Saya tidak akan membahas hal ini secara rinci, jika Anda mau, lebih baik membiasakan diri dengan penjelasan visual dalam laporan . Saya hanya akan menguraikan gagasan utama: memanipulasi data di dalam ggplot (), evil on the fly, dan semakin banyak kode yang dapat Anda bawa ke tingkat atas aplikasi, semakin baik. Produktivitas pada saat yang sama tumbuh di waktu.
Bahkan, semakin jauh saya melihat laporan itu, semakin jelas saya menyadari betapa kode dalam prototipe pertama saya tidak dikelola oleh Feng Shui, dan pada beberapa titik menjadi jelas bahwa proyek itu lebih mudah untuk ditulis ulang daripada untuk refactor. Tetapi bagaimana cara meninggalkan gagasan Anda ketika begitu banyak usaha telah diinvestasikan di dalamnya?
Apa yang sudah mati tidak bisa mati
- Saya memikirkan dan menulis ulang proyek dari awal, dan kali ini
- mengirimkan seluruh kode untuk mengumpulkan data tentang lowongan dan resume (pada kenyataannya, seluruh proses ETL) ke dalam skrip terpisah yang dapat dijalankan secara independen dari aplikasi yang mengkilap, menyelamatkan pengguna dari menunggu yang membosankan;
- menggunakan reactiveFileReader () untuk membaca data yang dikumpulkan sebelumnya dari file csv, memastikan relevansi sumber data dalam aplikasi saya tanpa perlu me-restart dan tindakan pengguna yang tidak perlu;
- menyingkirkan assign () untuk bekerja dengan daftar dan secara aktif menggunakan lapply () di mana ada loop sebelumnya;
- aplikasi UI yang didesain ulang menggunakan shinydashboard, sebagai bonus - tidak perlu khawatir tentang kurangnya ruang di layar;
- beberapa kali mengurangi total volume aplikasi (dari ~ 1800 hingga 360 baris kode).
Sekarang solusinya bekerja sebagai berikut.
- Skrip ETL dijalankan sebulan sekali (di sini adalah instruksi tentang cara melakukan ini) dan dengan hati-hati melewati semua profesi, mengumpulkan data mentah tentang lowongan dan resume dari jj.
Selain itu, data lowongan diambil melalui API situs (saya dapat menggunakan kembali sebagian kode dari proyek sebelumnya ), tetapi untuk setiap resume saya harus mengurai halaman web menggunakan paket rvest, karena akses ke metode API yang sesuai sekarang telah dibayar. Anda bisa menebak bagaimana ini memengaruhi kecepatan skrip. - Data yang dikumpulkan disisir - prosesnya dijelaskan secara rinci dan dengan contoh kode di sini . Data yang diproses disimpan ke disk dalam file terpisah dari hist form / profesi-hist-vac.csv dan hist / profesi-hist-res.csv. Omong-omong, pencilan dalam data seperti ini dapat menyebabkan hal-hal lucu, berhati-hatilah :)
Untuk setiap profesi, skrip mengambil file yang ditambah dengan data historis, memilih yang paling relevan - yang tidak lebih dari sebulan dari tanggal pembaruan terakhir - dan menghasilkan file csv baru dari formulir data .res / profesi-res-recent.csv dan data.vac / profesi -vac-recent.csv. Aplikasi terakhir juga berfungsi dengan data ini ... - ... yang, setelah memulai, membaca konten resume dan folder pekerjaan (data.res dan data.vac, masing-masing), dan kemudian memeriksa setiap jam untuk perubahan pada file. Melakukan ini dengan reactiveFileReader () jauh lebih efisien dalam hal sumber daya dan kecepatan eksekusi daripada menggunakan invalidateLater (). Jika ada perubahan dalam file, maka tabel dengan data sumber diperbarui secara otomatis, dan nilai rata-rata dan grafik dihitung ulang, karena mereka bergantung pada Nilai reaktif (), yaitu, tidak ada kode tambahan yang diperlukan untuk menangani situasi ini.
- Di halaman utama sekarang ada tabel yang menunjukkan nilai min, median, dan maks dari ekspektasi gaji dan penawaran untuk setiap spesialisasi untuk setiap level yang ditemukan (semua untuk TK). Selain itu, Anda dapat melihat grafik di tab dengan informasi terperinci dan mengunggah data dalam format .xlsx (Anda tidak pernah tahu angka-angka ini diperlukan untuk SDM).
Itu saja. Ternyata satu-satunya tombol yang sekarang tersedia untuk pengguna di dasbor kami adalah tombol Unduh. Dan ini menjadi lebih baik: semakin sedikit pengguna memiliki tombol, semakin sedikit kesempatan melempar pengecualian bingung di dalamnya.
Alih-alih sebuah epilog
Hari ini, aplikasi ini mengumpulkan dan menganalisis data hanya untuk St. Petersburg. Mempertimbangkan bahwa pemangku kepentingan utama puas, dan reaksi yang paling sering adalah "hebat, tetapi dapatkah ini dilakukan ke Moskow?", Saya menganggap percobaan ini sukses.
Anda dapat melihat aplikasi di tautan ini , dan semua kode sumber (bersama dengan contoh file selesai) tersedia di sini .
By the way, aplikasi ini disebut Salary Monitor, disingkat Salmon - "salmon".