Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,8 @@ the configuration for every supported metric, and private integration preference
"enabled": true,
"checkInEnabled": true,
"checkOutEnabled": false,
"displayUnit": null
"displayUnit": null,
"aggregation": "average"
}
},
"searchDailyNotes": false
Expand Down Expand Up @@ -360,6 +361,11 @@ growth reference sex is `female` or `male`. The response has the same shape as
The optional date and reference sex are stored for a future verified BMI-for-age
reference and do not produce a diagnosis or an inferred value.

Each returned metric configuration also contains its read-only registry
`aggregation` (`average`, `count`, `daily`, or `sum`). Clients must not send it
when updating preferences; it describes how Health derives summaries, including
the daily `sum` for Kilocalories Measurements.

Unknown metric keys, unsupported units, malformed dates, and invalid field types
return a client error. Configuration is always scoped to the authenticated user.
`searchDailyNotes` is a boolean and defaults to `false`; it is the explicit, per-user
Expand Down Expand Up @@ -1119,13 +1125,15 @@ The response returns canonical values and contains one item for every requested

Each metric object contains the metric identity, its category and value type, the canonical unit when applicable, metric `minimum` and `maximum` when defined, daily `series`, `summary`, and the goal-revision segments that can be represented as chart thresholds. `summary.count` is the number of source records, not the number of day buckets; `activeDays` is the number of day buckets with a recorded numeric point or a non-zero event total. Event metrics and blood pressure provide summaries for their individual series through `summary.subseries`.

The response also contains `sourceRecords`: the individual owner-scoped records used to build the selected metric series. Each record has its stable metric key, local `date`, a numeric value or option value, optional blood-pressure values, and `recordedAt` for timestamped records. Daily Values have no `recordedAt`. The records use the same selected-metric and half-open `[from, to)` filtering as the chart data; they are ordered by local day, then Daily Values before timestamped records, then timestamp and metric key. Notes are deliberately not included.

Every daily point has a `subseries` field. It is `null` for ordinary numeric metrics. It is a map for event metrics and blood pressure, where one logical metric has multiple chart series. Hydration uses `water`, `coffee`, `tea`, and `other`; Break uses its configured event categories; blood pressure uses `systolic` and `diastolic`.

---

# 39. Daily Series and Goal Semantics

Numeric scale and measurement values are aggregated as an arithmetic mean for each local day. A missing numeric day is represented by `value: null` and is excluded from numeric summaries.
Numeric scale and Measurement values use their registry aggregation for each local day. The standard numeric aggregation is arithmetic mean; Kilocalories uses `sum` across its timestamped `kcal` Measurements. Allergies is an option Measurement whose daily value is the count of owner-scoped symptom records. A missing numeric or Allergy day is represented by `value: null` and is excluded from numeric summaries.

Event metrics have a valid daily count of zero. Their series therefore contains zero-valued local days, and their average, minimum, and maximum include every day in the selected period.

Expand Down Expand Up @@ -1526,9 +1534,13 @@ The Configuration response profile contains canonical `heightCm`, the display pr

`GET /goals` returns owner-scoped active and paused logical goals plus the public, non-personal target registry; retired identities are excluded from goal management. `POST /goals` creates an identity with `targetKey`, `period`, `comparator` (`gte` or `lte`), canonical `targetValue`, and `remindersEnabled`. Identity uniqueness is `(user, targetKey, period)`, so distinct daily, weekly, monthly, or long-term goals may coexist where supported while an identical target/period duplicate is rejected. `PUT /goals/{id}` updates the owner-scoped goal; `active: false` pauses without retiring it and `active: true` resumes that same identity. Value/direction changes create or update the current local-period revision, while target/period changes retire the old identity and create a new one. `DELETE /goals/{id}` retires rather than deletes history. `GET /goals/progress?period=day|week|month|long_term&date=YYYY-MM-DD` returns derived owner-scoped progress; finite dates are interpreted in the user's Nextcloud timezone and future periods are rejected. Long-term latest-value responses include `baselineValue` and a clamped directional `progressRatio`. Job Satisfaction uses the existing daily-value endpoint with `metricKey: "job_satisfaction"`, integer numeric values 1–5, and `unit: null`.

