Derailed

Well. Didn't take long for that train to come off the tracks. I haven't gone running since last time I posted, which was about a century ago. First I got sick, then once I was better it was all icy and cold, and then I just couldn't carve out a good time. Augh.

Ah well. Reset. Try again. And again and again and again.

Long Weekend Report

We just came off a long weekend - Friday was a PA Day (which means school is closed for those of you who aren't from 'round here), and Monday was "Family Day", a merciful and much-needed mid-winter Ontario statutory holiday. The weekend was full; Friday the girls and I got together with friends. Saturday we had music class, and then Delphine, Blake and I left Cordelia with Auntie Morgan and Uncle Erik, and headed down to The Beaches for a birthday party for one of Delphine's classmates. While she was partying, we went to Dufflet and enjoyed a tasty slice of cake while an orange and white cat had a large and, I can only assume, satisfying poo in the garden outside the window.

It was great to hang out with Delphine by herself for a while. Both girls are really charming by themselves: Delphine is thoughtful and interesting, Cordelia is intense and funny. Together they are often irritating, to each other and thus to us. So to take a nice long transit ride with Delphine and get to hang out with her and see the city was a real treat. (How old do you have to be before the view from the Bloor Street Viaduct stops blowing your mind? Older than thirty-three, anyway.)

After the birthday party we came back uptown and dropped Delphine off at Baba and Zaida's, with Cordelia, for a sleepover. Then we went out to a movie! We saw Coraline, in 3D. (The movie was great but the 3D was gratuitous.)

