Support for weekly summaries and daily analysis.

build.xml
---------

Added `build-shell` target. This creates a folder, `build/shell` and copies all
of the required libraries and class files to that folder, excluding the
groovy-all jar that conflicts with groovysh's internal classpath.

Category.groovy
---------------

Fixed a bug in the `addEvent()` function.

CategorizationPlan.groovy
-------------------------

* Rewrote this as groovy code.
* Now is an abstract class instead of an interface.
* Added a property, `newCatSetupFun`, which is a closure that will be run
  whenever this categorization plan creates a new category. A caller can set
  this via the constructor.
* Added a function, `setupNewCategory()` that should be called inside of all
  `newCategory()` implementations. This allows the user to have categorization
  plans added to the subcategories dynamically.
* Updated all the CategorizationPlan implementations to respect this new
  behavior.

start-script.groovy
-------------------

Implemented a more comprehensive analysis.
This commit is contained in:
Jonathan Bernard 2012-09-01 22:20:28 -07:00
parent 2650fca7f1
commit 5dff4de089
9 changed files with 148 additions and 59 deletions

View File

@ -5,4 +5,15 @@
<target name="init"/> <target name="init"/>
<target name="build-shell" depends="build">
<mkdir dir="${build.dir}/shell"/>
<copy todir="${build.dir}/shell">
<fileset dir="${build.dir}/lib/runtime/jar">
<include name="*.jar"/>
<exclude name="groovy-*.jar"/>
</fileset>
<fileset dir="${build.dir}/main/classes"/>
</copy>
</target>
</project> </project>

View File

@ -1,5 +1,5 @@
#Wed, 29 Aug 2012 15:10:32 -0700 #Sat, 01 Sep 2012 22:19:54 -0700
name=time-analyzer name=time-analyzer
version=0.1 version=1.0
build.number=5 build.number=0
lib.local=true lib.local=true

View File

