Fixing Do More ratcheting

We’ve got a buttload of gissues in our repo related to issues with ratcheting. Probably the biggest complaint (aside from weird corner cases that cause insta-derails… yipes!) is that for do more goals (actually, all non-do-less goals) we shift the road along the x-axis to accomplish the ratchet, which has the side effect of changing the dates of scheduled road changes in the future. Which sucks. It’s also the reason why autoratchet (and manual ratchet) don’t play nice with weekends off, and other frustrations as well.

So I’ve got a partial fix, but it’s still problematic, because 1) it doesn’t work well with flat spots, and 2) it’ll only give approximate results when there are scheduled rate changes upcoming in your road. But maybe those complaints are minor compared to the annoyance of the date-shifting in the current implementation?

So here’s how the updated algorithm works:

  • vcur: current datapoint value (aggday-ed, gotten from bbrain)
  • rcurd: current daily rate
  • yaw: good side of the road (-1 => below, 1 => above)
  • nbuf: new buffer (in days)

From these params we calculate the newedge of the road like this:

newedge = vcur - (nbuf-1)*rcurd.abs*yaw

Generally, to give you one day of buffer, we want to move the road to be exactly on your current datapoint. For zero days of buffer, you want the road to be one-daily-rate’s worth above your current datapoint. For two days of buffer, one-daily-rate’s worth below, etc. (Except by ‘above’ and ‘below’ I mean “in the yawly direction”, hence the business with multiplying by yaw etc).

This works great when the road has a constant rate. You can just multiply rcurd by the number of days (actually days less one, because… well, I’ll leave that as an exercise for the reader), and move the road so that it’s that far from the current datapoint.

If the rate changes, however, using the current daily rate is not quite fully correct. To do this perfectly accurately, we’d need to get daily rates for each of the days of buffer you want to keep, and add those up and move the road accordingly.

So if you currently had 8 days of buffer, and you wanted to keep only 3 of those days, but your rate doubled each day this coming week, starting with 1, then to do it fully correctly, the amount of buffer we’d want to give would be 1+2+4.

Or more generally, assume we have function rate(date) which returns the daily rate at the given date.

newedge = vcur - (rate(today) + rate(today+1) + rate(today+2))*yaw

This would solve all the problems with upcoming rate changes… though we would probably want to be draconian about only ratcheting roads / road segments where the dir and the sign of the road agreed, and the road was monotonic. Basically non-monotonic roads are the worst.

Now you say “But wait! what about flat roads??” You caught me. I still haven’t addressed the issue of flat roads. With a zero rate segment this all breaks down because when rcur=0, then newedge == curedge. This could potentially actually be considered a feature, not a bug? It would mean that autoratchet wouldn’t overwrite your weekends or other scheduled breaks, as the worse it could do is move the road to you current datapoint, and then it would continue along the flat spot from there.

With that in mind, here’s a totally raw brainstorm about different ways to deal with zero-rate segments + ratcheting.


So one take would be to leave it as is with no zero-rate special case. This would be nice for autoratcheters who also want to schedule breaks sometimes. A user on a flat spot wanting to throw away extra buffer would have to first remove the flat spot manually using the road editor or something. It might be better than making assumptions about what to do with the road otherwise. And if we had a nice tutorial or something about it, and did sensible UX things in the case that you can’t ratchet because you’re on a flat spot", it might be pretty OK?


We could sort of automatically help out with the above. So rather than just saying “you’re out of luck, go use the road editor”, we’d have a different interface for dealing with flat spots, maybe kind of like an un-break scheduler. If your current rate is zero then the only thing you can do is slide the end of the flat closer. Instead of saying

“You currently have 8 days of buffer. Adjust it so you have [FILL IN THE BLANK] days of buffer”,

we’d say something like

“Ooh, you’re on a flat spot. You have 10 days of flat. Adjust so you have [0-10] days of flat remaining.”

If you threw away the entire flat spot and still had buffer left over, you’d now be back in the regular case, and you could adjust by a certain number of days more. So it’d take two steps to clear a flat spot and move the road closer to you, but it might be simpler (in terms of code) and more clear how it’s going to work and what results you’ll wring. So if you were 8 units above the flat spot, and then had 10 days of flat, we’d make you throw away the 10 days of flat as an explicit first step to ratcheting… like a flat spot ratchet says: “you have 10 days of flat spot. how many days of flat would you like remaining?” and then you can enter between 0-10, and we adjust accordingly. And then if you wanted to ratchet to 0 days of buffer, for example, first you’d have to send flat spot to 0, then as a second step, now you’d have a non-zero daily rate and you’d be allowed to do a regular day-ly ratchet.

option three:

Combine day-ly ratcheting and flat-spot-sliding ratcheting programmatically through the magic of if-statements!

