Need help with streak calculation functionality

Hey all,

I’m building a habit-tracking app and trying to include functionality that tracks how many days in a row the user completes a given habit. Each habit has a record for each day that has a status of either done, missed, or skipped.

As of now I kind of have it working by running a backend workflow that watches for changes in habit record statuses and then loops through all the records that habit holds from the first to last adding 1 to a running temporary streak count when a record’s status is “done” and resetting the it to 0 when a record’s status is “missed”. It stores this in a temporary streak count and then commits it to the actual streak count at the end of the looping so that it can be displayed.

This kind of works. The problem is that it is very slow. Currently, when previewing, it takes like 10 seconds to finish the process on 20 records and I expect this problem to grow with increasing numbers of records. It is also inaccurate sometimes when changing records from previous days. Not sure why that is and it fixes it if you change the latest record and let it recalculate but it’s less than ideal.

Is there a better way to do this (ideally on the front end) that works more quickly and accurately?

Do you need to know all previous streaks, or just the current one? If you just need the current one, could you just do a search for the most recent “missed” (or “skipped”?) record and count the number of days since then? I could be missing something obvious about your use case but this seems like a simple solution.

You’d need to have a record for every day for this to work, of course. Maybe that’s not what’s going on here

2 Likes

Brilliant. Thank you. I’m keeping track of a “best streak” but that wont require me to keep track of all historical streaks. Can you give any guidance on the best way to implement that?

Sure - is this a ‘best streak’ for each habit or just best streak overall per user? If it’s the best streak overall I’d store it in the user database item. If it’s the best streak for a specific habit, I’d try to store it with the rest of the details for that habit. (You mentioned a daily record for each habit, but I’m guessing that 1) that datatype is as light as possible since you’ll have so many items in the table, and 2) there’s a table of ‘habits’ somewhere else, maybe with a name, user and other metadata? If so, that’s where I’d put the individual ‘best streak for this habit’ number.)

Either way, any time a habit is checked off I’d add a workflow item to update that best streak number if the current streak is larger than that number. I don’t think you’d need to do anything on a daily basis there, since it would only change when a streak grows and not when a habit is missed or skipped.

I feel like I could be missing something on the skipped habits though - if this works like a Streak Freeze in Duolingo, then do the skipped days count towards the streak? I think the way Duolingo does it they don’t count to the streak but they don’t terminate the streak either. So if that’s what’s going on then to get the current streak you’d want to count the number of days since the most recent ‘missed’ record, and then subtract the number of ‘skipped’ records after that missed day.

1 Like

Yeah, the skipped days work just like that. They don’t end or add to a streak.

And yeah, the streak and the best streak are attached to the habits so each habit has its own current and best streak number.

I appreciate your help with this. What I was asking when I said, “could you give any guidance on the best way to implement that”, was if you could point me in the right direction for implementing the solution you wrote about in your first post. I have an idea of how to do it but I want to see if you might have a better way. Sorry that I was ambiguous about the meaning of “that”. lol

Something like ‘Current date/time - (minus) Search for habit_records :first item :format as days - Search for habit_records :count’

The first search is for missed records for this particular habit, sorted by date descending so the first item would be the most recent.

The second search is for skipped records for this particular habit, since the date identified in the first search. You’ll either have to do a nested search here to identify the date again, or store the date somewhere on the page to minimize searches. If we were being really scrupulous about workload units we might want to store all of this in a datatype somewhere so it only gets calculated once a day - but that may or may not be cheaper. I’m no expert in the WU thing :confused:

1 Like

Thank you so much. I did what you suggested and it worked almost perfectly. I hit a bug if there were no records in the list that were marked missed (it set the streak to like 19,600 when it should have been like 21, lol), but I fixed that by making that action only run if the number of records held by that habit and marked missed was greater than 0 and adding a second action that triggered when that number was 0 that simply subtracts the number of records marked skipped from the overall number of records for that habit and sets the streak as that number.

The only problem I’m having now is with the “best streak” bit. Right now, I have it set up to set the best streak as the current streak if the current streak is greater than the best streak, but this isn’t quite sufficient.

That “best streak” number actually refers to a specific streak that was achieved and should change if that streak changes. This is hard to explain but say you would fill in 20 records in a row as completed. If your previous Best Streak was 19 that number would change to 20. But, if you went back and realized that you actually missed a couple of those 20 days, that would negate that streak but it would still be reflected in that habit’s Best Streak number when you never actually earned it. Does that make sense? Basically, I need to retroactively revert to a previous Best Streak if the streak referenced in that best streak changes.

I’m stumped on this one. Any thoughts?

A couple of quick ideas:

  1. Iterate over a list with the fabulous ListShifter plugin? I know there are some powerful tools in there, but I’ve mostly only used the sorting function so I can’t say for sure. There might be a way to iterate over a list of missed habit_records and check, for each of them, the number of days since the previous missed record.
  2. Do the same thing, but in a cycle of backend workflows
  3. Store a ‘streak length’ field on every missed record that indicates how long the streak was before that missed day broke the streak. If a past record is changed, all you need to do is update the next chronological missed record. Then do a search on all missed records and find the max to locate the best streak.

I’d probably go with #3 if it was me - I know it’s another field on a likely-hefty table, but it’s just a 1-to-three-digit number so it can’t affect load times that much. Right? (Again, not a WU expert, I don’t know.)

Thanks a ton.