## Outcome Completed a **read-only, synthetic-test-only** audit of `/home/agent/.hermes/repos/HealthManager`. I did **not** access production assets/DB, and made **no edits or files**. Targeted tests passed: **45 passed in 27.59s**. Working tree remained clean. ## Exact implementation paths ### Apple daily canonicalization / cadence - `scripts/health/apple_health_analytics.py` - Metric aggregation/cadence policy: `metric_spec()` lines **128–145** - `sum` metrics → daily expected cadence (`1`) - `last` metrics (`weight_body_mass`, `body_mass_index`, `blood_pressure`) → weekly expected cadence (`7`) - all other metrics → `avg`, daily cadence - Canonical daily points: `daily_points()` lines **441–513** - Daily data-quality coverage: `coverage_summary()` lines **536–599** - Source precedence, normalization, deduplication are upstream in `_observations()`, `_deduplicated_rows()`, `_select_source()`, `_select_unit()`. ### Current weekly API aggregation - `scripts/health/dashboard_v5/read_api.py` - Weekly transformation: `_weekly()` lines **506–533** - Series flow / coverage timing: `_series()` lines **556–594** - API accepts `resolution=week`: `ALLOWED_RESOLUTIONS`, line **51** - Tests: `tests/test_dashboard_v5_sprint6b_api.py:199–231` - Documentation: `docs/sprint6b-metric-catalog-read-api.md:83–93` ### Apple source inventory and release allowlist - Allowlisted Apple identifiers: `scripts/health/dashboard_v5/metric_catalog_v2.py:59–75` (`APPLE_SOURCE_SPECS`) - Inventory collector: `inventory_sources()` lines **387–468** - API exposes only inventory counts, not identifier records: `read_api.py:764–778` - Existing design/test: `tests/test_dashboard_v5_sprint6b_catalog.py:122–138` - Historical metadata statement: `docs/sprint6b-metric-catalog-read-api.md:39–56` says **64 observed identifiers**, with **12 released** and **52 observed_not_released**. ## Current behavior and audit findings 1. **Daily Apple aggregation is metric-specific and well-defined.** - `sum`: e.g. steps/activity/sleep - `avg`: e.g. resting HR, HRV, SpO₂, respiratory rate - `last`: body measurements - It preserves zero, selects one source class/day, normalizes units, rejects current day by default in analytics, and reports daily coverage quality. 2. **Current weekly implementation is not cadence-specific.** - `_weekly()` groups points by ISO year/week and emits only when `len(distinct_dates) == 7`. - `sum` → sum seven values; `avg` → arithmetic mean of seven values; `last` → last dated value. - It rejects `observation` and `panel`, but it **allows `last` / intermittent Apple metrics**. Since intermittent metrics are not expected daily, requiring seven daily observations means weight/BMI weekly points will ordinarily be omitted. This conflicts with `metric_spec()`’s `expected_cadence_days=7`. - `_coverage()` runs **before** weekly conversion and reports daily `expected_days`, `observed_days`, and `missing_days`; it does not expose weekly observation count, expected weekly observations, complete-week count, or partial-week count. - The current source only guarantees seven distinct dates; it relies on ISO grouping to imply the dates are the complete calendar week. A hardened contract should also explicitly verify finite numeric values and Monday–Sunday membership. 3. **Current inventory is safely non-auto-releasing, but insufficient as the requested 64-row artifact.** - `inventory_sources()` marks `released` only if an identifier is explicitly present in `APPLE_SOURCE_SPECS`; otherwise it returns `observed_not_released`. - Current emitted Apple fields: `identifier`, `units`, `observations`, `numeric_observations`, `released_metric_ids`, `status`. - Missing required report fields: explicit boolean `released`, `reason`, canonical `unit`, `priority`, and a stable report envelope/schema. - The API intentionally returns only totals, not `inventory["apple_health"]`. - The repository has no checked-in list of the actual 64 identifier names; producing those rows would require calling `inventory_sources()` against the prohibited production DB. Therefore, the **64 total / 12 released / 52 not released** is documented evidence only, not independently re-enumerated here. ## Minimal metric-specific weekly contract ```json { "timezone": "Europe/Zurich", "week_key": "ISO-8601 year-week", "week_start": "Monday local date", "week_end": "Sunday local date", "source_daily_contract": { "distinct_local_dates_only": true, "finite_numeric_value_required": true, "explicit_zero_is_observed": true, "missing_is_not_zero": true, "current_day_policy": "exclude_or_mark_provisional_before_weekly_aggregation" }, "daily_metrics": { "expected_days_per_week": 7, "complete_week_rule": "exactly one finite daily point for each Monday-through-Sunday local date", "observation_count": "distinct valid daily dates, 0..7", "coverage": "observation_count / 7", "weekly_value": { "sum": "sum the 7 values", "avg": "mean of the 7 values", "last": "not applicable unless explicitly classified as daily" }, "incomplete_week": { "weekly_value": null, "status": "incomplete", "coverage": "observation_count / 7" } }, "intermittent_metrics": { "expected_observations_per_week": 1, "weekly_window_rule": "Monday-through-Sunday local dates", "observation_count": "distinct valid daily observation dates, 0..7", "coverage": "min(observation_count, 1) / 1", "weekly_value": { "last": "latest finite observation by timestamp within the week", "avg": "only if registry explicitly chooses avg" }, "no_observation": { "weekly_value": null, "status": "no_observation", "coverage": 0 } } } ``` Recommended minimal weekly point shape: ```json { "week": "2026-W24", "week_start": "2026-06-08", "week_end": "2026-06-14", "value": 70.5, "aggregation": "last", "cadence": "intermittent", "observation_count": 1, "expected_observations": 1, "coverage": 1.0, "status": "observed", "quality": "complete_for_cadence" } ``` For daily metrics, `expected_observations` is `7`; incomplete weekly values must remain `null`, never be divided by observed-day count. ## Machine-readable Apple inventory report design This is the proposed **non-UI, offline/read-only report artifact** contract. It can be generated from an explicitly injected read-only SQLite connection and must never change `APPLE_SOURCE_SPECS` or release unknown identifiers. ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "apple-health-source-inventory-v1", "type": "object", "required": [ "report_version", "scope", "release_policy", "summary", "identifiers" ], "properties": { "report_version": { "const": "apple-health-source-inventory-v1" }, "scope": { "type": "object", "required": ["source", "timezone", "data_access"], "properties": { "source": { "const": "apple_health_records" }, "timezone": { "const": "Europe/Zurich" }, "data_access": { "const": "read_only" } } }, "release_policy": { "type": "object", "required": ["automatic_release", "allowlist"], "properties": { "automatic_release": { "const": false }, "allowlist": { "const": "APPLE_SOURCE_SPECS" } } }, "summary": { "type": "object", "required": ["identifier_count", "released_count", "not_released_count"], "properties": { "identifier_count": { "type": "integer", "minimum": 0 }, "released_count": { "type": "integer", "minimum": 0 }, "not_released_count": { "type": "integer", "minimum": 0 } } }, "identifiers": { "type": "array", "items": { "type": "object", "required": [ "identifier", "released", "reason", "units", "observation_count", "numeric_observation_count", "priority" ], "properties": { "identifier": { "type": "string", "minLength": 1 }, "released": { "type": "boolean" }, "reason": { "enum": [ "explicit_allowlist_with_synthetic_contract", "not_explicitly_allowlisted" ] }, "units": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, "canonical_unit": { "type": ["string", "null"] }, "observation_count": { "type": "integer", "minimum": 0 }, "numeric_observation_count": { "type": "integer", "minimum": 0 }, "released_metric_ids": { "type": "array", "items": { "type": "string" } }, "priority": { "enum": [ "P0_released_contract", "P1_review_candidate", "P2_observed_not_released" ] } } } } } } ``` Deterministic population policy: - **Released (`true`)** only when `identifier in APPLE_SOURCE_SPECS`. - `reason`: `explicit_allowlist_with_synthetic_contract` - `priority`: `P0_released_contract` - `canonical_unit` and `released_metric_ids`: from `AppleSourceSpec`. - **Not released (`false`)** for every other observed identifier. - `reason`: `not_explicitly_allowlisted` - `priority`: `P2_observed_not_released` - `canonical_unit`: `null` - `released_metric_ids`: `[]` - Reserve `P1_review_candidate` only for a future, separately reviewed candidate registry; **observation counts alone must not create P1 or release a metric**. - For the documented production snapshot, acceptance should require: ```json { "identifier_count": 64, "released_count": 12, "not_released_count": 52 } ``` ## Files modified / created - **None.**