product

Upkeep Freshness — one model for "when was this last done vs. how often it should be"

Status: engine SHIPPED 2026-07-26 (upkeep-freshness.ts + golden test). The unified UI wiring across routines + maintenance is the follow-on. Requested 2026-07-26.

The idea

A single, universal model for anything with a recurring upkeep cadence — personal (brush teeth ~every 12h, shower ~daily) and household (bathroom cleaning ~weekly, furnace filter ~90d, dryer-vent ~yearly). Each item has:

  • last done — a timestamp (from a check-in, a "mark done" tap, or a logged event).
  • target cadence — "ideally every N hours/days/weeks/months."

From those two, we derive a freshness % and a green / yellow / red status that drives dots, rings, and "due soon / overdue" copy everywhere.

The engine (apps/backend/src/services/upkeep-freshness.ts)

Pure + deterministic (now is always passed in), so it's unit-tested and safe to mirror on the client. Golden test: __tests__/upkeep-freshness.test.ts.

computeFreshness({ lastDoneAt, intervalMs, now }) → {
  status: 'fresh' | 'due_soon' | 'overdue' | 'unknown',
  color:  'green'  | 'yellow'  | 'red'     | 'gray',
  pct,          // remaining life 0–100 (100 = just done, 0 = due/overdue)
  ratio,        // elapsed / interval (≥1 overdue; Infinity if never done)
  overdue, elapsedMs, nextDueAt,
}
intervalToMs(n, 'hour'|'day'|'week'|'month')
humanSince(elapsedMs)   // "3d ago"
humanDue(nextDueAt, now) // "due in 2d" / "3d overdue"

Thresholds: overdue (red) at ratio ≥ 1; due-soon (yellow) at ratio ≥ 0.75 by default, per-item overridable — brushing might only go yellow in its last 10% (dueSoonFraction: 0.9), a furnace filter yellow at 60%. Never done → gray/unknown, treated as needing attention.

Where it plugs in

  • Household maintenance hotspots — the heart of the House Map's family_maintenance_items (FAMILY_HOUSE_MAP.md): store last_done_at + interval_n/interval_unit; render the room/hotspot in its freshness color; sort the "what's due" list by ratio descending (most overdue first).
  • Family routines — brushing/showers already log check-ins (family_routine_checkins); the most-recent check-in is last_done_at, the routine's cadence maps to an interval. A freshness dot on each routine turns "did we do it lately enough" into a glance — richer than the current binary done-today.
  • Anything else with a cadence — pet flea meds, car oil, plant watering, medication refills. The engine doesn't care what the item is.

Data shape (when the unified tracker is built)

Extend family_maintenance_items (and optionally a light interval on routines):

last_done_at   timestamptz null
interval_n     integer            -- "every N"
interval_unit  text               -- hour | day | week | month
due_soon_frac  real null          -- per-item yellow threshold (default 0.75)

Marking done sets last_done_at = now(); freshness is computed on read, never stored (so it's always current). A "done" event can also drop a row in a small history/log for trend lines later.

Follow-on build

  1. ✅ Engine + golden test (shipped).
  2. Household maintenance items (migration 586, family-upkeep.ts + /dashboard/family/upkeep) — furnace filter, dryer vent, bathroom deep clean, etc., with an "every N hours/days/weeks/months" interval, rendered green/yellow/red + %fresh, most-overdue first, "Done" resets the clock. Starter catalog + custom add.
  3. Routine freshness dot (cadence → interval; last check-in → last_done) on the family dashboard routines.
  4. Room attachment (room_id reserved on family_maintenance_items) once the House Map rooms exist; render hotspots on the map by freshness color.
  5. A single unified "Upkeep" view listing personal + household items together by how overdue they are, and putting freshness dots on the wall.

Guardrails

Inherits FAMILY_INTENT.md: a red dot is information, not a guilt trip. No streak-shaming; "overdue" states are calm and actionable (one tap to mark done or reschedule). Household ops, not a cleanliness scoreboard for a person.

UPKEEP FRESHNESS — Docs | HiveJournal