I’ll be the first to admit that my dad did my SAT book’s website (; I did nothing but proofread and offer wordsmithing suggestions. As part of the website design process, my dad and I decided that we should do some “AB” testing of website sales copy, meaning that we would display one of two possible websites to our users at random.

My dad complained to me that most of the existing AB testing solutions were either too difficult to use or were far too complex for the simple act of choosing a page to show to a user. I decided to fix that: I wrote a jQuery plugin called SimpleAB that would switch views in and out. And it only switches views in and out.

The premise is simple: add the class “simpleab-#” (where # is a natural number) to all the elements that you wish to switch in and out with SimpleAB. Elements with the same number will be shown together. That way, you can separate the text and structure of your elements of the page and only AB test the parts you want to.

SimpleAB also has some other nice features:

  • You can choose whether or not to “persist” a view. That is, SimpleAB will set a cookie that will cause the user to always see the same view.
  • You can customize the SimpleAB class name, so that if “simpleab-#” doesn’t float your boat, you can use “mycoolwebsite-#” instead!
  • Even if you persist views, the “flip” feature lets you make sure that all your views look beautiful by iterating through the views as you refresh your SimpleAB page.

Want to see more? SimpleAB is MIT licensed! Check it out on my GitHub:

Reason for my Absence: Freshman Fall

The good news is that I’m done with school until February; I’ve been on break since the middle of December; everything has gone very well since I left. Still, I feel as though I should update my blog–even during the school year–from now on. I learned tons and tons of interesting things this fall, and I’d love to share them!

Oh yeah, and one of my friends (my neighbor, in fact) has started blogging, and I would never want to miss out on the fun. The difference is that he’s writing his own blogging platform, and I’m lazy and running WordPress.

Maybe I’ll do something cool with Django someday.

In the meantime, it’s good to be authoring some content again. I’ll start posting a lot more regularly (no, really, I will this time), especially since I’ll be authoring lots of cool software in 6.01 (Intro to Electrical Engineering and Computer Science I) next semester. Here’s what I’ve done since I left:

Wrote a Book

A long time ago, I started authoring (and nearly published) a blog post about how one could beat the SAT essay. I expanded that rough post into a much longer handbook that I lovingly call The SAT Essay Ace Template. You can get your own copy at Kindle and print (!) versions are forthcoming on Amazon!

Almost Released Hawgrade GradeSolve

As I reread my blog, I realized that I hadn’t given any of my last summer’s work nearly as much love as it deserved. I released a beta version (that I redesigned and am almost ready to release) of a web application remake of Hawgrade that I call GradeSolve. Learn more at GradeSolve‘s website.

Joined a Fraternity

Or, as my girlfriend’s father calls it, a franerdity. I’m a pledge-member of Phi Kappa Sigma at MIT. Hopefully, I’ll post that I am a brother within a few weeks.

Lived Through Freshman Year

I did well, even with Pass/No Record taken out of the picture. I’m proud of how I did.

Started Writing More Software

I’m working with a friend to expand GradeSolve into something even more useful. I’ll probably be posting a lot of code snippets from my work.

Speaking of code snippets, I’ve been working with updating Django model data with user input from ModelForms. This would normally be trivial, except I’m getting my data via AJAX: I cannot simply render a ModelForm with data in a GET request, and I don’t think it’s necessary for all relevant fields to be present in the POST data for the update.

In other words, I want to update what a user tells me to update, and leave everything else alone. Simply specifying a model instance for a ModelForm does not work. Luckily, you can create a new dictionary to which to bind the ModelForm for validation. The dictionary contains the data supplied by the user, and form fields left blank are automatically populated from the model instance. Here’s the method:

def edited_form_data_merge(post_data, model_instance, form_fields):
	data = { }
	for field in form_fields:
		if field in post_data:
			data[field] = post_data[field]
			data[field] = getattr(model_instance, field)
	return data

The method should be invoked like so (Subject is a model, SubjectForm is a ModelForm for Subject):

from x.y.z import Subject, SubjectForm, edited_form_data_merge
# ...
subject = Subject.objects.get(id = subject_id)
frm_data = edited_form_data_merge(request.POST, subject, SubjectForm._meta.fields)
form = SubjectForm(frm_data, instance = subject)
# ...

The form will always validate (assuming correct user input), and will reflect only changes: if data is not specified by a user, then data from the model is used instead.

One very interesting part of this is SubjectForm._meta.fields. This collection contains a string list of fields that makes it very easy to create the new data dictionary.

It’s good to be back!

Easy SQL Query Counting in Django

I’ve become somewhat of a Django ninja during my time as GradeSolve’s developer (okay, a Django hacker…). One of the things that I have noticed as I have written several thousand lines of Django code is that SQL queries tend to be made pretty regularly–often, a little too regularly. Some of GradeSolve’s pages were loading a little slowly for my tastes, so I decided to do some basic SQL profiling to see where the trouble was.

The issue there was that django-debug-toolbar didn’t want to play nice, and I lacked the motivation to try to get it working. Instead, I re-invented the wheel a little bit and write a simple piece of middleware to print out the SQL execution time and number of SQL queries made for a particular request. Obviously, this is going to go away in the production version of GradeSolve (which is being released in eight days!), but for the time being, I like the extra information on my debug console.

Anyway, here’s the middleware class. Stick this in one of your project’s files:

from django.db import connection
class SqlPrintMiddleware(object):
    def process_response(self, request, response):
        sqltime = 0 # Variable to store execution time
        for query in connection.queries:
            sqltime += float(query["time"])  # Add the time that the query took to the total
        # len(connection.queries) = total number of queries
        print "Page render: " + unicode(sqltime) + "sec for " + unicode(len(connection.queries)) + " queries"
        return response

Now, in your file, add the path to the middleware class. For example, if your project’s name is gradesolve and your file’s name is, your middleware classes setting would end up looking like:

    # ...
    # ...

Happy (very simple) profiling!

How to Keep Unoconv + Apache From Making You Sad

Server-side conversion of documents is a fairly common task. On Linux/Apache, doing so should be easy: you should just be able to run LibreOffice or OpenOffice in headless mode. If you’re really adventurous, you can install unoconv, which makes document conversion better[citation needed]. Anyway, you’ll be delighted to know that you can invoke any of those programs from your terminal. You won’t be delighted to know that they will return error code 77 when Apache tries to invoke them to actually convert a document. That means your server will probably return a 500 error to your users, and that means you’ll get a phone call at 3:00 in the morning from a crying user. Nobody wants to get a 3 AM phone call from a crying user.

To save yourself from such punishment, there is actually a very simple fix. First, create a new home directory for your Apache user (www-data):

sudo mkdir /home/www-data && sudo chown www-data /home/www-data

Then, you’ll need to edit your /etc/passwd file (scary stuff, right) to change the Apache user’s home directory and default shell. Doing so magically gets rid of your error code 77 troubles (I can’t explain further than that).

sudo nano /etc/passwd

Scroll down to the line that starts with “www-data.” It will look something like “www-data:x:[a number]:[a number]:[a string]:[user's home directory]:[path to user's default shell].” Obviously, we only care about the last two parts. Change the default directory to /home/www-data and then change the default shell to /bin/bash. You may need to move some of your website’s files around–I’m not sure because I use Django with mod_wsgi for GradeSolve so my files are stored elsewhere and I’m still an Apache noob.

Good luck.

Quick and Dirty Word Counting in JavaScript

Here’s an interesting problem that came up today during my Gradesolve development: counting the number of words in a particular div. It’s remarkably simple to do so (approximately), efficiently, and correctly. I pass this method the results of a $(“#myelement”).text() call to jQuery:

function wordCount(bodyText){
	var split = bodyText.split(/ |\n|-/);
	var count = 0;
	for (var i = 0; i < split.length; i++){
		if (split[i] != "") count++;
	return count;

This returns a result very close to that returned by LibreOffice. It also runs in O(n) (and Θ(n) and Ω(n)). On my hyperthreading-enabled, 3.6GHz Pentium 4 test PC, it took about 0.76ms to count the words in a 400-word document. That means counting the words in a 100,000-word novel would take 190ms or so (not including any memory allocation time) even on a relatively old machine. In other words, it’s quick!

Saving Massive IE Headaches

It shouldn’t surprise you that I’m still working on Hawgrade–now called “Gradesolve.” I’ve been making a huge amount of progress with it. Well, I suppose I should say “was making a huge amount of progress.” Today was my dreaded IE day: I’ve been testing everything in Internet Explorer.

Most of my time has been spent with the heart of the Gradesolve experience, the “Magic Editor JavaScript Engine (from Hell),” which I have lovingly named magic_editor.js. A surprising amount of the code has worked flawlessly; most of the changes resulted in something that was better in every browser (A big one was updating some jQuery .css() calls to use .offset()). My updates were coming with relative ease (read: I do not enjoy making my code cater to imaginary standards) until I hit a major snafu with the Twitter Bootstrap modal.

The problem was simple: it didn’t work. Not working, however, is easy to fix. The bigger problem was that the modal worked in the Bootstrap documentation. I was at a loss; I spent hours digging through my code, making messy tweaks, and was on the brink of throwing out the comment editing functionality when I finally decided to look at what was going on with the browser standards inside the iframe in which the magic editor does its magic.

I’ll give those of you who’ve had to go through this the doctype of the page that was inside the iframe (generated by an outside source):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

Yep. WHEN I USED IE VERSION 8 OR OLDER, THE DOCUMENT MODE WAS SET TO IE5 QUIRKS. That’s the rough equivalent of trying to run an iOS app on MS-DOS. It’s not going to work.

Luckily, the fix is relatively easy. I added this to the head of my page (it is not feasible to change the doctype of the served page):

<meta http-equiv='X-UA-Compatible' content='IE=edge'>

That forces IE to render in whatever the latest and greatest document mode for the browser mode is.

Creating Profiles with Django-registration

As part of developing Hawgrade, I’m learning the Django framework. That seemed like a noble idea about a week ago, and I’m practically competent already, but I have run into several snags so far. Major snags. Hair-pulling snags. One of the big ones was how to create a profile while simultaneously registering a user with django-registration 1.0 (FYI: I am using Python 2.7.4 with Django 1.5.1 on Ubuntu 13.04 “Raring Ringtail” x86). I’ve decided to do this post because I saw about a million questions about this topic on StackOverflow. This tutorial assumes that you have already installed django-registration and have included it in your file such that you access the django-registration app in your accounts/ url:

urlpatterns = patterns('',
    # ...
    url(r'^accounts/', include('registration.backends.default.urls')),
    # ...

We’re going to do the following:

  1. Define a model for our profile.
  2. Define a registration form that inherits from registration.forms.RegistrationForm.
  3. Attach to the django-registration user_registered signal so we know when the user registers. We save the user’s profile in the callback.
  4. Modify to use our custom registration view.

And now, we get to the Python! We’ll start in Our example profile stores whether or not a user is human.

class ExUserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    is_human = models.BooleanField()
    def __unicode__(self):
        return self.user

Next, we need the custom registration form. If you don’t already have a file named in your app, make one. You’re going to make a custom form.

from registration.forms import RegistrationForm
from django import forms
class ExRegistrationForm(RegistrationForm):
    is_human = forms.ChoiceField(label = "Are you human?:")

After the form, we need to attach to the user_registered signal. This is easily accomplished. We’re going to put the method in our file, because, well…I’m new to this…the Django documentation says so!

from registration.signals import user_registered
def user_registered_callback(sender, user, request, **kwargs):
    profile = ExUserProfile(user = user)
    profile.is_human = bool(request.POST["is_human"])

The last step is the simplest one: editing the urls in so that users can actually access your cool new registration form. It is REALLY IMPORTANT to put your new entry BEFORE the django-registration include entry.

from YOUR_APP.forms import ExRegistrationForm
from registration.backends.default.views import RegistrationView
urlpatterns = patterns('',
    # ...
        RegistrationView.as_view(form_class = ExRegistrationForm), 
        name = 'registration_register'),
    url(r'^accounts/', include('registration.backends.default.urls')),
    # ...

This code should be fairly extensible–even if you’re not very familiar with Django. One thing I have noticed is that the documentation is very thorough. Good luck!

Hawgrade: Coming soon to a school near you?

I’m sitting in my new office at RMC, writing this post with Ubuntu 13.04 on monitor #2 (I had to bring in my own NVidia GeForce GTS 250 to get two monitors to work; some gamers may note that the GTS 250 is a beefy card). I think it’s only right that I update my blog a log more frequently this summer, since it was RMC that gave rise to this blog in the first place.



So what am I working on? For one thing, probably fewer C# tutorials. I’m working on learning Python and the Django framework. To do so, I’m porting Hawgrade to Python/Django. The best part is that RMC is paying me to do so; I’m going to try to get a marketable product by the end of the summer. Right now, the process is going surprisingly well:

Looks a lot like the current version of Hawgrade, huh? I need a graphic designer...

Looks a lot like the current version of Hawgrade, huh? I need a graphic designer…

[Much more frequent] updates will continue. For now, I have a backend to finish!

Computational Chemistry is the Spice of Life

Actually, no, it isn’t. It’s pretty darn interesting, though.

Since AP Chemistry ran out of things to do (since we took the AP test), my teacher decided to assign our class a project: come up with a way to relate chemistry to your future career field. Easy, I thought. MIT has Course 6-7 (computational biology), so some sort of chemistry equivalent (computational chemistry) must exist somewhere on the Internet. As it turned out, I was spot on. Computational chemistry is big.

I started looking at computational chemistry software. All of it carried a steep learning curve and a steeper price tag, and I couldn’t really think of much high-level research, since I just finished AP Chemistry. Instead, I decided to take matters into my own hands. I thought it might be interesting to do some “exploration” around the Ideal Gas Law. At first, the goal of my project was simply to show that PV did indeed equal nRT. After I finished the first step, I started looking into why (hint: gas molecules aren’t very big). After that, it dawned on me that I could calculate an approximate value of R, the Universal Gas Constant.

What’s more, I wrote my program in Python:

#!/usr/bin/env python3
# Computational chemistry: does PV = nRT?
# Copyright (c) 2013 John Parsons. All rights reserved.
import sys
import random
import math
import copy
class Particle:
    """Gas molecule."""
    def __init__(self, vel, pos):
        """Creates a new object instance. Vel: (xVelocity, yVelocity, zVelocity). Pos = (x, y, z) coordinates."""
        # Set up variables
        self.velocity = vel
        self.position = pos
    def getListOfDistances(self, allParticlesInTank):
        """Gets a sorted list of distances between the current particle and all other particles. allParticlesInTank: list of all other particles."""
        particles = [x for x in allParticlesInTank if x.velocity != self.velocity and x.position != self.position] # Get a shallow copy of the particle list and remove the current particle
        particleDistances = [] # List of tuples that stores distances
        for particle in particles: # Loop through particles
            distance = 0
            # Quick way of implementing 3D distance formula
            for x in range(3):
                distance += (self.position[x] - particle.position[x])** 2
            distance = distance ** 0.5 # Take the square root
            particleDistances.append((particle, distance)) # Add the particle - distance tuple to the list
        return sorted(particleDistances, key = lambda x:x[1]) # Return particles sorted by their distance from the current particle
# Shameless self-promo        
print(" -- Copyright (c) 2013 John Parsons")
print("Compares simulated gas pressure with the Ideal Gas Law.")
# Print usage if user doesn't know what he's doing
if len(sys.argv) < 7:
    print("EX: python 300 16.00 1000 1000 200 True")
# Constants / calculations to make the later code more verbose
AVOGADRO_NUMBER = 6.022 * (10 ** 23)
CUBE_SIDE_WIDTH = 1000000 # Bounds of simulation, in nanometers
SURFACE_AREA_M = (6 * (CUBE_SIDE_WIDTH ** 2)) * 10**-18 
NUMBER_OF_PARTICLES = int(sys.argv[3]) # User-defined number of particles to play with
GAS_CONSTANT = 8.3144621 # R
GAS_TEMPERATURE = float(sys.argv[1]) # T
GAS_MOLAR_MASS = float(sys.argv[2]) # M (grams -- note that we must convert to KG for momentum!)
GAS_PARTICLE_MASS = GAS_MOLAR_MASS / AVOGADRO_NUMBER # Convenient mass of single particle
GAS_SPEED = (3 * GAS_CONSTANT * GAS_TEMPERATURE / (GAS_MOLAR_MASS / 1000)) ** 0.5 # (3rt/m)^.5, root-mean-square velocity of gas
TANK_VOLUME = CUBE_SIDE_WIDTH ** 3 # Volume of container in nm^3, used later.
SIMULATOR_ITERATIONS = int(sys.argv[4])
ATOMIC_RADIUS = float(sys.argv[5]) # In picometers = 10**-12m
if sys.argv[6].lower() == "true": # Whether or not to check for collisions between molecules
# Print all the simulation parameters (calculated and hard-coded) in a nicely formatted manner
print("Simulation paramaters:\r\n\tGAS_SPEED = " + str(GAS_SPEED) + " m/s\r\n\tGAS_MOLES = " + str(NUMBER_OF_PARTICLES / AVOGADRO_NUMBER) + " moles\r\n\tTANK_VOLUME = " + str(TANK_VOLUME) + " nm^3\r\n\tATOMIC_RADIUS = " + str(ATOMIC_RADIUS) + " pm\r\n\tCHECK_INTERMOLECULAR_COLLISIONS = " + str(INTERMOLECULAR_COLLISIONS) + "\r\n\tSIMULATION_TIME = " + str(SIMULATOR_ITERATIONS * 10**-8) + " s")
# Apply ideal gas law: num particles/Avogadro = n; GAS_CONSTANT = R, GAS_TEMPERATURE = T, tank_volume * 10**-24 = tank volume in m^3
print("\r\nIdeal Gas Law calculated pressure: " + str(IDEAL_PRESSURE) + " Pa\r\n")
def velocityVectorWithSpeed(speed):
    """Creates a velocity vector with random direction and specified speed."""
    x = random.randint(-500, 500) # Create random vector components
    y = random.randint(-500, 500)
    z = random.randint(-500, 500)
    magnitude = (x**2 + y**2 + z**2)**0.5 # Magnitude of vector
    unitVector = (x/magnitude, y/magnitude, z/magnitude)
    return ([i * speed for i in unitVector]) # Return vector such that its magnitude is the specified speed
def randomPosition():
    """Generates a random tuple (x, y, z) that stores a position."""
    return ([random.randint(0, CUBE_SIDE_WIDTH) for x in range(3)])
particles = [] # list of particles
# Create all our cute little particles
for i in range(0, NUMBER_OF_PARTICLES):
    particles.append(Particle(velocityVectorWithSpeed(GAS_SPEED), randomPosition()))
sumOfMomenta = 0 # Used for storing total impulse imparted to walls of container
print("Simulating: 0/" + str(SIMULATOR_ITERATIONS) + "\r", end = "")
sys.stdout.flush() # Force printing
# Now run the simulation -- each iteration will represent 10^-8 seconds
for ii in range(0, SIMULATOR_ITERATIONS): # Do some number of 10**-8 second iterations (as defined by user)
    if ii % 100 == 0: # Make it pretty as hell
        print("Simulating: " + str(ii) + "/" + str(SIMULATOR_ITERATIONS) + "\r", end="")
        sys.stdout.flush() # Make it actually print
    for particle in particles: # Loop through all the particles
        repositionVector = ([x * 10 for x in particle.velocity]) # x m/s * 10**9 nm/m * 10**-8 s/iteration = nm/iteration
        # Loop through and change positions
        for i in range(3):
            particle.position[i] = particle.position[i] + repositionVector[i] # Adjust particle position
            if particle.position[i] >= CUBE_SIDE_WIDTH or particle.position[i] <= 0: # Check to see if we need to change direction
                particle.velocity[i] = particle.velocity[i] * -1 # We do! Now we need to figure out how much impulse is imparted
                if particle.position[i] >= CUBE_SIDE_WIDTH: # Prevent the particle from being turned around again by putting the particle where it should really be.
                    particle.position[i] = CUBE_SIDE_WIDTH - (particle.position[i] - CUBE_SIDE_WIDTH) 
                    particle.position[i] = -particle.position[i]
                # p = mv (mass in kilograms). J = delta p = dp
                dp = (GAS_PARTICLE_MASS * 2 * math.fabs(particle.velocity[i])) / 1000
                sumOfMomenta += dp
        # Now we do collision detection
            distances = particle.getListOfDistances(particles)
            for distance in distances:
                if distance[1] <= ATOMIC_RADIUS * 10**-3: # If they're too close together, we have a collision
                    # We had a collision!
                    print("Collision between molecules.")
                    break # There will be no distances greater than this one
print("Simulating: " + str(ii + 1) + "/" + str(SIMULATOR_ITERATIONS), end="")
print("\n\r\nSum of momentum changes: " + str(sumOfMomenta) +" m*N")
# We ran for (10**-8 * SIMULATOR ITERATIONS) seconds; P=F/A; mv = Ft so P = mv/tA
PERCENT_ERROR = (math.fabs(IDEAL_PRESSURE - ACTUAL_PRESSURE) / IDEAL_PRESSURE) * 100 # |actual-experimental|/actual
# Let the user know how we did
print("Average pressure: " + str(ACTUAL_PRESSURE) + " Pa")
print("Pressure error: " + str(PERCENT_ERROR) + "%")
    print("Percent error was too great; Ideal Gas Law did not hold.")
    print("Percent error was reasonable; Ideal Gas Law held as expected.")
print() # Formatting
# Now calculate the simulated value of the gas constant
# PV/nT = R
GAS_CONSTANT_ERROR = (math.fabs(GAS_CONSTANT - ACTUAL_GAS_CONSTANT) / GAS_CONSTANT) * 100 # Same error formula as above
print("Calculated gas constant: " + str(ACTUAL_GAS_CONSTANT) + " m^3*Pa*K^-1*mol^-1")
print("Gas constant error: " + str(GAS_CONSTANT_ERROR) + "%")
input("Press enter to exit.")

I won’t go into any sort of massive description of my code. I will go through a few points, though.

  • You will notice from my (likely very improvable) code that adding molecular collisions will add significantly to the running time of the program. 
  • You will get a significant amount of error when molecules are travelling very quickly. This is expected. The error, however, is on the wrong side of the Ideal Gas Law. It happens because particles literally escape the “test chamber” and thus are unable to impart pressure on the inside of its walls.
  • Low temperatures also create significant error.
  • Newtonian Mechanics are exclusively used. You won’t see Schrodinger’s Equation anywhere in this program. There are a few reasons for this. First, it’s written in Python, so it’s slow to begin with. Second, I have no idea what a partial differential equation is. I might come back to this issue next year!
  • Everything in the program is based on the idea that impulse, or change in momentum, is equal to force times change in time, which is equal to mass times the change in velocity. Thus, mass times change in velocity divided by total change in time is equal to total average force. Divide that by area and you get a pressure! Woo-hoo! Physics!
  • My code probably isn’t very “pythonic.” That’s because I don’t know Python very well. Yet. I’ll get better!
  • Estimation quality and computing time, as they should be, are inversely related (as opposed to the direct relation to time spent doing computational chemistry and fun). The more molecules you simulate for more time, the better your results will be. Fewer molecules give crappier results. Numbers in the thousands for both iterations and particles seem to do okay.

Here’s the program in action. Note the relatively accurate estimation! Fun-fun-fun!

Mmmmmm. Semiconductor chemistry simulating physics. I wonder if the transistors in my CPU feel like actors.

Mmmmmm. Semiconductor chemistry simulating more chemistry. I wonder if the transistors in my CPU feel like actors.

Oh. By the way. This trial eventually finished. FYI, the accepted value of the Universal Gas Constant is 8.3144621 m^3 Pa mol^-1 K^-1. We were off by about 0.0001–we had four sig figs of perfection!

Not bad for a rough approximation that uses "10^-20 moles of gas" and runs for "0.0001 seconds!"

Not bad for a rough approximation that uses “10^-20 moles of gas” and runs for “0.0001 seconds!”


I know I haven’t posted for far too long. It’s really embarrassing, especially since I now aspire to be an admissions blogger at MIT.

Wait, what?

Yeah. I committed to MIT. I’m going to be a Beaver (or maybe an Engineer or an IHTFPer or whatever), and I couldn’t be happier! So now that the college admissions process is done until I (hopefully don’t have to) apply to grad school, what am I up to? Moreover, what have I been up to for the past month and a half? Why haven’t I been blogging?

I could post myriad excuses, and depending on how the rest of this blog post goes, I very well might. However, for the time being, I think it would be more appropriate if I directed your attention to Hawgrade!

Hi, Hawgrade!

Hi, Hawgrade! What the heck are you, anyway?

You can actually see the live version of this website at Now, to answer the caption’s snarky question, Hawgrade is grading for the Twenty First Century, developed with the help of one of the social studies teachers at my school, Dave Hawley. Mr. Hawley also goes by Hawley, hence the name Hawgrade. The website will eventually join forces with an iPad app, and the two will offer the following functionality:

  • Easy, paperless submitting of student papers.
  • Grading that stores data remotely. Teachers can keep their students’ papers anywhere!
  • Corrections for the most common student mistakes built in. This makes grading most papers as simple as highlighting passages and pressing a few buttons.
  • Recording voice comments to end the time consuming process of actually writing comments on students’ work.
  • Returning students’ corrected papers over email.

Most of that functionality has been implemented by now. It’s a wonderful culmination to my high school career, although it is unfortunate that I (a second semester senior) actually have to work on something! It’s also an enormous project. Maybe I’ll post some of my secret, proprietary code samples on this blog.

Regardless, I’ll definitely start posting more!