The F# Mentorship Schedule Planner

Hi readers,

F# advent calendar

For those who are not aware, the F# advent calendar has been initiated by Sergey Tihon a couple of years ago. I started participating in it last year and I must say that the F# community is amazing! Everyone always produces great content to share. Honestly, I could not have wished to find a better group of people with which I can continue to grow as a software developer. Okay, let’s discuss the core topic of today’s post: the F# mentorship schedule planner.

F# Software Foundation mentorship initiative

As you may be aware, the F# Software Foundation, or FSSF, has inaugurated an educative initiative to aid and to nurture F# software developers. The mentorship has been here for a couple of years now; we have completed the ninth round of mentorship a few weeks ago. You can easily be someone who has never written any F#, or be an expert; it does not matter. Everyone is welcome to join and reach out to the community!

I have been loving the program! I have been a mentee for a couple of times with the same mentor who tremendously helped sustain my growth as I was trying to get more familiar with functional programming and F#. This explains why I wanted to find ways to give back to the F# community. They have done a lot for me and I felt that it was my turn to help out too!

Late in the summer, I have become a board member of the FSSF. One of my responsibilities is to take care of the mentorship initiative. This means that I have to:

  • create the schedule for the event,
  • make sure the community is aware that it is coming,
  • update the application’s form for the new mentorship round,
  • go through all the applicants (mentees and mentors), 
    • pair the applicants that have the same F# interest(s),
    • make sure their availabilities work (time zones and weekly schedule),
  • send an email to the pairings that could be done,
  • notify the applicants that could not be matched.

Pairing applicants together

After being updated on my responsibilities for the program that I share with another board member, I started thinking of what I could do to help.

There is a huge time commitment involved in creating the pairings. My predecessor mentioned that it usually takes about 8 to 10 hours to make the pairings. For this round alone, we had about 120 applicants, which has never happened before. This meant that it would have taken even more time than before.

The thing is that I do not enjoy repetitive manual labor. As a software developer, my brain has been rewired to think about how I can bring automation to processes and help them gain efficiency by reducing waste. Being in charge of the mentorship initiative means that I am responsible for 2 rounds of mentorships. I approximated that we invested about 90 hours into that process based on the old data. I thought that we could do better and then, I started tinkering with the data.

The birth of the schedule planner

The first thing I did was to take a look at the applicant data in the Google forms. I did not know, but I could export it as a CSV document! From there, I got excited knowing that I could use the CSV Type Provider (TP) and start something that could be somehow helpful! 

After some trial and error with the TP, I managed to extract the data from the CSV document. For those who do not like TPs, I am aware that I could have gone in another direction, but I was constrained in time and liked the idea of the data being automatically formatted in types. It truly made my life easier and made it faster to develop the CLI tool. I waited until I had the data from all the applicants before tinkering with it. I only had a few days to act on a gut feeling, and I thought that I could get something to work in about 3 days.

Before going any further, I would like to take some time to thank a member of the F# community who has helped me through the implementation of the planner. Kerry has been a tremendous source of help! We have discussed many issues together in the code, did a lot of refactoring and mainly, had a ton of fun while working on this! Thank you for the help!

From there, multiple fun software design issues started peeking through as I was trying to put a domain together and pairing the applicants. One of the top things I needed to do was to separate mentees from my mentors and keep them in distinct collections.

Trying to get a working implementation 

Initially, I started thinking about the information I needed to extract from the applicants in the CSV and came up with:

type 'T nel = 'T NonEmptyList

type DayAvailability = { WeekDayName: string; UtcHours: TimeSpan list }

type CalendarSchedule = { AvailableDays: DayAvailability nel }

type PersonInformation =
    { Fullname: string
      SlackName: string
      EmailAddress: string
      MentorshipSchedule: CalendarSchedule }
with
    member x.FirstName = x.Fullname.Split(' ').[0]

After knowing what kind of information I wanted to extract for any given person, I needed to take some time to think about what would differentiate a mentee from a mentor such as:

type Mentor =
    { MentorInformation: PersonInformation
      AreasOfExpertise: FsharpTopic nel
      SimultaneousMenteeCount: uint }

