25 February 2007

Dgrin busts out with $25,000 Photo Contest!

Ok, so the digital grin community (powered by SmugMug) tends to be my favorite online photography community. It's full of nice people, we have good technical conversations, great critiques of others works, very little flame wars, and practically no asshats. Unlike a few other photography communities I can think of. One of the things dgrin has had, for a long while now, is a photography contest. It's a topically driven two week window of folks trying to shot a given topic, and then voting on who captured it best. There have been some GREAT winners in the various contests. But there have also always been some kinda lame entries. Well, DGrin is kicking it up a notch now... it just might encourage more folks to play along, and help get those folks that are playing, to bring their A-game. digg story

04 February 2007

It works

Well, CAATT is now "functional". After scratching my head over how to do the scheduler for a while I had a pretty good theory, so I sat down to code it yesterday afternoon. hence the post clarifying some of the design details. At the end of the night last night I had annual, semi-annual, quarterly and monthly recurring tasks scheduling themselves. It was about 500 lines of php code (I choose php because the rest of CAATT is written in php... it's a LAMP application really at this point, so all the database access methods were fresh in my head). This afternoon, between a trip to Urgent Care for my wife and the superbowl, I finished weekly, daily, and daily+weekends scheduling, and implemented skip scheduling for all. scheduler.sh (still a php script) is now only 127 lines. Why the drop? Because having written the scheduler for 4 virtually identical scheduling patterns I recognized the similarities and collapsed the core of the scheduler into a loop with a small switch statement that covers the only difference. The skip scheduling was virtually the same as the offset scheduling too, so it was factored into the same loop. That's what it really all came down to in terms of cutting it down to size... yesterday I wrote the first four scheduling protocols to learn how to do it. Today, I refactored them into common code, and then factored an entirely different set of scheduling protocols into it.


for ($lcv=1; $lcv < 8; $lcv++) {
$query[$lcv]['tasks']="select * from tasks where sched_process=$lcv";
$query[$lcv]['exist']="select work_id from work_queue where task=%s ";
}

$query[1]['exist'].="and year(input) = year(now())";
$query[2]['exist'].="and year(input) = year(now())
and (month(input) / 6 = month(now()) / 6)";
$query[3]['exist'].="and year(input) = year(now())
and (month(input) / 3 = month(now()) / 3)";
$query[4]['exist'].="and year(input) = year(now())
and month(input) = month(now())";
$query[5]['exist'].="and year(input) = year(now())
and month(input) = month(now())
and week(input,0) = week(now(),0)";
$query[6]['exist'].="and year(input) = year(now())
and month(input) = month(now())
and day(input) = day(now())";
$query[7]['exist'].="and year(input) = year(now())
and month(input) = month(now())
and day(input) = day(now())";

$today=date("N", time());
if ($today == 1 || $today == 7) {
$startat=7;
} else {
$startat=1;
}
//$startat=1; //for debug.

for ($lcv=$startat; $lcv < 8; $lcv++) {
echo "scheduling type $lcv \n";
$result = mysql_query($query[$lcv]['tasks']);
if ($results === false) die("FAILED: {$query[$lcv]['tasks']}\n". mysql_error());
while ($task = mysql_fetch_assoc($result)) {
echo " evaluating task {$task['task_id']}\n";

//look for it already assigned
$cmd=sprintf($query[$lcv]['exist'], $task["task_id"]);
$existing = mysql_query($cmd);
if ($existing === false) die("FAILED: $cmd\n". mysql_error());
$dosched=false;
$now['d'] = date("d", time());
$now['m'] = date("m", time());
$now['w'] = date("W", time());
$now['z'] = date("z", time());
if (mysql_num_rows($existing) == 0) {
if (isset($task['sched_offset'])) {
echo " task has offset\n";
$schedoff = strtotime($task['sched_offset']);
$schedat['d'] = date("d", $schedoff);
$schedat['m'] = date("m", $schedoff);
$schedat['w'] = date("W", $schedoff);

switch ($lcv) {
case 1: //annual
if ($schedat['d'] == $now['d'] &&
$schedat['m'] == $now['m'] ) $dosched = true;
break;
case 2: //half
if ($schedat['d'] == $now['d'] &&
$schedat['m'] % 6 == $now['m'] % 6 ) $dosched = true;
break;
case 3: //quarter
if ($schedat['d'] == $now['d'] &&
$schedat['m'] % 3 == $now['m'] % 3 ) $dosched = true;
break;
case 4: //month
if ($schedat['d'] == $now['d']) $dosched = true;
break;
case 5: //weekly
if ($schedat['w'] == $now['w']) $dosched = true;
break;
default: //daily, daily+weekend
die("task {$task['task_id']} doesn't make sense, it has an offset, but that isn't applicable!");
}
} elseif ($task['sched_skip'] != 0) {
echo " task has a skip\n";
mysql_free_result($existing);
$cmd="select work_id,date(input) as input from work_queue where task={$task["task_id"]} order by input desc limit 1";
$existing = mysql_query($cmd);
if ($existing === false) die("FAILED: $cmd\n". mysql_error());
$work = mysql_fetch_assoc($existing);
$lastassign = strtotime($work['input']);
$last['z'] = date("z", $lastassign);
$last['m'] = date("m", $lastassign);
$last['w'] = date("W", $lastassign);

switch ($lcv) {
case 4: //month
$lookat='m'; break;
case 5: //weekly
$lookat='w'; break;
case 6: //daily
case 7: //daily+weekend
$lookat='z'; break;
default: //annual, half, quarter
die("task {$task['task_id']} doesn't make sense, it has a skip, but that isn't applicable!");
}
if ($last[$lookat] + $task['sched_skip'] < $now[$lookat]) $dosched = true;

} else {
echo" task has no schedule offset or skip, and no satisfying schedule. yet....\n";
$dosched=true;
}
if ($dosched) {
echo " >> need to assign task ",$task["task_id"],"... ";
$cmd="insert into work_queue set task={$task["task_id"]}, doer={$task["usual_doer"]}";
mysql_query($cmd) or die ("FAILED: $cmd\n". mysql_error());
echo "inserted as work_id ",mysql_insert_id(),"\n";
} else {
echo " -- task does not need to be scheduled.\n";
}
} else { //there are satisfying instances in work_queue
echo " task {$task['task_id']} has ",mysql_num_rows($existing)," satisfying instances in work_queue: ";
while($work = mysql_fetch_assoc($existing)) echo $work['work_id']," ";
echo "\n";
}
mysql_free_result($existing);
}
mysql_free_result($result);
}


Now that I've finished writing the first draft of that, I read Wolf's take on programmers and why we write code. Wolf nails it on the head, as usual. It's not the writing code I enjoy, it's the problem solving, yesterday's code writing was a learning process, so that today I could really make it work. By the way, the above code (and all code for this project) was written with TextWrangler, and is covered under GPLv2.

Now all we need to do to make it useful is get some of the tasks around the house into it and start using it! I've already put many of mine... like the cat box, the water filters, the softener salt, the air filters, etc. And this isn't 100% complete... still missing the ability to lock out the users internet connection at the firewall until they're done with their chores, and the time/priority based scheduling (so you can put in a whole bunch of "once a week" jobs and tell the system to only allocate 100 minutes a day worth of work let's say and have *it* figure out what order to put those various weekly jobs in.) is still missing... but they're more "advanced features".

And of course, having just posted this, I now realize the first bug in the system... scheduled offsets in anything other than daily+weekends, will fail to be scheduled on the weekend. Oops.

Labels: ,

03 February 2007

random design points

I've been working on the hardest part of the caatt system... the scheduler. I had previously left the designations of some of the fields in the database's tasks table kinda up in the air. As I work on implementing it, I need them to solidify, so I'm documenting them here.

First off, I added an additional column, sched_skip of type tinyint. This is a convenience scheduling knob for things that are "every N intervals", like "every other day", "every 4 months", etc. (this removes the "bi-weekly" previously mentioned as a scheduling process.)

Sched_offset is another that was vague. It's meaning changes based on the value of sched_process.

When sched_process is 1 (annual) then the MONTH and DAY portion of sched_offset is the day of the year to schedule the task on. (really, it's the number of months and days into the period.)

When sched_process is 2 (half) then the MONTH and DAY portion of sched_offset is the offset of months and days into the period to schedule on. Ditto 3 (quarterly).

When sched_process is 4 (monthly) then the DAY portion of sched_offset is the offset into the month. Ditto 5 (weekly).

Labels: ,