feat(daily): add the daily-digest spine and priority-card primitive

Introduce the two foundational primitives of the unified daily-value
system, with no user surface yet:

- PriorityItem: the one wire-serialisable model behind every "worth a
  look" rail item, coach check-in, milestone, and device-verdict pointer.
  Its Lucide glyph is derived from the closed `kind` enum in the renderer
  so the model itself stays JSON-serialisable across the API and future
  native-client boundaries.
- PriorityCard: a calm, token-only, reduced-motion-aware card composed
  entirely from the shipped Card / TileHeader / ProseBlocks primitives —
  no new visual language, semantic-token status wash only, no markdown.
- buildDailyDigest: one pure, deterministic server composer that assembles
  the day's read from data that already exists — the nightly briefing
  lifted read-only from the insights cache plus the dashboard-snapshot
  health score, meds-today, and sleep freshness. It computes no analytics
  and never reaches an AI provider. It emits the DailyDigest DTO the Today
  surface, the daily push, and a future iOS widget will all consume.
  Deterministic item builders cover overdue doses, broken integrations,
  and due Vorsorge reminders; the push line prefers the cached briefing
  lead with a deterministic score floor so a keyless self-hoster still
  gets a first-class line.

The read seam GET /api/daily/digest gathers the cached inputs, gates on
the insights module (cookie or Bearer; userId from the session), and
returns the digest. OpenAPI regenerated; the route is covered by the
module gate inventory. Digest strings ship in all six locales.
This commit is contained in:
Marc-André Bombeck committed 2026-07-16 19:13:01 +02:00
1 parent ecc34880ba
commit ba6c0990bf
18 files changed
+1580

No files matched your search

+182
View File
@@ -10706,6 +10706,26 @@ paths:
"401": *a1
"422": *a3
"429": *a4
/api/daily/digest:
get:
tags:
- Insights
summary: The unified daily digest
description: "Assembles the day's read from already-cached data: the nightly briefing lifted read-only from the insights
cache, the dashboard-snapshot health score / meds-today / sleep freshness, plus deterministic integration-status
and Vorsorge reads for the 'worth a look' rail. No provider call, no warm-on-mount. Requires the insights
module. Cookie or Bearer auth."
responses:
"200":
description: The daily digest.
content:
application/json:
schema:
$ref: "#/components/schemas/DailyDigestEnvelope"
"401": *a1
"403": *a5
"422": *a3
"429": *a4
components:
schemas:
EncryptedExportRequest:
@@ -29772,6 +29792,168 @@ components:
- data
- error
additionalProperties: false
DailyDigestEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/DailyDigest"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
DailyDigest:
type: object
properties:
generatedAt:
type: string
description: ISO-8601 instant the digest was read.
phase:
type: string
enum:
- provisional
- final
description: Freshness lifecycle — 'final' once last night's sleep is in.
sleepPending:
type: boolean
description: Sleep tracked but last night not yet recorded.
score:
anyOf:
- type: object
properties:
value:
type: number
band:
type: string
delta:
anyOf:
- type: number
- type: "null"
required:
- value
- band
- delta
additionalProperties: false
- type: "null"
description: Health score + band + week-over-week delta; null when none.
topSignal:
anyOf:
- type: object
properties:
sourceMetric:
type: string
tone:
type: string
enum:
- good
- watch
- info
headline:
type: string
nudge:
type: string
delta:
anyOf:
- type: string
- type: "null"
required:
- sourceMetric
- tone
- headline
- nudge
- delta
additionalProperties: false
- type: "null"
description: Clinical-priority top signal, lifted from the cached briefing.
briefingLead:
anyOf:
- type: string
- type: "null"
description: First sentence of the cached briefing paragraph.
line:
type: string
description: "Push / lock-screen line: cached-AI lead with a deterministic floor."
worthALook:
type: array
items:
$ref: "#/components/schemas/DailyPriorityItem"
description: Bounded 0–3 rail items, never padded.
required:
- generatedAt
- phase
- sleepPending
- score
- topSignal
- briefingLead
- line
- worthALook
additionalProperties: false
description: The one data spine of the daily-value system — composed from already-cached data, never a fresh AI call.
Consumed by the Today surface, the daily push, and a future iOS widget.
DailyPriorityItem:
type: object
properties:
kind:
type: string
enum:
- coach_checkin
- dose_window
- preventive_care
- sync_issue
- milestone
- ecg_new_recording
- tension_window
description: Closed rail-item kind.
title:
type: string
description: Localised headline (resolved server-side).
body:
description: Grounded one-liner, rendered as plain text.
type: string
status:
description: Semantic status wash — meaning, not decoration.
type: string
enum:
- success
- warning
- info
- destructive
actions:
maxItems: 3
type: array
items:
type: object
properties:
labelKey:
type: string
description: i18n key, resolved client-side.
intent:
type: string
description: Stable action token.
href:
description: Deep-link when navigational.
type: string
required:
- labelKey
- intent
additionalProperties: false
description: 1–3 one-tap actions.
moduleKey:
description: Provenance of the gate that admitted the item.
type: string
required:
- kind
- title
- actions
additionalProperties: false
description: One 'worth a look' rail item. The single model every daily-value consumer renders through PriorityCard.
MedicationScheduleOutput:
type: object
properties: