2023年9月28日: Recruiting Opsをやっていく 2を公開しました。
はじめに
自己紹介
- 株式会社エクサウィザーズでエンジニア・デザイナーポジションの採用をしている者です
- マーケティング/PRがバックグラウンドで、現在は採用人事をメインにしています
- 技術者だったことはありません (予防線)
Recruiting Opsってなに
なんでしょうね...
要約
- レポーティングを自動化した
- その結果どういうことができるようになったか
- 今後やっていきたいこと(ダッシュボード化、より高次のインサイトの抽出etc)
課題
1.採用は自動化されていないオペレーションだらけ
オペレーションの重要性が高い一方で、よりよい活動のためには個別化(パーソナライズ)が必須という状況。モチベーションとしては適切なオペレーションの自動化を通して、個別化を限界までしたいわけです。
2.エクサウィザーズは採用目標人数に対して採用チームが小さく、効率化 or Die
年間100人以上の採用目標があって、2020年4月〜2021年3月の1年間で5,948件の応募がありました。2020年度の営業日は246日だそうなので、24.2件/日の応募があったことになります。この応募数に対して、約4名のリクルーターと、2名弱のバックオフィスで対応しています。
しかしチームは肥大化させたくないのです。これは経営のわがままとかではなく、チームが大きく組織に対して金銭的コスト・コミュニケーションコストが高い状態は普通に考えてネガティブという話です。
3.適切なビジネスインサイトを取り出せる分析の基盤/ツールが無い
以上を考えるとコックピット的な、複数のレーダーが一覧できるダッシュボードのような存在が必要になるわけです。しかし「そんなもの ウチにはないよ...」とのこと...。
4.ゆえに属人化を生み、スケーラブルではない構造に陥りかけている
当たり前ですね。我々はもっと採用活動を可視化し、センシングし、改善し、標準化していくという当たり前のことを、当たり前にする必要があります。主語が大きくなったので今回は「可視化とセンシング」にフォーカスを絞ります。
#やったこと
ようやくここまで辿り着きました。まずはレポーティングの自動化です。
使ったものと構造
- Python3
- Selenium
- Pandas
- Webhook API
- Automator
シンプルですがこういう感じです。
Python+Seleniumで採用データを蓄積しているWebアプリケーションからcsvデータを取得して、Pandasで加工、HTML形式で吐き出してWebhook経由でTeamsに投稿する、という一連をMacのAutomatorで日次実行します。
APIでやればSeleniumいらないじゃんって話なのですが、APIないのですよね..
抽出する情報
下記は現状の採用アクティビティの状況に応じて優先度を決定し、必要に応じてモニタリング結果を確認〜アクションを決定できるよう、チームとしてフローを組んでいます。
n日以上アクティビティ情報に更新がない応募者
やりとりが停止してしまっているリスクがあるためです。「止まっていること」の定義には質的な判断を多く含み、企業ごとに状況が異なるためか、採用管理システムではカバーできていません。社内で独自の検出ルールを設けています。
管理を徹底していますが、毎日24件の新規応募が来る環境下だと埋もれてしまうリスクがないとは言い切れません。最重要の項目として、可能な限り仕組みでもカバーできるようにしています。
評価情報の一覧抽出
採用活動をされていない方だと何を言っているのかわからないと思うのですが、ありのままを言います、面接数がすごい数になってくると、「評価がいつ入力されたのか」に気づくことが困難になります。
(エクサウィザーズでは面接のフィードバックは必ずテキストにしてもらっています。)
「どの応募者に対して・誰が・いつ・どのような評価を入力したのか」を抽出しています。
エージェンシーのパフォーマンス・センシング
少人数でやっているというのも尚更あって、エージェンシー企業から多大な協力を得て採用活動が成立しています。
社内で(単純な)スコアリングロジックを作成していて、応募者の紹介数だけではなく選考通過率、内定数などの数値を加味したスコアリングを通して、エージェシーとのコミュニケーションを見直したり、まだ目立っていないが活躍し始めているエージェンシーを検出したりできます。
ちなみに、自社で独自ロジックを組んでいるのは特定の時期を限定して分析するのであれば、採用管理システムのAnalytics機能があるのですが、スコアリングやトレンドラインの変化を見るのは難しいためです。
面接官の週あたり面接数のモニタリング
一時的に、特定のポジションに注力して活動することによって、ありがたくも応募が集中するケースがあります。こういった場合にボトルネックになりやすいのが面接官の面接キャパシティです。「もう空きがないので次回面接は再来週」といったことは、候補者体験(Candidate Experience: CX)の観点から見た時、避けたいわけです。
モニタリングをしておくことによって面接官のキャパシティ不足を早期に発見しようと試みています。
#結果
こういう感じで「抜け漏れがないか目を光らせている自分」が夜中にモニタリング結果を自動で吐き出してきます。
これに対して、翌朝バックオフィスチームが状況を確認していきます。
また、簡単ですがBIツールにクラウド上の集計済みファイルを同期ごとに参照してもらう形でダッシュボードをアップデートするようにしています。
まずは「漏れを回避する」「何か変化の兆しを掴む」という点をカバーすることを優先的に目指しています。
その結果、少人数のチームでも機能が強化され、属人化していた管理から一部脱却ができるようになってきました。が、正直まだまだです...。
ちなみにですが、情報が一覧されているからといって解決するわけでは当然なく、やはり「その情報を誰が確認してどうアクションに繋げていくのか」まで設計されていなければ、モニタリングの効果もないであろうと思います。
#今後やっていきたいこと
やはりモニタリングが断片的であることは否定できないので、ダッシュボード的な存在が必要ではありそうです。インターフェースを自前で作るほかにBIツールと接続するとか、MAツール使うとかももちろん検討しているのですが、現状の情報量だとなんやかんやチャットツールに繋ぎ込むのが「アップデートがわかりやすい」という意味でまだ優位に感じています。
同時に、ダッシュボードが高度化していくと読み手のリテラシーが求められてしまいそうで、例えば入社して間もないメンバーと、数年在籍しているメンバーでは取り出せる情報に差がありそうなので、この辺りをどう標準化していくのかを整備するのも必要そうですね。
こういうの好きな人いたら一緒にやりましょう。採用チームで募集してます。
採用担当(中途担当) - 株式会社エクサウィザーズ
#最後に
正直かなり恥ずかしいのですがコード公開しておきます、ひとつだけ言い訳させてください、採用管理システムのアップデートに対応する形でサグラダファミリア化してますので、いらない箇所とかきっとあります
(この汚さが逆にリアルみたいな感じで受け取ってください)
import pandas as pd
from datetime import date, timedelta
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import sys
import chromedriver_binary
#情報抽出用の期間設定に必要な変数。ところどころある謎の定義は採用管理サイトに適合させるため。
today = pd.to_datetime('today').strftime('%Y-%m-%d')
todaywithout = pd.Series(today).str.replace('-','')[0]
190daysago = (pd.to_datetime('today') - pd.Timedelta('190days')).strftime('%Y-%m-%d')
recent90days = (pd.to_datetime('today') - pd.Timedelta('90days')).strftime('%Y-%m-%d')
recent7days = (pd.to_datetime('today') - pd.Timedelta('7days')).strftime('%Y-%m-%d')
recent14days = (pd.to_datetime('today') - pd.Timedelta('14days')).strftime('%Y-%m-%d')
recent28days = (pd.to_datetime('today') - pd.Timedelta('28days')).strftime('%Y-%m-%d')
year = str(int(pd.Series(halfyearago).str.replace('-','')[0][:4]))
month = str(int(pd.Series(halfyearago).str.replace('-','')[0][4:6])).zfill(2)
day = str(int(pd.Series(halfyearago).str.replace('-','')[0][6:8])).zfill(2)
browser = webdriver.Chrome()
loginURL = ''
username = ''
password = ''
username_field = ''
password_field = ''
'''
以下略。採用管理システムのXpathを定義
'''
def login():
browser.get(loginURL)
time.sleep(2)
usernameField = browser.find_element_by_xpath(username_field)
usernameField.send_keys(username)
time.sleep(2)
usernameField.send_keys(Keys.RETURN)
time.sleep(1)
passwordField = browser.find_element_by_xpath(password_field)
passwordField.send_keys(password)
time.sleep(2)
passwordField.send_keys(Keys.RETURN)
return print("Login Successed.")
def get_evaluation_csv():
time.sleep(2)
browser.find_element_by_xpath(report_Xpath).click()
time.sleep(2)
browser.find_element_by_xpath(hyouka_csv_path).click()
time.sleep(2)
browser.find_element_by_xpath(dl_button_path).click()
time.sleep(2)
return print("Get Successed to Download Evaluation CSV.")
def get_candidates_csv():
browser.find_element_by_xpath(report_Xpath).click()
time.sleep(2)
browser.find_element_by_xpath(oubosha_csv_path).click()
time.sleep(2)
startdayselect = browser.find_element_by_xpath(startday)
startdayselect.clear()
startdayselect.send_keys(year+month+day)
browser.find_element_by_xpath(dl_button_path2).click()
login()
get_evaluation_csv()
successed_flag = True
i = 0
while True:
get_candidates_csv()
while True:
try:
csvfile = '***'.format(year+month+day,todaywithout)
with open(csvfile, encoding = "utf-16") as file:
df = pd.read_table(file, delimiter ="\t")
print("{} seconds waited".format(i))
print("Get successed to candidates csv download.")
successed_flag = False
break
except:
time.sleep(1)
i += 1
print(i,"secs...")
if i > 20:
break
if successed_flag == False:
print("done at",today)
browser.close()
break
else:
print("!!! someting error occured. !!!")
i = 0
time.sleep(3)