Sunday we slept in, then went in search of breakfast. On the way home we picked up some groceries, and then relaxed for an hour or so before the girls came home. Later that day Kat came over, and the whole family joined us for Sunday dinner. After the girls were in bed, Kat and Blake and I watched every Being Erica so far. (Conclusion: good for now, but Erica is going to have to stop saying stupid things if she's going to keep our sympathies. Moving the plot along by making your protagonist repeatedly screw up is lazy writing.)

Kat slept over, and the next morning we went skating together. In addition to her already-dizzying array of talents and accomplishments, Kat is a Can-Skate instructor and figure skater, so she taught both Delphine and me some cool tricks. I now have muscle aches where I wasn't previously aware I had muscles.

Kat headed home in the afternoon (after a long and nearly fruitless search for an open grocery store), and the weekend wound down quietly.


Going out to see a movie was a nice treat, but the best part of our date was talking to Blake before the movie, and on the way home, and the next morning when we went out for breakfast. It was exactly like we were before the girls were born, a relaxed, easy repartee. We talked about all kinds of things, we laughed at each other's jokes, we worked on the cryptic and teased each other. I was literally shocked at how happy I was to be away from the children. As I alluded to above, the girls have been a little annoying lately and I guess it's been making me tenser than I realized.

No, it's worse than that. I felt more like myself when I was away from the girls. I felt like I was the real me, the me who has a tidy house and isn't mad all the time, the me who talks to her husband about something other than tomorrow's agenda and who is going to give up what in order to take somebody somewhere.

I don't want to be one of those mothers who is delighted to get away from their kids! I love my kids! They are charming and clever and fun. I'm just overwhelmed by them - five-and-a-half-years of almost non-stop childcare is a lot. Maybe next year when Delphine is in school five hours a day, and Cordelia is in school half-days I will have enough breathing space that I can properly enjoy their company. I want to like being with my kids, and I want to feel normal when I'm with them, not all stressed and crabby and resentful.

But in the meantime, thank goodness for sleepovers. No; thank Baba!

Seven Things You Probably Didn’t Know About Me

My life, such as it is, is pretty much an open book. I’m not a very complicated person, so I don’t know if I can find seven things that the average reader of this blog probably doesn’t know about me.

Still, that’s no reason not to try. So, here we go.

  1. I’ve had two jobs that didn’t pay me. After the first, I swore that if an employer didn’t pay me again, I would quit, but this last time they laid me off before I found out that I wasn’t getting paid. The most recent company swears that they’re trying to get me my money, but I’m not going to hold my breath.

  2. I hated country music from the first time I heard Billy Ray (father of Miley) Cyrus until Elizabeth Campbell sent me a CD full of the good stuff. So now the only kind of music I don’t like is “New Country”. And Schönberg. Der Mondfleck sucks.

  3. I know enough Visual Basic to write a compiler for it, but you will never ever see it on any of my resumes. Ever! My policy is to only put on my resume things that I would enjoy working with on a day-to-day basis, and VB definitely isn’t one of those things.

  4. I am almost every link but for one in the first 5 pages of Google results for “Blake Winton”. I guess that’s one of the benefits of being on the Internet for 17 years. (Well, kinda. Amy has been online as long as I have, but doesn’t show up until page 8 of the results for her name, mainly because of the Unicorns-riding-fairies artist who shares her name.)

  5. There’s a quarter of a cow in our downstairs freezer. Well, quite a bit less now that Amy and I have eaten a bunch of it. We split half a cow with some friends, and it arrived pre-chopped up, so we’ve been having at least one beef dinner per week, and often two.

  6. I download, watch, and enjoy, American Idol. Less so, now that we’re out of the “Simon says snarky things to really bad singers”, and into the “Simon says snarky things to Ryan”, but it’s still enjoyable. (Go Alexis Grace!)

  7. My first hard drive wasn’t actually 5 Meg, as I like to claim. My father bought two hard drives, and took the 5 Meg one to the office, leaving the 10 Meg one at home. The story about having 11 Meg of RAM is true, though.

Okay, that’s my seven. There were a few more I could think of, but I’m glad David didn’t run across the 25-things-about-you version, since then I would have had no choice but to resort to things you probably don’t want to know about me.

In the tradition of disbelieving in bad luck, I will now tag no-one, thus breaking the chain. (Although, if any of you are feeling verbose, feel free to pick up the meme and post a list of your own.)

Creating invoices with CouchDB.

A while ago I (and the rest of the company I worked for) was laid off, so I was forcibly thrust into the world of consulting. It’s been quite a change, keeping track of my hours and sending out invoices for the work I do. And it’s that second point that I want to talk a little more about.

Perhaps I’m too picky, but there seemed to be a fatal flaw with all the online invoicing tools I tried. One didn’t allow enough clients for me on the free plan. (Yes, I am that cheap, especially when I’ve just been laid off.) Another would have allowed the client to dispute the invoice. I mean, really. This is the invoice. You don’t get to dispute it. Or at least, I don’t particularly want to make it easy for you. So I ended up using an online time tracker, and left the invoice creation step until later.

Well, later rolled around, and I really kinda wanted to get paid, so I downloaded a report of my hours as a csv file, and whipped up a quick Python script to parse it, and import it into a locally running copy of CouchDB.

The python script looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import couchdb.client
import csv
import path

server = couchdb.client.Server('http://localhost:5984/')
if 'timelog' not in server:
    db = server.create('timelog')

db = server['timelog']
print db

def cleanup():
    # Clear out old rows.
    for row in db:
        if not row.startswith("_"):
            del db[row]

def input( docName ):
    print docName
    input = open( docName )
    reader = csv.DictReader( input )
    i = 1
    for row in reader:
        row['Import Doc Name'] = docName
        row['Import Doc Row'] = i
        if 'Client Name' not in row:
          row['Client Name'] = "Client1"
        key = "%s_%03d" % (docName.name, i)
        print "  ", key
        db[key] = row
        i += 1

cleanup()
base = path.path("/Users/bwinton/Documents/Client1")
for file in base.files("*.csv"):
    input( file )

Pretty easy, eh? After that, I had to create a couple of CouchDB views:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// hoursPerDay
map=function(doc) {
  key = [
    doc['Employee name'],
    doc['Client Name'],
    doc['Date of work'], 
  ];
  emit( key, doc );
};
reduce=function (tag, values) {
  sum=0;
  temp = []
  for( var i=0; i<values.length; i++ ) {
    sum += parseFloat(values[i]['Time in hours']);
    temp[i] = {
      'Order':values[i]['Import Doc Row'],
      'Description':values[i]['Description'],
      'Time':values[i]['Time in hours'],
      'Type':values[i]['Activity Type'],
    }
  }
  temp = temp.sort(function(a,b) {
    a = parseFloat(a['Order'])
    b = parseFloat(b['Order'])
    return a - b
  })
  return [sum, temp];
};

// totalHours
// This view is just to save me re-calculating this value every time
// I call the page, because it should only change when we add a new
// document.
map=function(doc) {
  key = [
    doc['Employee name'],
    doc['Client Name'],
    doc['Date of work'], 
  ];
  emit( key, parseFloat(doc['Time in hours']) );
};
reduce=function(keys, values, rereduce) {
  function sum( values )
  {
    retval = 0;
    for (i=0; i<values.length; i++)
    {
      retval += values[i];
    }
    return retval;
  }
  return sum(values);
};

it was a quick snippet of HTML:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>
  <head><title>Time Log</title>
    <link rel="stylesheet" href="style/blueprint.css" type="text/css">
    <script>
      // Set up some variables.
      months = [ "2009-02" ]
      users = {
        "Blake Winton":{
          invoiceDates : [ "March 1st, 2009."],
          address : ["16 Forman Avenue",
                     "Toronto, ON, M4S 2R2",
                     "bwinton@latte.ca"]
        }};
    </script>
    <script src="script/bwTimesheet.js"></script>
  </head>
  <body>
    <p class="prepend-1"><b>Blake Winton</b><br/>
    16 Forman Avenue<br/>
    Toronto, ON, M4S 2R2<br/>
    bwinton@latte.ca</p>
    <p class="prepend-1"><b>Invoice Number:</b>
      XX-<span id="invoiceNum">000</span><br/>
    <b>Date:</b> <span id="invoiceDate">March 5th, 1973.</span><br/></p>

    <div class="prepend-1 span-1"><b>To:</b></div>
    <div class="span-22 last">
    Client Name<br/>
    16 Client Address</div>
    <p class="span-24">&nbsp;</p>
    <p><span id="timesheet">Timesheet loading...</span>
      <span id="summary">Summary loading...</span>
    </p>
  </body>
</html>

and Javascript:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Get a default month, if one wasn’t passed in.
month = $.url.param('month')
if (typeof(month) == 'undefined')
{
  month = "0";
}
month = parseInt(month)
if ( month < 0 || month >= months.length )
{
  month = 0;
}
invoiceNumInt = month+1;
month = months[month];

// Get a default user, if one wasn’t passed in.
// Yeah, I’ve defaulted it to me.  ;)
user = $.url.param('user')
if (typeof(user) == 'undefined')
{
  user = "Blake Winton";
}
if (!(user in users))
{
  user = "Blake Winton";
}

// Figure out when to start and end the invoice.
invoiceDates = users[user].invoiceDates;
invoiceDateStr = invoiceDates[invoiceNumInt-1];
start = [user, client, month+"-01"];
end = [user, client, month+"-31"];

// Why, oh why, doesn’t Javascript have printf?
function zeroPad(num,count)
{
  var numZeropad = num + '';
  while(numZeropad.length < count)
  {
    numZeropad = "0" + numZeropad;
  }
  return numZeropad;
}

// Update the header, with the address, invoice number, and date.
function updateHeader() {
  var address = $("#address").empty();
  address.append( "<b>"+user+"</b><br/>" );
  for (var i=0; i<users[user].address.length; i++)
  {
    var line = users[user].address[i];
    address.append( line + "<br/>" );
  }

  var invoiceNum = $("#invoiceNum").empty();
  invoiceNum.append( zeroPad( invoiceNumInt, 3 ));

  var invoiceDate = $("#invoiceDate").empty();
  invoiceDate.append( invoiceDateStr );
  updateTimesheet();
}

// Update the timesheet, with hours and descriptions.
function updateTimesheet() {
  var timesheet = $("#timesheet").empty();
  var dbs = $.couch.db("timelog").view("invoice/hoursPerDay",{
    group: true,
    startkey: start,
    endkey: end,
    success: function(r) {
      table = "<div class='span-20 prepend-1 last'><table class='timelog'>";
      rowNum = 0;
      response = r['rows']
      for (var i = 0; i < response.length; i++) {
        var record = response[i];
        var date = record['key'][2];
        var total = record['value'][0];
        var entries = record['value'][1];
        table += "<tr class='row"+rowNum+"'><td>" +
        date + "</td><td>" +
        total + "h</td>";
        for (var j=0; j < entries.length; j++ )
        {
          if (j > 0)
          {
            table += "<tr class='row"+rowNum+"'><td/><td/>";
          }
          table += "<td>" + entries[j]['Time'] + "h - " +
            entries[j]['Description'] + "</td></tr>";
          rowNum += 1;
          rowNum %= 2;
        }
      }
      table += "</table></div>";
      timesheet.append( table );
      updateSummary();
    }
  })
};

// And finally, update the important stuff.
// The total hours, rate, and amount owed.
function updateSummary() {
  var dbs = $.couch.db("timelog").view("invoice/totalHours",{
    startkey: start,
    endkey: end,
    success: function(r) {
      summary = $("#summary").empty();
      content = "<p class='span-20 prepend-1 last'>";
      if ( r['rows'].length == 0 )
      {
        content += "No data available.";
      }
      else
      {
        value = r['rows'][0]['value'];
        content += "<b>Total hours:</b> " + value + "<br/>" +
          "<b>Rate:</b> $" + rate + currency +"/hour.<br/>" +
          "<b>Sub-total:</b> $" + (value*rate).toFixed(2) + "<br/>" +
          "<b>Tax (GST @ 5%):</b> $" + (value*5).toFixed(2) + "<br/>" +
          "<b>Total Payable:</b> $" + (value*(rate+5)).toFixed(2) + "<br/>"
          }
          content += "</p>";
          summary.append( content );
    }
  });
};

// When the document is ready, kick off the updates.
$(function() {
  updateHeader();
});

And we’re done. Not to sound too much like an infomercial, but with JQuery, Blueprint, CouchDB, and Python, it took me only 255 lines of code to get a decent-enough-quality invoice that I could save it as a PDF, send to the client, and get paid. In fact, the client liked it enough (or just wanted things to be standard enough) that they asked that the other consultant on the project use the same template, so I had to spend some time taking my original invoice script and editing it to support more than one user. Either way, I would call it a success.

MMR Study Fixed

Just a quick link to a blog post on Rational Moms about how the data in that one study which linked the MMR vaccine to autism was manipulated.

I hope the lives of those two children weigh heavily on Dr Wakefield's conscience. I bet they don't.

Professor Banned From Campus For... What?

There was an article in yesterday's Globe about a University of Ottawa professor locked out of his office and banned from campus. The article implies that he was suspended from teaching because of his unorthodox teaching methods: he doesn't like to give grades, he altered course curriculum with student input.

It was not his job, as he explained later, to rank their skills for future employers, or train them to be “information transfer machines,” regurgitating facts on demand. Released from the pressure to ace the test, they would become “scientists, not automatons,” he reasoned.

Which, frankly, to me, sounds freaking fantastic. It's exactly what Alfie Kohn recommends: student-led learning, no evaluations.

I'm pretty sure we're not getting the whole story, though. A quick Google shows that this guy is a bit of a pain in the ass, so maybe the university has other reasons to can him. It would sure be nice if they would step and say so, if they do. In the meantime, it seems like they're being idiots.

Sick

Cordelia and I are sick. According to the Venn diagram of misery I have either a cold with muscle pain, fever and headache, or a flu with sneezing and runny nose. Maybe I have a cold and flu simultaneously! Whatever I have it's mild enough to allow me to get up and make myself useful while still being severe enough to make me miserable.

I kept Cordelia home from school today because she woke up during the night all hot and coughing, but now that she's awake she seems more or less fine. I'm sure she will hit a brick wall sooner or later. Hopefully I can get someone to pick Delphine up from school so Cordelia can have a nice long nap.

Now I'm going to set the children up with the obligatory Sick Day TV.

Home again…

A while ago, I downloaded Aquamacs, an emacs port for OSX, and tried to switch to it as my default editor. I mainly did it because I wanted an editor I could add functionality to, à la Steve Yegge. I used it for a while, and got fairly proficient at it. There were a lot of things about it I really liked, like the Markdown mode, and the ability to edit files on a remote host, and the way I could write small functions in elisp and use them to make me more productive.

But while it worked, and worked well, I didn’t really write all that much extra elisp, and whenever I did, I didn’t really enjoy it. So the other day, when I found myself reading a weblog about the lack of a good graphical text editor on OSX, I found myself agreeing with him. So when I read a later article on how the author had finally switched back to vim, I thought that I might give vim another chance.

Well, it turns out that vim is actually pretty close to a perfect text editor for me. It’s got Markdown support, remote file editing, and not only can I write functions for it, I can write them in Python! As an example, here’s Steve Yegge’s blog-check function in vim/python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function! BlogCheck()
  python << END
:::python
import re
import vim
pattern = re.compile( r"\s" )
count = 0
for line in vim.current.buffer:
  line = pattern.sub( "", line )
  count += len(line)
if count <= 5000:
  message = "Okay so far"
else:
  message = "Dude, too long"
print "%s:  %d chars, %d words" % (message, count, count/5)
:::vim
END
endfunction

A Story of One Girl and Her Dirty Chunk of Snow

Cordelia is a pretty compassionate kid. She always wants you to feel good; one of her most common phrases is, "Don't worry!" She's always doling out hugs, and kisses.

Sometimes the love goes to far, though. On the way home from taking Delphine to schol, Cordelia picked up a big ol' hunk of dirty snow, big enough that she needed two arms to carry it. I said, "What are you gonna do with that big dirty hunk of snow?"

"I'm gonna throw it!"

"Who are you going to throw it at?"

"No-one!" You can see who has the sense in this family.

"No, it's not nice to throw things at people. So what are you going to throw it at?"

"The ground!" She stopped, planted herself firmly and threw the chunk of snow down where it broke into a big chunk, some smaller chunks and some slush.

"Are you going to take a piece?"

"Yeah!" She picked out one of the smaller pieces and off we went. As we walked she was talking to herself, constructing some kind of narrative for this piece of snow; she's big on anthropomorphising. I wasn't really paying attention so I didn't catch the details.

After a block she said, "The snowball is lonely! He wants his daddy! Can we go back and put him with his daddy?"

"Um, no, let's take him home and leave him on the porch while you take a nap, and we can take him to his Daddy when we pick up Delphine."

Satisfied with that, we walked on, and she kept on talking. Half a block later: "Can he come with me while I nap?"

"No, he would melt. Remember Peter brought his snowball inside and then it wasn't there? His jacket was just wet?" Appealing to literature sometimes helps.

"But he will be lonely!"

"But he will just melt into water if you bring him inside. I think he'll be okay on the porch."

We rounded the corner and crossed the street onto our block. I walked a few paces ahead of Cordelia, and then turned around to see how she was doing. The snow was no longer in her hand. "I dropped him! Now he will never go back to his Daddy! He's gonna be lonely! He wants his Daddy and his brothers and sisters!"

And she wept, heartbroken, all the way home over the sad fate of this little chunk of dirty snow. I had to carry her and commiserate. Poor thing.

Science Groupie For Hire

This morning while I was hanging up laundry in the basement, I was thinking about stuff. Mainly I was thinking about the clever, interesting people at the party yesterday and trying to figure out how I could spend more time with, or maybe work with, clever, interesting people like that. (Not that I didn't work with clever, interesting people in software; in fact, that's one of my favourite things about software. Love me some geeks.) I was casting my mind about, musing on this and that, on academia and science and research and clever people doing interesting things, when it came to me:

I don't want to do science.

I don't know why that seemed like such a bombshell, but it seemed very exciting at the time. (It doesn't take much when you're hanging cold wet t-shirts in a cold wet basement.) I love science, I love facts, I love knowing about the latest studies and ideas, but I don't want to do the research myself. I'm more interested in getting things done (with the best new knowledge) than in finding things out. I love that other people are finding things out but I just want to know what they've got.

So a career in dusty academia is not for me, which makes this not much of an epiphany because I never thought it was. However, hanging out with Greg Wilson's clever friends made me think that I would like to work close to science, applying or disseminating the latest studies and discoveries. Blake's suggestion was science outreach, and I also think science writing could be cool. The other idea that popped into my head was urban planning. I feel like there must be some way to take scientific knowledge and apply it to public policy, maybe try and get us out of this mess we're in.

So many ideas.