@ -1,27 +1,23 @@
import com.jdbernard.timeanalyzer.processors.* import com.jdbernard.timeanalyzer.categories.FilteredCategory
import com.jdbernard.timeanalyzer.categories.* import com.jdbernard.timeanalyzer.categories.TimeIntervalCategory
import com.jdbernard.timeanalyzer.categorizationplans.* import com.jdbernard.timeanalyzer.categorizationplans.DailyCategorizationPlan
import com.jdbernard.timeanalyzer.chart.* import com.jdbernard.timeanalyzer.categorizationplans.DescriptionBasedCategorizationPlan
import com.jdbernard.timeanalyzer.events.* import com.jdbernard.timeanalyzer.categorizationplans.TwoLevelCategorizationPlan
import com.jdbernard.timestamper.core.* import com.jdbernard.timeanalyzer.processors.TimelineEventProcessor
import com.quantumdigital.ithelp.timeanalyzer.* import com.jdbernard.timestamper.core.FileTimelineSource
import org.joda.time.* import org.jfree.chart.ChartFactory
import org.joda.time.format.* import org.jfree.chart.ChartFrame
import org.jfree.chart.* import org.jfree.data.general.DefaultPieDataset
import org.jfree.data.general.*
import org.jfree.util.SortOrder import org.jfree.util.SortOrder
import org.joda.time.DateTime
tep = new TimelineEventProcessor() import org.joda.time.Interval
tep.exclusions << ~/.*Home.*/ import org.joda.time.Period
import org.joda.time.format.PeriodFormat
pf = PeriodFormat.getDefault() pf = PeriodFormat.getDefault()
topcat = new FilteredCategory("Top Category") printDuration = { duration ->
pf.print(duration.toPeriod()) }
twoLevelCatPlan = new TwoLevelCategorizationPlan()
descriptionBasedCatPlan = new DescriptionBasedCategorizationPlan()
topcat.categorizationPlans << twoLevelCatPlan
topcat.categorizationPlans << descriptionBasedCatPlan
loadEvents = { file, processor -> loadEvents = { file, processor ->
fileSource = new FileTimelineSource(file.toURI()) fileSource = new FileTimelineSource(file.toURI())
@ -35,9 +31,61 @@ makePieDataset = { category ->
category.events.each { entry -> category.events.each { entry ->
dpds.setValue(entry.description, entry.duration.standardSeconds) } dpds.setValue(entry.description, entry.duration.standardSeconds) }
dpds.sortByValues(SortOrder.DESCENDING) dpds.sortByValues(SortOrder.DESCENDING)
return dpds } return dpds }
makeFrame = { categoryName, dataset -> makePieFrame = { categoryName, category ->
def dataset = makePieDataset(category)
return new ChartFrame(categoryName, return new ChartFrame(categoryName,
ChartFactory.createPieChart("Time Spent", dataset, true, true, false)) } ChartFactory.createPieChart("Time Spent", dataset, true, true, false)) }
analyze = { file ->
def tep = new TimelineEventProcessor()
tep.exclusions << /.*Home.*/
def twoLevelPlan = new TwoLevelCategorizationPlan()
def descriptionPlan = new DescriptionBasedCategorizationPlan()
def setupCat = { cat ->
cat.categorizationPlans << twoLevelPlan
cat.categorizationPlans << descriptionPlan }
def dailyPlan = new DailyCategorizationPlan(setupCat)
def events = loadEvents(file, tep)
def topcat = new FilteredCategory("Time Analysis: Jonathan Bernard")
topcat.categorizationPlans << dailyPlan
topcat.categorizationPlans << twoLevelPlan
topcat.categorizationPlans << descriptionPlan
events.each { if (topcat.matchesEvent(it)) topcat.addEvent(it) }
return [topcat: topcat, rawEvents: events] }
listEvents = {topcat ->
topcat.events.eachWithIndex { event, index ->
println "${index}: ${event} (${printDuration(event.duration)})" }}
listCategories = { topcat ->
topcat.categories.eachWithIndex { cat, index ->
println "${index}: ${cat} (${printDuration(cat.duration)})" }}
details = { topcat ->
println "Categories"
listCategories(topcat)
println "Events"
listEvents(topcat) }
weeklySummary = { events ->
def todayMidnight = new DateTime()
def friday = todayMidnight.withDayOfWeek(5)
def monday = todayMidnight.withDayOfWeek(1)
def week = new Interval(monday, Period.days(7))
def weekCat = new TimeIntervalCategory(
"Week of ${friday.toString('MMM dd, yyyy')}", week)
weekCat.categorizationPlans << new TwoLevelCategorizationPlan()
weekCat.categorizationPlans << new DescriptionBasedCategorizationPlan()
events.each { if (weekCat.matchesEvent(it)) weekCat.addEvent(it) }
return weekCat }

View File

@ -47,9 +47,13 @@ public abstract class Category implements Comparable<Category> {
* made and the `Event` matches.*/ * made and the `Event` matches.*/
public Event addEvent(Event event) { public Event addEvent(Event event) {
def addedEvent
// Try first to add it to a subcategory (or create a new subcategory). // Try first to add it to a subcategory (or create a new subcategory).
if (!addToSubcategory(event)) { addedEvent = addToSubcategory(event)
// Cannot add to a subcategory, add to ourselves.
// Cannot add to a subcategory, add to ourselves.
if (!addedEvent) {
events << event events << event
addedEvent = event } addedEvent = event }

View File

@ -0,0 +1,24 @@
package com.jdbernard.timeanalyzer.categorizationplans;
import com.jdbernard.timeanalyzer.categories.Category;
import com.jdbernard.timeanalyzer.events.Event;
import java.util.List;
import java.util.Map;
public abstract class CategorizationPlan {
public CategorizationPlan() {}
public CategorizationPlan(Closure newCatSetupFun) {
newCategorySetupFun = newCatSetupFun }
protected Closure newCategorySetupFun
protected void setupNewCategory(Category cat) {
if (newCategorySetupFun) newCategorySetupFun(cat) }
public abstract boolean deservesNewCategory(Event event, List<Event> existingEvents)
public abstract Category newCategory(Event event, List<Event> existingEvents)
public abstract List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents)
}

View File

@ -1,13 +0,0 @@
package com.jdbernard.timeanalyzer.categorizationplans;
import com.jdbernard.timeanalyzer.categories.Category;
import com.jdbernard.timeanalyzer.events.Event;
import java.util.List;
import java.util.Map;
public interface CategorizationPlan {
boolean deservesNewCategory(Event event, List<Event> existingEvents);
Category newCategory(Event event, List<Event> existingEvents);
List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents);
}

View File

@ -1,16 +1,21 @@
package com.jdbernard.timeanalyzer.categorizationplans; package com.jdbernard.timeanalyzer.categorizationplans
import com.jdbernard.timeanalyzer.categories.Category import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.TimeIntervalCategory
import com.jdbernard.timeanalyzer.events.Event import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.DateTime import org.joda.time.DateTime
import org.joda.time.Interval import org.joda.time.Interval
import org.joda.time.Period import org.joda.time.Period
public class DailyCategorizationPlan { public class DailyCategorizationPlan extends CategorizationPlan {
public DailyCategorizationPlan() {}
public DailyCategorizationPlan(Closure newCatSetupFun) {
super(newCatSetupFun) }
boolean deservesNewCategory(Event event, List<Event> existingEvents) { boolean deservesNewCategory(Event event, List<Event> existingEvents) {
Interval fullday = new Interval( Interval fullDay = new Interval(
event.start.toDateMidnight(), Period.days(1)) event.start.toDateMidnight(), Period.days(1))
Interval eventIv = new Interval( Interval eventIv = new Interval(
event.start, event.duration) event.start, event.duration)
@ -21,8 +26,12 @@ public class DailyCategorizationPlan {
Interval fullday = new Interval( Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1)) event.start.toDateMidnight(), Period.days(1))
return TimeIntervalCategory( Category newCat = new TimeIntervalCategory(
event.start.toString("EEE, MMM dd", fullday)) } event.start.toString("EEE, MMM dd"), fullday)
setupNewCategory(newCat)
return newCat }
List<Event> findEventsToRecategorize(Event event, List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) { List<Event> existingEvents) {

View File

@ -4,24 +4,27 @@ import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.DescriptionBasedCategory import com.jdbernard.timeanalyzer.categories.DescriptionBasedCategory
import com.jdbernard.timeanalyzer.events.Event import com.jdbernard.timeanalyzer.events.Event
public class DescriptionBasedCategorizationPlan implements CategorizationPlan { public class DescriptionBasedCategorizationPlan extends CategorizationPlan {
public DescriptionBasedCategorizationPlan() {}
public DescriptionBasedCategorizationPlan(Closure newCatSetupFun) {
super(newCatSetupFun) }
public boolean deservesNewCategory(Event event, List<Event> existingEvents) { public boolean deservesNewCategory(Event event, List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase() def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.any { return existingEvents.any {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } }
}
public Category newCategory(Event event, public Category newCategory(Event event,
List<Event> existingEvents) { List<Event> existingEvents) {
return new DescriptionBasedCategory(event.description) def newCat = new DescriptionBasedCategory(event.description)
} setupNewCategory(newCat)
return newCat }
public List<Event> findEventsToRecategorize(Event event, public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) { List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase() def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.findAll { return existingEvents.findAll {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } }
}
} }

View File

@ -4,22 +4,25 @@ import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.TwoLevelCategory import com.jdbernard.timeanalyzer.categories.TwoLevelCategory
import com.jdbernard.timeanalyzer.events.Event import com.jdbernard.timeanalyzer.events.Event
public class TwoLevelCategorizationPlan implements CategorizationPlan { public class TwoLevelCategorizationPlan extends CategorizationPlan {
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/ private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
public TwoLevelCategorizationPlan() {}
public TwoLevelCategorizationPlan(Closure newCatSetupFun) {
super(newCatSetupFun) }
public boolean deservesNewCategory(Event event, List<Event> el) { public boolean deservesNewCategory(Event event, List<Event> el) {
return event ==~ TWO_LEVEL_PATTERN return event ==~ TWO_LEVEL_PATTERN }
}
public Category newCategory(Event event, List<Event> el) { public Category newCategory(Event event, List<Event> el) {
def m = event.description =~ TWO_LEVEL_PATTERN def m = event.description =~ TWO_LEVEL_PATTERN
return new TwoLevelCategory(m[0][1]) def newCat = new TwoLevelCategory(m[0][1])
} setupNewCategory(newCat)
return newCat }
public List<Event> findEventsToRecategorize(Event event, public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) { List<Event> existingEvents) {
def m = event.description =~ TWO_LEVEL_PATTERN def m = event.description =~ TWO_LEVEL_PATTERN
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ } return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ } }
}
} }