## Kilocalories and Fruit daily values
## Kilocalories Measurements and Fruit daily values

Kilocalories uses `POST /measurements`: each non-negative numeric record has fixed unit `kcal` and an RFC3339 `recordedAt`; multiple owner-scoped records may be created for one local day. Day-based goal progress and Statistics use their sum. `PUT /daily-values/kilocalories/{date}` is intentionally rejected because Kilocalories is no longer a Daily Value. Existing v3 Daily Value calorie rows are converted by the forward migration to noon-local Measurements and are retained as source rows. Fruit continues to use `PUT /daily-values/fruit/{date}` and accepts a non-negative whole number with fixed unit `pieces`. Unsupported units, negative values, and fractional Fruit values return `400`.

## Allergy symptom Measurements

Both metrics use the existing `PUT /daily-values/{metricKey}/{date}` and `GET /daily-values?date=YYYY-MM-DD` resources. `kilocalories` accepts a non-negative numeric value with fixed unit `kcal`. `fruit` accepts a non-negative whole number with fixed unit `pieces`. Unsupported units, negative values, and fractional Fruit values return `400`.
Allergies uses the generic `POST /measurements` and `PUT /measurements/{id}` endpoints with `metricKey: "allergies"`, `numericValue: null`, `values: null`, `unit: null`, and one canonical `optionValue`. Supported values are `sneezing`, `runny_nose`, `nasal_congestion`, `itchy_nose`, `itchy_eyes`, `watery_eyes`, `red_eyes`, `swollen_eyelids`, `cough`, `scratchy_throat`, `wheezing`, `shortness_of_breath`, `itchy_skin`, and `hives`. A note is optional. Labels and their nasal, eye, respiratory, and skin presentation groups are localized client concerns and are never persisted. Unknown option values return `400`; all records are owner-scoped. Daily Journal and Statistics values count entries without deriving severity or medical meaning.

## Idempotent timestamped writes

Expand Down
4 changes: 2 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ aggregation: count

The implemented Break metric is also an event metric. Its allowed options are `short`, `regular`, `short_walk`, `long_walk`, `mindfulness`, and `fresh_air`; it uses count aggregation. These are atomic entries and do not require a separate endpoint or table.

`kilocalories` is a non-negative daily numeric metric with fixed canonical unit `kcal`. `fruit` is a non-negative whole-number daily counter with fixed canonical count unit `pieces`. Both reuse `health_daily_values`, configuration, goals, Statistics, saved Statistics views, Journal, and the shared frontend metric presentation registry.
`kilocalories` is a non-negative timestamped Measurement with fixed canonical unit `kcal`. Multiple records may have the same local day; its registry aggregation is `sum`, so Journal totals, goals, Statistics, saved Statistics views, and dashboard summaries use the daily Measurement sum. `fruit` is a non-negative whole-number daily counter with fixed canonical count unit `pieces` and reuses `health_daily_values`.

Module and metric definitions are application definitions and are not database records.

Expand Down Expand Up @@ -1245,4 +1245,4 @@ Goal identity is `(user_id, target_key, period)`. Distinct periods for the same

The PWA is a second frontend surface of the existing `health` app, not a separate Nextcloud app or backend. Source lives in `src/pwa/`, its application and service-worker bundles use dedicated Vite configurations, and three explicit public shell routes serve `/apps/health/pwa/`, its manifest, and its worker. All Health data APIs remain authenticated and CSRF-protected.

