The complete code can be found at: https://github.com/MichaelAHood/fitrep
Disclaimer: If you use this script as the sole means to generate your student FITREPS’s you are not doing them any favors, and you are probably a bad instructor.
With that out of the way let me introduce my little project.
I currently work as an ROTC instructor.
Note: For the those who are unfamiliar with the Navy, a FITREP is the Navy equivalent of the civilian performance review.
As I was writing last semester’s student FITREPS I noticed that my students always submitted FITREP information using the wrong formatting. In the Navy we are really big on formatting.
Considering that another instructor and I had given the upperclass students thorough training — on multiple occasions — on how to format and submit underclass FITREPs, I was frustrated.
My first thought was to kick the FITREPs back to the upperclassmen who prepared them and have them correct the mistakes. Given that it was the end of the year, finals were in progress, and the seniors had pretty much checked out mentally — as evidenced by the poor quality of work they just submitted — this would have been an exercise in futility.
As I was looking through page after page of incorrectly formatted FITREPs, my initial annoyance steadily built into a murderous rage. I was able to subdue my desire for murder long enough to observe a few common mistakes: confusion over which words should or should not be capitalized, omitting spaces, forgetting to hyphenate certain parts, using commas instead of semicolons, etc. A lot of trivial little things that — when added up — would require several hours for me and the other instructors to correct them all.
Suddenly I had a revelation!
Maybe I could use my new-found Python programming powers to write a script to fix these formatting issues.
I opened up my Python IDE and banged out my first script in about 45 min. This script was simply a series of procedures for string operations. The script took the block of text as an input, split up the text into constituent words, capitalized everything except for the articles (e.g. “of”, “for”, “the”, “and” etc), placed a comma at the end of each activity and rebuilt the whole text block.
I coped the mangled text that my students submitted for their reports, ran the script, and — voila! — clean, properly formatted text was output in my IDE (see below):
import formatter text = """jones society officer-5; liaison to the Hugo project-5; Colorguard-5; intramural basketball-3, Intramural Soccer-2, tutor at City Center for the homeless children’s center-2, eucharistic minister and lector for dorm mass-9, trident Naval Society-5; PFA: 14–1""" formatter.format_text(text)
Out: “ Jones Society Officer-5, Liaison to the Hugo Project-5, Colorguard-5, Intramural Basketball-3, Intramural Soccer-2, Tutor at City Center for the Homeless Children’s Center-2, Eucharistic Minister and Lector for Dorm Mass-9, Community Service Society-5, PFA: 14–1,”
I was feeling pretty smug at this point. Not to mention that it is just a ton of fun to write a program that will actually do something useful, however trivial it may be.
Then, I had a much more ambitious idea: Would it be possible to create another script that could automatically generate my comments for each of my students FITREPs?
Before I go any farther, I want to explain that the way we write FITREPs in NROTC is unique and not representative of the rest of the Navy. The Navy uses FITREPs as a promotion and advancement tool, rather than a true evaluation tool.
So, while on the surface a FITREP resembles a feedback and evaluation method, it is not. The feedback and evaluation process happens via counseling sessions and frequent informal meetings between senior and subordinate.
As an instructor, I sit down with my students and actually discuss their performance a minimum of twice a semester. In actuality, I meet with them twice formally, and informally four to six times — sometimes more for my problem children. In these meetings, we discuss specific actions that they can take to improve across all domains: academics, military aptitude, professional conduct, physical fitness, social skills, etc.
For NROTC, using a FITREP is a way of formally documenting the various data points that we collect on students, e.g. grades, conduct issues, physical fitness tests, etc.
Additionally, writing these reports each semester for an entire class takes anywhere from 15–20 hours per instructor. With four instructors I have the potential to realize an 80 man-hour cost savings!
Here is how I went about creating a program to automatically write FITREPs.
Having been an NROTC instructor for several years now, I have developed a formulaic approach to writing FITREP comments for students.
My comment block usually consists of 4–5 sentences, and look something like this:
Sentence 1: “Student Jones has had a difficult time and finished the term in the bottom third of his class.”
Sentence 2: “As an engineering student, he finished the term with a 2.43 GPA and earned a 65 on the physical readiness test.”
Sentence 3: “Additionally, he had conduct problems and was repeatedly late for physical fitness training.”
Sentence 4: “To improve his ranking, he was counseled to devote more energy to his grades and punctuality, while ensuring that he complies with unit regulations.”
Sentence 5: “Student Smith is an extremely charismatic officer candidate, but he must focus on meeting program requirements if he wants to earn a commission. He is ranked 27 of 30 in his class.”
I started by taking a standard excel spreadsheet that I use for tracking my students performance. The names have been anonymized by replacing them with fake names. In my search for fake names, I found a ready supply by drawing on my extensive knowledge of hard alcohol gained from 10 years of naval service.
The comment writer first starts by opening the CSV file as a Pandas DataFrame.
Note: I probably could have used the CSV module instead of Pandas, but I just recently learned Pandas, so I felt like using it. (http://pandas.pydata.org/)
import pandas df = pandas.open_csv("data.csv")
For reading the values of individual dataframe cells, the df.at() method was useful:
sex = df.at[0, ‘Sex’] sex Out: ‘M’
Next, I wrote a simple procedure, get_pronouns(), to initialize a list of gender appropriate pronouns:
fitrep.get_pronouns(df, 0) Out: [‘he’, ‘his’, ‘He’, ‘His’] fitrep.get_pronouns(df, 6) Out: [‘she’, ‘her’, ‘She’, ‘Her’]
Having an array of pronouns comes in handy when building sentences… Something I took for granted until I started teaching my two year old to speak in grammatically correct sentences.
Similarly, I wrote several helper procedures that return the value of other useful cells:
get_gpa() # returns the term gpa get_major() # returns the students major get_prt() # returns the most recent physical fitness score get_conduct() # returns ‘Y’ or ‘N’ for conduct problems get_tardy() # returns ‘Y’ or ‘N’ for excessive tardies get_third() # returns which third of the class they are in (e.g. first, middle, last)
For example, I can now construct sentences of the form:
“%s earned a score of %d on the most recent physical fitness test.” % (pronouns, prt_score)
When run, this code returns:
“He earned a score of 85 on the most recent physical fitness test.”
The next step is to write the introductory line of the comment block. The introductory line states the name of the student and expresses a general sentiment about that student’s performance. To do this, I found it helpful to group the students into one of five cases: first in their class, top third, middle third, bottom third, and dead last.
The procedure make_intro_line() will take as inputs the DataFrame and the index of the student in question ,and return an introductory sentence:
fitrep.make_intro_line(df, 0) Out: ‘MIDN Thomas has finished the semester as the top midshipman in the Freshmen class!’ fitrep.make_intro_line(df, 22) Out: ‘MIDN Makers has finished the semester in the bottom third of the Freshmen class.’
Next, I want something more tangible about the students performance. The second part of the comment block is a sentence that addresses the students current major, term GPA, and how they performed on the recent physical fitness test. Here are two examples:
fitrep.make_second_line(df, 1) Out: “As an Engineering major, he earned a 3.37 GPA for the semester and earned a score of ‘OUTSTANDING’ on the PRT.” fitrep.make_second_line(df, 18) Out: ‘As an Engineering major, she earned an impressive 3.748 GPA for the semester earned a score of 60 on the PRT.’
The function will also return additional comments if the student is not meeting the program or the program’s academic standards:
fitrep.make_second_line(df, 23) Out: ‘As an Engineering major, he struggled academically with a 2.211 GPA and is failing to meet NROTC standards. he earned a score of 60 on the PRT.’
Or, if their GPA is really in the dumps:
fitrep.make_second_line(df, 25) Out: ‘As a Communications major, he struggled academically with a 1.912 GPA and is failing to meet both NROTC and University standards. He earned a score of 55 on the PRT.’
We now have two parts of the three part comment complete.
Coding the final line was the most difficult part. This is the sentence where I like to make a specific recommendation on what the student can do to improve their performance and ranking in the class.
I created a simple list of boolean conditions that determine appropriate suggestions based on the student’s performance data.
For example, if a student earned less than a 3.4 GPA, the function returns a recommendation for improving grades. Similarly the function will return recommendations for a student to improve physical fitness based on the score that a student earned on the PRT.
fitrep.make_last_line(df, 25) Out: ‘To improve his ranking, he should concentrate on improving his academic performance and his physical fitness.’
I also thought it would be useful to write a new procedure called professional_comment(). professional_comment() will return a sentence that comments on whether or not the student had conduct problems, e.g. underage drinking, trouble with the law, or they just decided to join the Poor Decisions Club. The function will also return whether or not the student had multiple unauthorized absences from military obligations.
Here is an example:
fitrep.professional_comment(df, 25) Out: ‘He needs to ensure that he complies with NROTC regulations and attends all military obligations. ‘
Incorporating professional_comment() into make_last_line() gives something like this:
fitrep.make_last_line(df, 25) Out: ‘To improve his ranking, he should concentrate on improving his academic performance and his physical fitness. He needs to ensure that he complies with NROTC regulations and attends all military obligations. ‘
All of the pieces for a complete comment have been created. It’s just a matter of putting them together into one single chunk of text.
Now I finalize the process… It s a simple matter to write another procedure complete_comment() that takes make_intro_line(), make_second_line(), make_last_line(), and the students overall class ranking, and returns a final comment.
This comment can be used as a boilerplate, i.e. the starting point or 80% solution, for a student write-up (to truly do it right, requires human intervention to personalize the comments).
fitrep.complete_comment(df, 2) Out: ‘MIDN Yani has finished the semester in the top third of the Freshmen class. As a Biology major, he earned an impressive 3.596 GPA for the semester earned a score of 60 on the PRT. To improve his ranking, he should concentrate on improving his physical fitness and his involvement in the Battalion while finding ways to assume positions of greater responsibility. As a result of his performance, he is ranked 3 of 26 in his class.’ fitrep.complete_comment(df, 25) Out: ‘MIDN Dodson has finished the semester in the bottom third of the Freshmen class. As a Communications major, he struggled academically with a 1.912 GPA and is failing to meet both NROTC and University standards. He earned a score of 55 on the PRT. To improve his ranking, he should concentrate on improving his academic performance and his physical fitness. He needs to ensure that he complies with NROTC regulations and attends all military obligations. As a result of his performance, he is ranked 26 of 26 in his class.’
Now that I have a script that can automatically generate boilerplate comments for the reports, I want to make this process even easier and create another script that can iterate through a list of names, call complete_comment(), and then write the output text to a file that I can share with my coworkers.
The last step is especially important because it gives my coworkers the impression that I have magical computer hacker skills that are completely beyond their comprehension. While this is not true, they don’t know it. I’m fine with that.
The last step of iterating through each student name and writing the comments to a CSV file was actually pretty easy. With a little bit of googling I was able to find the CSV module and learn how to use the CSV.writer and the CSV.writerow methods.
fitrep – A collection of scripts to automate error correction and comment generation for my student FITREPsgithub.com
Here is a simple example of the whole thing in action:
import writer What is the name of the file with the student data in it? Note: This file must end in ‘.csv’. Ex: student_data.csv : data.csv What is the name of the file that you want the comments written to? Note: Your filename must end in ‘.csv’. Ex: my_comments.csv : comments.csv Comments successfully written to comments.csv.
Pretty simple, huh?
Just to show what the output data looks like, I’ll open up the newly created file (“comments.csv”) and show you:
Now those comments can be copied and pasted directly into the FTIREP. Easy day.
Below is a list of things I want to add to make this program to make it more user friendly (not in any particular order):
-Create a graphical user interface, so that it is easy for “non-computer people.” Downloading Python, installing Pandas and the associated dependencies, and then running the script from the command line, is asking a lot of people who just want the thing “to work.”
-Not use Pandas. I just happened to use Pandas because I knew how to use it to create dataframes and operate on data. Pandas is awesome, but it seems a little “heavy” — for lack of a better word — for this use. I’m positive there is another simpler tool out there.
-Introduce random variation in the sentences. This will introduce more variety and make the comments seem more organic, rather than scripted.
-Have the writer.py module write directly to the comment block (Block 42) of the FITREP. This would be cool as the effect would be that the program “just somehow made the comments appear” in the FITREP.
Please feel free to add any comments, suggestions, or feedback. Or email me at firstname.lastname@example.org.