type Mentee =
    { MenteeInformation: PersonInformation
      TopicsOfInterest: FsharpTopic nel }

To be paired, a mentee’s F# topic would have to match one of the areas of expertise of a mentor.

type FSharpCategory =
    | IntroductionToFSharp
    | DeepDiveInFSharp
    | ContributeToOpenSource
    | WebDevelopment
    | ContributeToCompiler
    | MachineLearning
    | DistributedSystems
    | MobileDevelopment
    | DomainModelling
    | UpForAnything
type PopularityWeight =
    | Common = 3
    | Popular = 5
    | Rare = 10
type FsharpTopic =
    { Category: FSharpCategory
      PopularityWeight: PopularityWeight }

From there, it became an interesting challenge to think about pairing people based on their weekly availabilities, local time, and if they were on daylight saving time. When submitting its application to the program, an applicant must include when they are free to meet with someone based on their own local time. One thing that made it a bit complicated to automate was that I did not have access to their time zone, only their offset with the universal time (UTC).

After trying a few things, such as discussing the topic with a NodaTime’s author, I saw that I could not automate that part and did not handle daylight saving time as part of the implementation. At that point, we could extract the mentee and mentor data as well as their weekly schedule. Now was the time to start matching them together.

One aspect that is noteworthy to discuss before we see how I pair applicants together is the impact of a selected F# topic. 

type PopularityWeight =
    | Common = 3
    | Popular = 5
    | Rare = 10

Based on the demand from the community, each topic was given a dedicated popularity weight such as “Introduction to F#” was common and working on the compiler was rare. This meant that if there was a mentor who had been matched on the intro to F# and compiler work, then the compiler work had been given priority.

Pairing applicants together

After all this work, we can now focus on the mission: pairing applicants together. For those who are interested in taking a deeper look at the implementation, you can find the repository here. Since I want to keep the post relatively short, I will not go over every little detail of the code! Although, you are welcome to take a look 🙂

With the help of a few helper methods such as:

let extractMentorshipPlannerInputs (csvDocumentFilePath: string) =
        let (unmatchedMentors, unmatchedMentees) =
            MentorshipInformation.Load csvDocumentFilePath
            |> Impl.extractPeopleInformation

        {   FullMenteeList = unmatchedMentees
            FullMentorList = unmatchedMentors
            ConfirmedMatches = []
            MatchedMenteesSet = Set.empty
            MatchedMentorSet = Set.empty
            NumberOfHoursRequiredForOverlap = 1 }
let doesMenteeMatchMentorProfile (mentor: Mentor) (mentee: Mentee) (hoursOfOverlap: int)=
    let foundScheduleOverlap = (mentee.MenteeInformation.MentorshipSchedule, mentor.MentorInformation.MentorshipSchedule, hoursOfOverlap) |||> doScheduleOverlap
    let hasAnyMatchingInterest = (mentor.AreasOfExpertise, mentee.TopicsOfInterest) ||> extractMatchingFsharpTopics |> Seq.length > 0
    
    foundScheduleOverlap && hasAnyMatchingInterest
let findAllMatchingMenteesForMentor (mentor: Mentor) (mentees: Mentee list) (hoursOfOverlap: int) =
    mentees 
    |> List.filter(fun mentee -> doesMenteeMatchMentorProfile mentor mentee hoursOfOverlap)
    |> List.map(fun mentee ->  { Mentor = mentor; Mentee = mentee; MatchingFsharpInterests = extractMatchingFsharpTopics mentee.TopicsOfInterest mentor.AreasOfExpertise } )
let findAllPotentialMentorshipMatches mentors mentees hoursOfOverlap =
    mentors 
    |> List.map(fun mentor -> findAllMatchingMenteesForMentor mentor mentees hoursOfOverlap)
    |> List.concat
    |> List.map(fun mentorshipMatch -> 
        let orderedInterestBasedOnPopularity = mentorshipMatch.MatchingFsharpInterests |> List.sortByDescending(fun fsharpTopic -> fsharpTopic.PopularityWeight)
        { mentorshipMatch with MatchingFsharpInterests = orderedInterestBasedOnPopularity }
    )
