Modeling a hiring funnel with Systems library.
After writing the Introduction to systems thinking, I scheduled my yearly trip through the internet looking for easy to use and reasonably priced systems modeling software, and didn’t quite find what I wanted.
Aftewards, I decided to do an experiment in defining a text format for models, loosely inspired by Graphviz, and build some tools for running those models. That experiment culminated in the systems library for Python, which I’ll use here to model a hiring funnel.
We’ll start with a very simple model, and continue layering on concepts until we
have something interesting. Installation instructions are here,
but for most cases you can install via Python’s pip
module:
python3 -m pip install systems
Once you have systems
install, we can start writing our model,
starting with the simplest definition:
[Candidate] > Prospect @ 10
Prospect > PhoneScreen @ 0.5
PhoneScreen > Onsite @ 0.5
Onsite > Offer @ 0.25
Offer > Accept @ 0.5
Accept > Hired @ 1.0
Breaking this down into a few interesting details. Each line represents two stocks and one flow.
Prospect > PhoneScreen @ 0.5
For example, the line above shows the the Prospect
stock and the
PhoneScreen
stock. There is a 50% conversion flow between them,
such that each round the entire value of Prospect
will be multiplied
by 50% and that value will be added to PhoneScreen
. Prospect
itself
will be zeroed out by the conversion, but in this case it’ll be refilled
immediately by the flow from Candidate
:
[Candidate] > Prospect @ 10
Stocks surrounded by brackets, such as [Candidate]
, are infinite stocks,
typically used as the entry or exit of a system. These stand to be stand-ins
for some other system that you’re not modeling right now. This flow,
represented by the whole number 10
, will remove 10
from the Candidate
stock and add 10
to Prospect
. If the source stock has less than ten,
the flow will move up to ten instead, although in this case the source is
infinite so that’s not likely to happen.
Assuming we have written our model in hiring.txt
, we can
run the model via:
cat hiring.txt | systems-run
Which will run the model for ten rounds, showing this output:
Prospect PhoneScreen Onsite Offer Accept Hired
0 0 0 0 0 0 0
1 10 0 0 0 0 0
2 10 5 0 0 0 0
3 10 5 2 0 0 0
4 10 5 4 0 0 0
5 10 5 2 1 0 0
6 10 5 4 1 0 0
7 10 5 2 2 0 0
8 10 5 4 0 1 0
9 10 5 2 1 0 1
10 10 5 4 1 0 1
A few things to note. First, the infinite [Candidate]
stock is not rendered,
because the state of infinite stocks isn’t very interesting. Second, that getting
folks hired takes a long time! Finally, note that converion flows require that
their source reach a high enough number than the output is a whole number, which
means that you see some buffering across rounds in Onsite
or Offer
as their
preceeding flows’ inputs get to 4
and 2
respectively.
This model isn’t quite as realistic as it could be though, for example, we
don’t really get ten new prospects each day, it depends on how many sourcers
are sourcing. Also, we’re probably hiring more recruiters as well to scale
until we run out of recruiting headcount! We can model the second bit by creating a
new stock Recruiters
stock that has an initial value of 3
and a maximum
of 8
, assuming that eight is your headcount available for recruiters.
[Candidate] > Recruiters(3, 7) @ 1
In round zero we’ll have three recruiters, and we’ll add one each round until we have seven recruiters, at which point we’ll stop.
More interesting, we can use the value of the Recruiters
stock to determine
the number of prospects we find each round. For example, we could say that
each recruiter will source ten prospects per round:
[Candidate] > Prospect @ Recruiters * 10
In the first round we’ll get thity prospects, forty in the second round, up to seventy in the last round. Now when we run the model:
cat hiring.txt | systems-run
We see these results (hiding the Hires
column so the results fit on screen more easily):
Recruiter Prospect PhoneScrn Onsite Offer Accep
0 3 0 0 0 0 0
1 4 30 0 0 0 0
2 5 40 15 0 0 0
3 6 50 20 7 0 0
4 7 60 25 10 1 0
5 7 70 30 12 3 0
6 7 70 35 15 3 1
7 7 70 35 17 3 1
8 7 70 35 17 4 1
9 7 70 35 17 4 2
10 7 70 35 17 4 2
This is pretty neat, because we’ve been able to develop a fairly sophisticated model which would be rather hard to represent in a spreadsheet (particularly the backpressure of the stock maximums), and were able to do it in a couple of minutes.
If you want to do some graphing on these outputs, you can export them as CSV
cat hiring-blog.txt | systems-run --csv
Which will get results like:
,Recruiters,Prospect,PhoneScreen,Onsite,Offer,Accept
0,3,0,0,0,0,0
1,4,30,0,0,0,0
2,5,40,15,0,0,0
3,6,50,20,7,0,0
4,7,60,25,10,1,0
5,7,70,30,12,3,0
6,7,70,35,15,3,1
7,7,70,35,17,3,1
8,7,70,35,17,4,1
9,7,70,35,17,4,2
10,7,70,35,17,4,2
There is still a bunch of functionality to add to the underlying library, systems, but I’m pretty excited by the early flexibility and speed of writing.