Beeminder Forum

Convert a graph from odometer-style to do-more (ie, cumulative aka kyoom)

Until today our goal was odometer style where each datapoint’s value was the cumulative number of UVIs. We usually used Twitter’s count, but there was a period where Twitter was being flaky and we let it diverge. We finally exported the full list and it turned out we were behind, so we figured out which ones we’d missed… Anyway, long story short, we also converted the goal to to a normal Do More.

I did that via our API, in Mathematica. In case it’s ever useful, I thought I’d share:

usr = "bob"; (* or user "meta" in our case *)
goal = "foo"; (* or "uvi" in our case *)
key = "abc123babyyouandmeyeah";
baseurl = "";

(* Slurp the datapoints into a list of Associations (Mathematica hashes) *)
getdata[g_] := Association@@@Reverse@Import[
  baseurl<>"users/me/goals/"<>g<>"/datapoints.json?auth_token="<>key, "JSON"]

(* For goal g, update datapoint with id using hash h *)
editdata[g_, id_, h___] := URLFetch[
  baseurl<>"users/me/goals/"<>g<>"/datapoints/"<>id<>".json", "Method"->"PUT", 
  "Parameters" -> {"auth_token"->key, h}]

(* General utility functions... *)
cat = StringJoin@@(ToString/@{##})&;        (* Like sprintf/strout in C/C++.  *)
SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
each[pat_, lst_List, bod_] :=               (*  converts pattern to body for  *)
  (Cases[Unevaluated@lst, pat:>bod]; Null); (*   each element of list.        *)
each[p_, l_, b_] := (Cases[l, p:>b]; Null); (*    (Warning: eats Return[]s.)  *)

That’s the set-up. Here’s the part that slurps up all the data and remembers things about it:

d = getdata[goal];

(* Make hashes mapping datapoint id to all the fields for that datapoint *)
each[a_, d,
 timestamp[a["id"]] = a["timestamp"];
 daystamp[a["id"]]  = a["daystamp"];
 value[a["id"]]     = Round[a["value"]];
 comment[a["id"]]   = a["comment"];

(* Remember the previous value for each datapoint *)
prevval[_] = 0;
each[{ap_, a_}, Partition[d, 2, 1],
 prevval[a["id"]] = value[ap["id"]]

(* Non-Kyoom Value *)
nkv[id_] := value[id] - prevval[id]

(* Sanity-check that all the nkv's are reasonable: nkv[#["id"]] & /@ d *)

And here’s the part that actually updates the datapoints:

(* Bam! Replace all the odom values with the diffs aka kyoomable values: *)
each[x_, d,
 id = x["id"];
 editdata["uvi", id, 
  "timestamp" -> cat@timestamp[id], 
  "daystamp" -> daystamp[id], 
  "value" -> cat@nkv[id], 
  "comment" -> comment[id]]