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="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>

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
version=0.1
build.number=5
version=1.0
build.number=0
lib.local=true

View File

@ -1,27 +1,23 @@
import com.jdbernard.timeanalyzer.processors.*
import com.jdbernard.timeanalyzer.categories.*
import com.jdbernard.timeanalyzer.categorizationplans.*
import com.jdbernard.timeanalyzer.chart.*
import com.jdbernard.timeanalyzer.events.*
import com.jdbernard.timestamper.core.*
import com.quantumdigital.ithelp.timeanalyzer.*
import org.joda.time.*
import org.joda.time.format.*
import org.jfree.chart.*
import org.jfree.data.general.*
import com.jdbernard.timeanalyzer.categories.FilteredCategory
import com.jdbernard.timeanalyzer.categories.TimeIntervalCategory
import com.jdbernard.timeanalyzer.categorizationplans.DailyCategorizationPlan
import com.jdbernard.timeanalyzer.categorizationplans.DescriptionBasedCategorizationPlan
import com.jdbernard.timeanalyzer.categorizationplans.TwoLevelCategorizationPlan
import com.jdbernard.timeanalyzer.processors.TimelineEventProcessor
import com.jdbernard.timestamper.core.FileTimelineSource
import org.jfree.chart.ChartFactory
import org.jfree.chart.ChartFrame
import org.jfree.data.general.DefaultPieDataset
import org.jfree.util.SortOrder
tep = new TimelineEventProcessor()
tep.exclusions << ~/.*Home.*/
import org.joda.time.DateTime
import org.joda.time.Interval
import org.joda.time.Period
import org.joda.time.format.PeriodFormat
pf = PeriodFormat.getDefault()
topcat = new FilteredCategory("Top Category")
twoLevelCatPlan = new TwoLevelCategorizationPlan()
descriptionBasedCatPlan = new DescriptionBasedCategorizationPlan()
topcat.categorizationPlans << twoLevelCatPlan
topcat.categorizationPlans << descriptionBasedCatPlan
printDuration = { duration ->
pf.print(duration.toPeriod()) }
loadEvents = { file, processor ->
fileSource = new FileTimelineSource(file.toURI())
@ -35,9 +31,61 @@ makePieDataset = { category ->
category.events.each { entry ->
dpds.setValue(entry.description, entry.duration.standardSeconds) }
dpds.sortByValues(SortOrder.DESCENDING)
return dpds }
makeFrame = { categoryName, dataset ->
makePieFrame = { categoryName, category ->
def dataset = makePieDataset(category)
return new ChartFrame(categoryName,
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.*/
public Event addEvent(Event event) {
def addedEvent
// 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.
if (!addedEvent) {
events << 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.TimeIntervalCategory
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.DateTime
import org.joda.time.Interval
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) {
Interval fullday = new Interval(
Interval fullDay = new Interval(
event.start.toDateMidnight(), Period.days(1))
Interval eventIv = new Interval(
event.start, event.duration)
@ -21,8 +26,12 @@ public class DailyCategorizationPlan {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
return TimeIntervalCategory(
event.start.toString("EEE, MMM dd", fullday)) }
Category newCat = new TimeIntervalCategory(
event.start.toString("EEE, MMM dd"), fullday)
setupNewCategory(newCat)
return newCat }
List<Event> findEventsToRecategorize(Event event,
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.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) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.any {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
}
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } }
public Category newCategory(Event event,
List<Event> existingEvents) {
return new DescriptionBasedCategory(event.description)
}
def newCat = new DescriptionBasedCategory(event.description)
setupNewCategory(newCat)
return newCat }
public List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
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.events.Event
public class TwoLevelCategorizationPlan implements CategorizationPlan {
public class TwoLevelCategorizationPlan extends CategorizationPlan {
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
public TwoLevelCategorizationPlan() {}
public TwoLevelCategorizationPlan(Closure newCatSetupFun) {
super(newCatSetupFun) }
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) {
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,
List<Event> existingEvents) {
def m = event.description =~ TWO_LEVEL_PATTERN
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ }
}
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ } }
}