On the one hand it might seem relatively straightforward, but in practice it could get complex and weird, with like, checking for a flat spot, and then sliding, and then rechecking how many days of buffer you have now, and then day-ly ratcheting, and again checking how much buffer, and if there was lots of buffer and lots of weekends off, or something like that it could be real weird?

option four:

Maybe we could do some more limited version of ratcheting when there’s a flat segment. Like maybe you can only ratchet to a beemergency if you’re in a flat segment. We pick some amount (the average rate?, the upcoming rate?, as the user for an amount?) and move the road to be that far above your vcur. So on a flat your only option re ratcheting is “make me do something today” and then we ask you “how much” and you say 10, or whatever, and we make it so you now have to do 10, but the road going forward from today just remains the same.


I am so happy that this is being worked on. :smile:

1 is appealing because it’s simple, and probably enough for me personally.

2 sounds intriguing, and it would be nice to have the option to ratchet during a break. I wouldn’t want auto ratcheting to do that, though.

3 doesn’t appeal on a first reading. Though maybe I’d want it if I had weekends off and a bunch of buffer to kill? I can imagine that scenario…

4 sounds like it would open up some interesting use cases for completely flat goals that you’d only use to commit to something on a daily basis, though I don’t think I’d use them. So I’m not that interested in this one.


I’m inclined toward Option 1. Candidate errorcopy:

Ratcheting doesn’t work when your road is flat! Remove the flat spot with the road editor and try again.

As to the problem of ratcheting being oblivious to upcoming rate changes, it doesn’t sound totally crazy to declare that rare enough to ignore but that does feel pretty unsatisfying. I kind of want to just get the math right but I realize it’s fraught (spanning the Beebody/Beebrain divide and all).

PS: @bee and I are now thinking through the math of just getting it right!

1 Like

If you got the math right, that would mean that it could work during breaks in some circumstances, right? Like, if I had 3 days of flat road in front of me, but I only wanted to ratchet down to 3 + n days, it could still work fine. The only time it wouldn’t generalize is in the case that moving the road all the way up to the current point doesn’t remove enough buffer to reach the user’s desired horizon. And at that point, instead of saying, “Sorry! This feature doesn’t work when you’re in the middle of a break,” you could just say, “We’ve removed all the buffer we can but you still have n + x days of buffer because of previously-scheduled breaks,” and I feel like that would sound a lot less broken to the user than the first message.

1 Like

I worry about “remove the flat spot with the road editor”, at least right at this moment. Most people don’t know there is one, most users don’t have access to the one on Beeminder itself, and it is suuuper clunky UX to ask them to go to to do it. Ratcheting away flat spot is too common (especially for new users who didn’t realise the days of mercy setting existed) to make it this clunky.


I’m a bit late to comment, but here’s my datapoint:

In my system I specified & implemented this to always leave you with the number of days that you request. If I read what you wrote right, that’s similar to your option 2, but no questions asked: if I ask it to leave the goal with 10 days buffer, I get 10 days buffer, no user interaction involved. Partly that’s because there’s kind of no UI :wink: but it’s also what I wanted anyway. If I recall it actually always adds a new event to the linked list of events that describes the schedule of rates – except that there’s at most one rate-changing event per day, so if you happen to hit an existing scheduled rate change, it gets replaced, keeping the same scheduled rate as that rate change event (so my choice of the word “event” is not a great one: these events can be discarded or replaced if you change your future schedule somehow, they’re not immutable-for-all-time as the word might suggest). So if there’s a zero-rate section N days out when you ask for N days of buffer, of course you’ll end up with that zero-rate section being shorter – because it always sets the rate N days out, never anything else (everything to the right of that “sliding up” due to cumulative-ness).

Internally there’s no explicit concept of “sliding” – I’m not sure if that’s different to your plans or not. There are also no daily rates, except as implied by rate changes and other events that define the schedule (as I say basically a linked list, at least in memory).

Caveat: I didn’t try to understand all your options carefully, but I think I got the gist of it.

I use the same code to implement recommit on derail: the schedule line just goes up instead of down.

It’s worked mostly fine for me. (“mostly” only because there’s a bug in there somewhere that occasionally leaves my days of buffer off-by-one that I’ve not bothered to fix :slight_smile: )

When I get started on my rewrite to support a mobile UI + some other goals (so far just UI experiments – previously my UI was command line + text file), I’d like to support explicit breaks, which I think involves different behaviour here than for zero-rate sections. I’m sure I’ve discussed what I think about that here before – I don’t recall the behaviour I reckoned it should have without looking back on here or thinking about it carefully again, but it certainly involved zero-rate sections and breaks behaving differently!

Sounds like great progress with your big changes by the way!

1 Like