The PWA uses Nextcloud Login Flow v2 to obtain a revocable app password, discovers public metric definitions from Nextcloud Capabilities and owner-scoped enablement from Health Configuration, and submits to the existing OCS v2 entry, measurement, and daily-value endpoints. Its home renders only uniform metric launchers; a selected metric opens a focused dialog whose controls derive direct named-option buttons, small finite range buttons, direct counter increments, and numeric/composite inputs from that definition metadata. Cache Storage contains only the static shell. IndexedDB contains the connection metadata and minimum pending-write outbox. Event and measurement replays carry a UUID v4 `operationId`; daily values use idempotent PUT; daily counter increments are converted once to a persisted absolute daily-value PUT before submission. The header uses the same compact sync and device-action conventions as Taskbook; diagnostics are generated from a technical allow-list rather than from local Health records.
The PWA uses Nextcloud Login Flow v2 to obtain a revocable app password, discovers public metric definitions from Nextcloud Capabilities and owner-scoped enablement from Health Configuration, and submits to the existing OCS v2 entry, measurement, and daily-value endpoints. Its home renders only uniform metric launchers; shared hydration option metadata expands into a direct Water `small_glass` action and a Coffee type picker, while other controls derive direct named-option buttons, small finite range buttons, direct counter increments, and numeric/composite inputs from definition metadata. Cache Storage contains only the static shell, including the install icons and both theme-aware header icon variants. IndexedDB contains the connection metadata and minimum pending-write outbox. Event and measurement replays carry a UUID v4 `operationId`; daily values use idempotent PUT; daily counter increments are converted once to a persisted absolute daily-value PUT before submission. The header uses the same compact sync and device-action conventions as Taskbook; diagnostics are generated from a technical allow-list rather than from local Health records.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.3.0]

### Added

- Add fixed-unit daily Kilocalories and whole-number Fruit counter metrics across Journal, Goals, Statistics, saved views, reminders, and configuration.
- Add an integrated offline-first Health quick-entry PWA with Login Flow v2 authentication and idempotent synchronization.
- Add Allergies as an optional timestamped symptom measurement with owner-scoped Journal, Statistics, saved-view, configuration, and PWA support.

### Fixed

- Show baseline-aware directional long-term Weight goal progress in today's Journal.
- Allow independent goal periods for the same metric while rejecting identical target/period duplicates.
- Store Kilocalories as timestamped measurements so multiple values recorded on one day aggregate correctly.

## [3.1.2]

Expand Down
6 changes: 5 additions & 1 deletion DATA_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,10 @@ Do not sacrifice understandable data for theoretical flexibility.

## Kilocalories, Fruit, and replay identity

`kilocalories` and `fruit` are atomic `health_daily_values` metrics, so no metric-specific table is added. Kilocalories stores a non-negative canonical numeric `kcal` value per local date. Fruit stores a non-negative whole-number canonical `pieces` count per local date.
`kilocalories` is an atomic `health_measurements` metric, so no metric-specific table is added. Each non-negative canonical `kcal` record has the ordinary owner-scoped Measurement timestamp. Its daily aggregation strategy is `sum`; this is the value used by Journal totals, goal progress, and day-based Statistics. Fruit remains an atomic `health_daily_values` metric with a non-negative whole-number canonical `pieces` count per local date.

`allergies` is an atomic `health_measurements` option metric. Its nullable `option_value` stores only one stable symptom identifier from the built-in registry, never a translated label. It has no unit or numeric health value; the required non-null storage numeric value is an internal placeholder and is never returned. Journal and day-based Statistics aggregate owner-scoped Allergy Measurements by count. The registry groups symptoms as nasal, eye, respiratory, and skin symptoms for presentation only. Allergy entries and notes remain private journal data and receive no diagnosis, severity, or medical interpretation.

`Version3006Date20260915140000` preserves an existing v3 Daily Value calorie row and creates one corresponding Measurement with the same owner and numeric value. It uses noon in that owner’s Nextcloud timezone for `recorded_at`, preserving the original local-day meaning across offsets and DST. A deterministic owner-scoped operation ID prevents duplicate converted Measurements if migration execution is retried.