let rec getMentorshipPairing (plannerInputs: MentorshipPlannerInputs) =
        let filterToUnmatchedMentees (unmatchedMentees: Mentee list) (matchedMentees: Set<Mentee>) =
            unmatchedMentees |> List.filter(fun mentee -> matchedMentees.Contains mentee <> true)

        let filterToUnmatchedMentors (unmatchedMentors: Mentor list) (confirmedPairings: ConfirmedMentorshipApplication list) =
            unmatchedMentors |> List.filter(fun unmatchedMentor -> confirmedPairings.Any(fun x -> x.MatchedMentor = unmatchedMentor) <> true)

        match (plannerInputs.FullMentorList, plannerInputs.NumberOfHoursRequiredForOverlap) with
        | ([], _) ->
            plannerInputs.ConfirmedMatches, plannerInputs

        | (_, 0) ->
            plannerInputs.ConfirmedMatches, plannerInputs

        | _ ->
            let (confirmedMatches, matchedMenteeSet, matchedMentorsSet) = getConfirmedMatchesFromPlanner plannerInputs
            let updatedPlanner = 
                { plannerInputs with
                    FullMenteeList = filterToUnmatchedMentees plannerInputs.FullMenteeList plannerInputs.MatchedMenteesSet
                    FullMentorList = filterToUnmatchedMentors plannerInputs.FullMentorList plannerInputs.ConfirmedMatches
                    ConfirmedMatches = plannerInputs.ConfirmedMatches @ confirmedMatches
                    MatchedMenteesSet = matchedMenteeSet
                    MatchedMentorSet = matchedMentorsSet
                    NumberOfHoursRequiredForOverlap = plannerInputs.NumberOfHoursRequiredForOverlap - 1 }

            getMentorshipPairing updatedPlanner

I was able to dump in a file the pairings that the tool managed to create. Unfortunately, it was not able to perform 100% of the matchings in a single pass. The other FSSF board member in charge of the mentorship program forked my tool and created support for the lacking functionality (unfortunately I cannot remember what it was). We managed to get it working and have the system complete the pairings in the second pass.

Leveraging mathematical planning for pairings

After discussing with a few people in the community, I have learned that we could improve the implementation of the CLI tool by removing the use of heuristics. In fact, mathematical planning would be a far better tool to use in the problem space in which I find myself.

So, this sounds like a straightforward assignment problem but there is a twist. The questioner wanted pairings of rare skills to be a higher priority than pairings of more common skills. Ah, now we have a Mathematical Planning problem!

I have discussed it a bit with Matthew Crews who has been nice enough to not only implement a model using Flips but also write about it in his blog. I recommend reading the blog post as I could not do it justice to learn why mathematical planning is a better alternative approach.

Roadmap for the future

Currently, I am super happy with the turnout. For future rounds of the mentorship, we can literally wait until all the data is available and feed it to the CLI tool. It takes a few seconds to make the pairings and dump them into a file. This saves a lot of time for the FSSF who can redirect it towards more meaningful contributions to the community!

There are a few things that I wish to improve in the tool such as:

  • Sending emails to the paired applicants automatically
  • Sending emails to the applicants that could not be paired automatically
  • Adding the mentors to the private mentor Slack channel of the FSSF automatically

These would save about 2-4 hours of work per round.

Because I want to dive deeper into web development and SAFE in 2021, I want to put together a web application that will be responsible for the mentorship program. I think this shall become my main project in 2021. 

I do not want to divulge my aspirations for that web application too early. I am obligated to discuss it internally with the other board members to see what we can do about it. Honestly, if it becomes an enormous time commitment on my end too, that could also be a blocker as I do not want to spend too much time on software development outside of work. 

Closing remarks

This is my last blog entry of 2020!! We made it to the end! This has been a wild-wild west kind of year, but we have managed to survive it! Be proud of yourselves, you’ve been great 😉

I wish everyone all the best in 2021! I am hoping that it will be more a mellow year (PLEASE)

See you soon 🙂

Kevin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s