Let a day be left out of the stats
Every logged day was treated as equally trustworthy, and they aren't. A day someone else had the puppy leaves a thin record that reads exactly like a real one — five hours of sleep, two pees, no walk — and then drags down the average, widens the longest gap in the Timing panel and puts a trough in every chart that never happened. "Not counted", in the overview panel's heading, takes the day you are looking at out of everything that aggregates across days. Nothing is deleted or hidden. The day's own overview, history and sleep & wake list are exactly as they were, dimmed and labelled; navigate to it and it is all still there. Only the cross-day views stop seeing it, and weight and notes keep counting wherever they fall — a weigh-in and a vet note are facts you recorded, not behaviour a sparse logger distorts. The mark is an ordinary event, the way a training session is. That was the whole reason to do it this way: a set of marks that sync per-item with last-write-wins and tombstones is exactly what the event contract already provides, so un-marking is a delete, offline works, and two devices marking the same day resolve themselves. An excluded_days table would have meant a table, an endpoint, a request/response pair and a client cache to re-derive semantics already in hand. Every renderer selects events by type, so a new type is inert everywhere it isn't wanted; only the History log has to filter it out, being the one view that shows whatever it is handed. render() already computed the event list once and fanned it out, which made the seam a single place: day-scoped panels keep the full list, weight and notes keep it too, and the seven cross-day renderers take a counted one. Filtering alone gets two things wrong, and those are most of the diff. An empty slot lies. A marked day with no events draws a zero bar, which reads as "the puppy barely slept" — precisely the misreading the mark exists to prevent. So weeklyData zeroes the day's figures and flags it, and the four bar charts, both actograms and the training grid paint a hatch in the slot instead. Zeroing centrally rather than in each chart means every axis maximum, total and tooltip downstream is already right. The slot stays: dropping it would make consecutive bars stop being consecutive days. Gaps balloon. gapsBetween subtracts consecutive events, so with a day's events gone Tuesday's last pee sits next to Thursday's first and the subtraction invents thirty hours — worse for the panel than the sparse day ever was. Any gap whose interval touches a marked day is therefore discarded rather than measured. Sleep and walk durations need no such care: sleepMsInRange and walkMsInRange already clip to the day being measured, so a nap running in from a marked day contributes only its counted part. Both trend charts skip marked days explicitly rather than leaning on their existing "any sleep at all" guard, which would have let a nap crossing midnight give a marked day a non-zero total and sneak it back into the average. Owner-only, alongside the rest of what a guest may not decide: a sitter should not be able to rule their own thin day out, nor quietly take a good one out of the averages. The server drops day-excluded events arriving on a guest session; the client hides the control to match.
This commit is contained in:
@@ -57,6 +57,12 @@ type Exercise struct {
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
}
|
||||
|
||||
// eventTypeDayExcluded marks a day the owner has taken out of the charts and
|
||||
// averages — a sitter's thin day, a stay at kennels. It is an event so it rides
|
||||
// the ordinary sync (per-item last-write-wins, tombstone to un-mark) rather than
|
||||
// needing a table and endpoint of its own; the client reads it in app.js.
|
||||
const eventTypeDayExcluded = "day-excluded"
|
||||
|
||||
var uuidRE = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
||||
|
||||
func validUUID(s string) bool { return uuidRE.MatchString(s) }
|
||||
@@ -190,6 +196,13 @@ func (s *Store) sync(userID, loggedBy, shareID string, client []Event) ([]Event,
|
||||
if ce.ID == "" {
|
||||
continue
|
||||
}
|
||||
// Marking a day as not counted is a judgment about the record rather
|
||||
// than something that happened to the puppy, so it belongs to the owner
|
||||
// alongside everything else a guest may not decide. The client hides the
|
||||
// control; this is what enforces it.
|
||||
if shareID != "" && ce.Type == eventTypeDayExcluded {
|
||||
continue
|
||||
}
|
||||
if _, err := stmt.Exec(
|
||||
ce.ID, ce.Type, ce.At, ce.Note, ce.PhotoID, ce.Weight, ce.Grams, ce.ExerciseID, ce.UpdatedAt, ce.Deleted, userID, loggedBy, shareID,
|
||||
); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user