Nullable `client_operation_id` columns on `health_entries` and `health_measurements` store only a client-generated UUID v4 replay identity. Owner-scoped unique indexes make retried timestamped writes idempotent without exposing or trusting a client user ID. Blood-pressure rows may share an operation ID because uniqueness also includes their atomic metric key. The replay identity is write metadata and is not returned as Health history.
8 changes: 8 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,11 @@ The later design must be restartable or otherwise guarantee that interruption ca
## Offline replay identity migration

`Version3005Date20260905190000` is additive and adds nullable `client_operation_id` columns plus owner-scoped unique replay indexes to the v3 entry and measurement tables. Existing rows remain null and unchanged. The migration stores no new Health values, creates no PWA-specific server records, and does not alter any released migration.

## Kilocalories Measurement migration

`Version3006Date20260915140000` is a forward v3 data migration. It leaves every existing `health_daily_values` row unchanged and converts each owner-scoped `kilocalories` Daily Value into one `health_measurements` row with the same canonical numeric `kcal` value. The Measurement timestamp is noon on the source local date in the owner’s configured Nextcloud timezone, converted to UTC; this preserves the source local-day meaning through timezone offsets and daylight-saving changes. A deterministic UUID v4-shaped `client_operation_id` derived from the source Daily Value ID makes the conversion retry-safe. No Health values are logged.

## Option Measurement migration

`Version3007Date20260916120000` is additive and adds nullable `option_value` to `health_measurements`. It leaves all existing rows and columns unchanged. The field stores stable built-in option keys for generic option Measurements such as Allergies; it does not store translated labels or introduce a metric-specific table.
16 changes: 13 additions & 3 deletions MVP.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,23 @@ Optional durations may be added later.

Type:

- daily numeric value
- timestamped numeric measurement

Unit:

- fixed canonical `kcal`

Kilocalories accepts non-negative numeric values, including decimals. It has no unit selector because its stable metric meaning is specifically kilocalories per local day.
Kilocalories accepts non-negative numeric values, including decimals. Every entry has fixed canonical unit `kcal`; multiple entries can be recorded on the same local day and the day's value is their sum. Goals and day-based Statistics use that sum.

---

## Allergies

Type:

- timestamped option measurement

Allergies records one selected, stable symptom key with an optional note. Multiple owner-scoped entries may be recorded on the same local day. Daily Journal and Statistics values are the count of recorded symptoms only; Health does not calculate severity, a score, or medical interpretation.

---

Expand Down Expand Up @@ -543,4 +553,4 @@ The MVP goal slice supports one logical owner-scoped target for each target/peri

## Quick-entry PWA slice

The Health app includes an installable PWA at `/apps/health/pwa/`. After initial loading and Nextcloud Login Flow v2 authorization, it can queue minimum write payloads in IndexedDB while offline and synchronize them through the existing owner-scoped Health API. Its equal-sized metric launcher grid opens entry dialogs that use direct buttons for finite choices, numeric Save plus direct increment for counters, and numeric inputs for arbitrary values. Its diagnostics are technical troubleshooting only and exclude Health and credential data. It does not display recorded Health values or derived data.
The Health app includes an installable PWA at `/apps/health/pwa/`. After initial loading and Nextcloud Login Flow v2 authorization, it can queue minimum write payloads in IndexedDB while offline and synchronize them through the existing owner-scoped Health API. Its equal-sized metric launcher grid has direct Water (one `small_glass`) and Coffee (supported coffee type) actions, as well as dialogs that use direct buttons for finite choices, numeric Save plus direct increment for counters, and numeric inputs for arbitrary values. Its diagnostics are technical troubleshooting only and exclude Health and credential data. It does not display recorded Health values or derived data.
1 change: 1 addition & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Possible metrics include:
- Weight
- Kilocalories
- Fruit
- Allergies

Only enabled metrics appear in the user's journal.

Expand Down
Loading
Loading