Thursday, May 21, 2015

Write hookscripts using Python


hookscript supports Python!

Since we opened for private beta, Python has been our most requested feature.

You can now use Python to run a web app, consume an API, or respond to webhooks. Please share your use cases in the comments below. We love to hear how users utilize hookscript.

Read the Python Documentation for hookscript. Then write your own code.

Want to speed up support for your favorite language? Visit our open source language support page.

Friday, May 1, 2015

Case Study: Mileage Log for Work Using Automatic Webhooks

SUMMARY:
  • Automatically log kilometers driven for work & personal use
  • Script runs only when a trip is finished
  • Respond to webhooks generated by Automatic


BACKGROUND:
Kenny uses his personal car for some work trips. He needs to keep track of work driving vs personal driving in order to claim tax deduction.

PROBLEM:
Manually keeping a log is a pain and he always forgets. Kenny wants to streamline the process and make it more accurate.

ANSWER:
Use Automatic dongle to monitor car trips. Uses hookscript to determine if trip was work/personal and store the data.

STACK:
Code is written in Perl. Car monitoring and webhook generated by Automatic. Data stored with hookscript's built-in persistent state.

Kenny owns an Automatic dongle to monitor his driving. Automatic can generate a webhook (post data to a url of your choosing) anytime a trip is finished. The webhook contains data on start location, end location, and date/time of trip.

The trip JSON data looks like this:
{"trip":
    {"id":"T_79f8283e16a1f288",
        "start_location":{
            "lat":39.100000,
            "lon":-105.100000,
            "accuracy_m":73.45800018310547
        },
        "end_location":{
            "lat":39.000000,
            "lon":-105.000000,
            "accuracy_m":6.0
        },
        "start_time":1429305158409,
        "end_time":1429305470878,
        "start_time_zone":"America/Boise",
        "end_time_zone":"America/Boise",
        "distance_m":2802.3056640625
    }
}
After coding, hookscript provides a url. Any requests to that url run Kenny's code. Kenny uses his script's url as Automatic's webhook destination.
https://www.runhook.com/kenny_unique_script_id_here
The script receives the data via HTTP Post and extracts the important info like start and end location, day of the week, and driving distance.
use Hookscript;
use JSON::XS qw( decode_json );

if ($req->method eq 'POST') {
    # automatic webhook sends POST request to this script's url
    # with JSON data with trip details
    # if script receives a POST request process the trip

    my $json = $req->content->asset->slurp;       # grab JSON
    my $d = decode_json($json);                   # decode it
    my $start = $d->{'trip'}->{'start_location'}; # convenience for repeated usage
    my $end =   $d->{'trip'}->{'end_location'};   # convenience for repeated usage

    # starting latitude and longitude
    # round to 4 decimal places to keep location accurate but allow
    # slight differences depending on where car is parked (garage, street, etc)
    my $start_lat = sprintf('%.4f', $start->{'lat'});
    my $start_lon = sprintf('%.4f', $start->{'lon'});

    # ending latitude and longitude
    my $end_lat = sprintf('%.4f', $end->{'lat'});
    my $end_lon = sprintf('%.4f', $end->{'lon'});

    # meters driven for this trip
    my $meters_driven = $d->{'trip'}->{'distance_m'};

    # day of week trip started.
    my $start_time = $d->{'trip'}->{'start_time'};
    my $weekday = (localtime($start_time))[6]; # 6 is weekday portion of time
For Kenny, all driving on the weekend is for personal reasons. And the IRS doesn't allow deductions for driving to or from work (that's considered commuting instead of work driving).
    # home latitude and longitude rounded for privacy
    my $home_lat = 39.0000;
    my $home_lon = -105.0000;
    my $trip_type;

    # work driving is driving that didn't start or end at home
    my $starts_at_home = $home_lat == $start_lat && $home_lon == $start_lon;
    my $ends_at_home   = $home_lat == $end_lat && $home_lon == $end_lon;

    # and took place during a weekday
    my $is_weekend     = $weekday == 6 || $weekday == 0;

    if ( $starts_at_home || $ends_at_home || $is_weekend ) {
        $trip_type = 'not_for_work';
    } else {
        $trip_type = 'for_work';
    }
hookscript has built-in data storage using persistent state.

After each trip is classified, store the meters driven into 'work' or 'not work' categories.
    # use trip_type as the key for storing meters driven
    # add meters driven to previous total
    $state->{$trip_type} += $meters_driven;
Kenny needs to access the data every year for his taxes. A GET request to the same unique url will show the current data.
} else {
    # use a GET request to show current data

    # display data as HTML
    $res->headers->content_type('text/html');

    # convert meters into kilometers with 2 decimals
    my $km_for_work = sprintf('%.2f', $state->{'for_work'}/1000);
    my $km_not_for_work = sprintf('%.2f', $state->{'not_for_work'}/1000);

    print "<h1>KMs driven for work</h1>\n";
    print "<p>For work: $km_for_work</p>\n";
    print "<p>Not for work: $km_not_for_work</p>\n";
}
Sample of results:


Using hookscript, Kenny was able to:
  • Accurately log distance driven for every trip
  • Forget about recording the log data manually
  • Stop tallying the data for taxes every year


Complete code:
use Hookscript;
use JSON::XS qw( decode_json );

if ($req->method eq 'POST') {
    # automatic webhook sends POST request to this script's url
    # with JSON data with trip details
    # if script receives a POST request process the trip

    my $json = $req->content->asset->slurp;
    my $d = decode_json($json);
    my $start = $d->{'trip'}->{'start_location'};
    my $end =   $d->{'trip'}->{'end_location'};

    # starting latitude and longitude
    # round to 4 decimal places to keep location accurate but allow
    # slight differences depending on where car is parked at home (garage, street, etc)
    my $start_lat = sprintf('%.4f', $start->{'lat'});
    my $start_lon = sprintf('%.4f', $start->{'lon'});

    # ending latitude and longitude
    my $end_lat = sprintf('%.4f', $end->{'lat'});
    my $end_lon = sprintf('%.4f', $end->{'lon'});

    # meters driven for this trip
    my $meters_driven = $d->{'trip'}->{'distance_m'};

    # day of week trip started.
    my $start_time = $d->{'trip'}->{'start_time'};
    my $weekday = (localtime($start_time))[6]; # 6 is weekday portion of time

    # home latitude and longitude rounded for privacy
    my $home_lat = 39.0000;
    my $home_lon = -105.0000;
    my $trip_type;

    # work driving is driving that didn't start or end at home
    my $starts_at_home = $home_lat == $start_lat && $home_lon == $start_lon;
    my $ends_at_home   = $home_lat == $end_lat && $home_lon == $end_lon;

    # and took place during a weekday
    my $is_weekend     = $weekday == 6 || $weekday == 0;

    if ( $starts_at_home || $ends_at_home || $is_weekend ) {
        $trip_type = 'not_for_work';
    } else {
        $trip_type = 'for_work';
    }

    # use trip_type as the key for storing meters driven
    # add meters driven to previous total
    my $prev_meters = $state->{$trip_type};
    $state->{$trip_type} = $prev_meters + $meters_driven;

} else {
    # if GET request show current data

    # display data as HTML
    $res->headers->content_type('text/html');

    # convert meters into kilometers with 2 decimals
    my $km_for_work = sprintf('%.2f', $state->{'for_work'}/1000);
    my $km_not_for_work = sprintf('%.2f', $state->{'not_for_work'}/1000);

    print "<h1>KMs driven for work</h1>\n";
    print "<p>For work: $km_for_work</p>\n";
    print "<p>Not for work: $km_not_for_work</p>\n";
}