Steps as the first DailyMetric #8

Closed
opened 2026-08-26 09:13:50 +00:00 by GKaszewski · 0 comments
Owner

What to build

The tracer slice for ADR 0008 and the DailyMetric machinery. One metric end-to-end: domain value object, storage, manual write, read back, display.

The shape matters more than the feature. A struct holding a kind beside a loose number makes invalid states constructible — a step count tagged as HRV compiles cleanly, because the kind is data and the value is untyped. Kind and value are therefore one type:

pub enum MetricValue {
    Steps(Steps),
    // one variant per MetricKind, each wrapping its own validated newtype
}

MetricKind survives as a payload-free discriminant for naming a kind without a value, but it is derived from the value and never stored beside it.

Each newtype validates its own range in new(), following the established value-object convention (new() validates, from_persistence() does not, value() reads). Units live in the type, not in a field name. Eleven near-identical bounded-integer newtypes justify a macro, as the existing identity-newtype macro already does for UUID types.

Source is Manual or a Provider. A DailyMetric sourced Manual is never overwritten by an import; a manual write always supersedes a Provider's. (User, Date, MetricKind) is unique.

Hydration is fallible in a way entry loading is not — a row can carry a kind this build does not know, or a value a later-tightened range rejects. Skip such rows and record them; do not fail the query.

Acceptance criteria

  • MetricValue is an enum of validated newtypes; MetricKind is derived from it
  • Pairing a value with the wrong kind is not expressible in the type system
  • A newtype macro covers the bounded-integer shape
  • Out-of-range values are rejected at construction with a typed error
  • (User, Date, MetricKind) is unique; re-writing a date upserts
  • A Manual metric is not overwritten by a Provider write; a Manual write always wins
  • Unreadable stored rows are skipped and recorded, not fatal
  • User can set and see a step count for a Date end to end
  • Date resolution uses the rule from #2

Blocked by

## What to build The tracer slice for ADR 0008 and the `DailyMetric` machinery. One metric end-to-end: domain value object, storage, manual write, read back, display. The shape matters more than the feature. A struct holding a kind beside a loose number makes invalid states constructible — a step count tagged as HRV compiles cleanly, because the kind is data and the value is untyped. Kind and value are therefore **one type**: ```rust pub enum MetricValue { Steps(Steps), // one variant per MetricKind, each wrapping its own validated newtype } ``` `MetricKind` survives as a payload-free discriminant for naming a kind without a value, but it is **derived** from the value and never stored beside it. Each newtype validates its own range in `new()`, following the established value-object convention (`new()` validates, `from_persistence()` does not, `value()` reads). Units live in the type, not in a field name. Eleven near-identical bounded-integer newtypes justify a macro, as the existing identity-newtype macro already does for UUID types. `Source` is `Manual` or a `Provider`. A `DailyMetric` sourced `Manual` is never overwritten by an import; a manual write always supersedes a Provider's. `(User, Date, MetricKind)` is unique. Hydration is fallible in a way entry loading is not — a row can carry a kind this build does not know, or a value a later-tightened range rejects. Skip such rows and record them; do not fail the query. ## Acceptance criteria - [ ] `MetricValue` is an enum of validated newtypes; `MetricKind` is derived from it - [ ] Pairing a value with the wrong kind is not expressible in the type system - [ ] A newtype macro covers the bounded-integer shape - [ ] Out-of-range values are rejected at construction with a typed error - [ ] `(User, Date, MetricKind)` is unique; re-writing a date upserts - [ ] A `Manual` metric is not overwritten by a `Provider` write; a `Manual` write always wins - [ ] Unreadable stored rows are skipped and recorded, not fatal - [ ] User can set and see a step count for a `Date` end to end - [ ] `Date` resolution uses the rule from #2 ## Blocked by - #2
GKaszewski added the ready-for-human label 2026-08-26 09:13:50 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GKaszewski/k-mood#8