Revert "feat(topics): compute topic overviews and topic pages on the server"

This reverts commit cc04e0944d.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 10:46:55 +02:00
1 parent c1911e71e3
commit 0182f91092
30 files changed
+35 -3937

No files matched your search

+5 -758
View File
@@ -26754,95 +26754,6 @@ paths:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a4
"429": *a5
/api/topics:
get:
tags:
- Insights
summary: Topic overview (preview)
description: One entry per topic the record holds data for, with a headline value, a rule-based sentence and a 30-day
sparkline, computed from the same daily values the metric pages draw. No model, no AI gate. Topics follow the
record's module switches. Reads across sections, so a grant scoped to some of them is refused.
responses:
"200":
description: The overview.
content:
application/json:
schema:
$ref: "#/components/schemas/TopicOverviewEnvelope"
"401": *a1
"403": *a15
"404":
description: "`topics.preview_off`: the person reading has not switched the topics preview on (Settings > System >
Preview features). The preview is per account and off by default."
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a4
"429": *a5
/api/topics/{topic}:
get:
tags:
- Insights
summary: One topic (preview)
description: "A topic's signals with their usual ranges, the side-by-side series for `range`, relationships between its
signals (nothing is said below 14 paired days), 90-day trends, the signals it holds nothing for yet, and its
blocks: training load, lab markers with their trend (three readings spanning a year), screener trends, today's
cycle day. No model, no AI gate."
parameters:
- in: path
name: topic
schema:
type: string
enum:
- heart
- body
- blood-pressure
- sleep
- activity
- metabolism
- mind
- cycle
description: The topic, as its URL slug.
required: true
description: The topic, as its URL slug.
- in: query
name: range
schema:
default: 30d
description: The window the side-by-side series cover. The usual ranges, the trends and the relationships always read at
least the trailing 90 days.
type: string
enum:
- 7d
- 30d
- 90d
- 1y
description: The window the side-by-side series cover. The usual ranges, the trends and the relationships always read at
least the trailing 90 days.
responses:
"200":
description: The topic.
content:
application/json:
schema:
$ref: "#/components/schemas/TopicDetailEnvelope"
"401": *a1
"403": *a15
"404":
description: >-
`topics.preview_off`: the person reading has not switched the topics preview on (Settings > System > Preview
features). The preview is per account and off by default.
`topics.not_found`: no topic by that name. `topics.not_available`: the record's module switches do not offer
this topic.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a4
"429": *a5
/api/bugreport:
post:
security: []
@@ -38095,11 +38006,10 @@ components:
symptoms.definition.limitReached, symptoms.definition.notFound, symptoms.episode.notOpen,
symptoms.event.notFound, symptoms.invalid; timeline — timeline.invalid; tokens —
tokens.documents.ceiling_reached, tokens.measurements.ceiling_reached, tokens.workouts.ceiling_reached;
topics — topics.not_available, topics.not_found, topics.preview_off; vaccination —
vaccination.booster-invalid, vaccination.booster-no-antigen, vaccination.booster-out-of-scope,
vaccination.custom-vaccine-not-found, vaccination.custom.invalid, vaccination.custom.name-taken,
vaccination.custom.not-found, vaccination.encounter-not-found, vaccination.identity-required,
vaccination.invalid, vaccination.link.invalid, vaccination.not-found,
vaccination — vaccination.booster-invalid, vaccination.booster-no-antigen,
vaccination.booster-out-of-scope, vaccination.custom-vaccine-not-found, vaccination.custom.invalid,
vaccination.custom.name-taken, vaccination.custom.not-found, vaccination.encounter-not-found,
vaccination.identity-required, vaccination.invalid, vaccination.link.invalid, vaccination.not-found,
vaccination.practitioner-not-found; workout — workout.batch.invalid, workout.batch.payload_too_large,
workout.batch.source_not_permitted, workout.batch.too_large, workout.delete.synced_source; (unprefixed)
— about_me_conflict, ai_response_truncated, coach_prefs_conflict, dashboard_layout_conflict, empty_file,
@@ -72772,8 +72682,7 @@ components:
properties:
topics:
type: boolean
description: "The Topics area (`/api/topics`): overview pages that bring related signals together above the
single-metric Insights pages, which stay as they are. Off by default."
description: Topic overview pages above the single-metric Insights pages. Off by default.
required:
- topics
additionalProperties: false
@@ -85298,8 +85207,6 @@ components:
$ref: "#/components/schemas/AiAccountBlock"
coachReasoning:
$ref: "#/components/schemas/CoachReasoningState"
previewFeatures:
$ref: "#/components/schemas/PreviewFeatures"
recordSession:
anyOf:
- $ref: "#/components/schemas/RecordSessionState"
@@ -85330,7 +85237,6 @@ components:
- moduleAccess
- ai
- coachReasoning
- previewFeatures
- recordSession
- notificationPrefs
- onboarding
@@ -92363,665 +92269,6 @@ components:
- data
- error
additionalProperties: false
TopicOverviewEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/TopicOverview"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
TopicOverview:
type: object
properties:
topics:
type: array
items:
$ref: "#/components/schemas/TopicOverviewEntry"
empty:
type: array
items:
$ref: "#/components/schemas/TopicKey"
description: Offered topics with nothing recorded yet.
hiddenByModule:
type: array
items:
$ref: "#/components/schemas/TopicKey"
description: Topics whose every module is switched off for the record.
required:
- topics
- empty
- hiddenByModule
additionalProperties: false
TopicOverviewEntry:
type: object
properties:
key:
$ref: "#/components/schemas/TopicKey"
headline:
anyOf:
- $ref: "#/components/schemas/TopicHeadline"
- type: "null"
summary:
$ref: "#/components/schemas/TopicSummary"
tone:
type: string
enum:
- steady
- watch
- learning
sparkline:
type: array
items:
type: number
description: The headline signal's daily values over the last 30 days, oldest first; empty when the headline is a block.
recorded:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
total:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
required:
- key
- headline
- summary
- tone
- sparkline
- recorded
- total
additionalProperties: false
TopicKey:
type: string
enum:
- heart
- body
- blood-pressure
- sleep
- activity
- metabolism
- mind
- cycle
description: A topic, as its URL slug.
TopicHeadline:
oneOf:
- type: object
properties:
kind:
type: string
const: signal
key:
$ref: "#/components/schemas/TopicSignalKey"
value:
type: number
day:
type: string
description: A local calendar day, `YYYY-MM-DD`.
required:
- kind
- key
- value
- day
additionalProperties: false
- type: object
properties:
kind:
type: string
const: labs
inRange:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
total:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
required:
- kind
- inRange
- total
additionalProperties: false
- type: object
properties:
kind:
type: string
const: cycleDay
dayOfCycle:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
required:
- kind
- dayOfCycle
additionalProperties: false
- type: object
properties:
kind:
type: string
const: screener
instrument:
type: string
score:
type: number
required:
- kind
- instrument
- score
additionalProperties: false
type: object
TopicSignalKey:
anyOf:
- $ref: "#/components/schemas/MeasurementType"
- type: string
const: MOOD
description: "A daily series a topic reads: a `MeasurementType`, or `MOOD` for the day's mean mood score (1 to 5).
Values are in the type's stored unit (sleep in minutes asleep per night, keyed by the day the night ended);
clients convert for display as they do for the metric pages."
TopicSummary:
type: object
properties:
code:
type: string
enum:
- outside
- labs
- cycleDay
- steady
- learning
- empty
signal:
anyOf:
- $ref: "#/components/schemas/TopicSignalKey"
- type: "null"
direction:
anyOf:
- type: string
enum:
- above
- below
- type: "null"
count:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
total:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
required:
- code
- signal
- direction
- count
- total
additionalProperties: false
description: "The topic's one sentence as a code with parameters, picked by a fixed rule (never model text): `outside`
names the first signal outside its usual range and the direction; `labs` counts lab markers inside their window
(`count` of `total`); `cycleDay` gives today's cycle day in `count`; `steady` counts signals inside their range
(`count` of `total` recorded); `learning` while no range is established; `empty` without any data."
TopicDetailEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/TopicDetail"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
TopicDetail:
type: object
properties:
key:
$ref: "#/components/schemas/TopicKey"
range:
type: string
enum:
- 7d
- 30d
- 90d
- 1y
from:
type: string
description: A local calendar day, `YYYY-MM-DD`.
to:
type: string
description: A local calendar day, `YYYY-MM-DD`.
status:
type: string
enum:
- ready
- partial
- empty
summary:
$ref: "#/components/schemas/TopicSummary"
signals:
type: array
items:
$ref: "#/components/schemas/TopicSignal"
series:
type: array
items:
type: object
properties:
key:
$ref: "#/components/schemas/TopicSignalKey"
usual:
anyOf:
- $ref: "#/components/schemas/TopicUsualRange"
- type: "null"
points:
type: array
items:
type: object
properties:
day:
type: string
description: A local calendar day, `YYYY-MM-DD`.
value:
type: number
required:
- day
- value
additionalProperties: false
required:
- key
- usual
- points
additionalProperties: false
relationships:
type: array
items:
$ref: "#/components/schemas/TopicRelationship"
trends:
type: array
items:
$ref: "#/components/schemas/TopicTrend"
missing:
type: array
items:
$ref: "#/components/schemas/TopicSignalKey"
description: Signals the topic could show that hold no value yet.
labs:
anyOf:
- $ref: "#/components/schemas/TopicLabPanel"
- type: "null"
training:
anyOf:
- $ref: "#/components/schemas/WorkoutTrainingLoad"
- type: "null"
screeners:
anyOf:
- type: array
items:
$ref: "#/components/schemas/MentalHealthAssessmentTrend"
- type: "null"
cycle:
anyOf:
- $ref: "#/components/schemas/TopicCycleSummary"
- type: "null"
required:
- key
- range
- from
- to
- status
- summary
- signals
- series
- relationships
- trends
- missing
- labs
- training
- screeners
- cycle
additionalProperties: false
TopicSignal:
type: object
properties:
key:
$ref: "#/components/schemas/TopicSignalKey"
latest:
anyOf:
- type: object
properties:
day:
type: string
description: A local calendar day, `YYYY-MM-DD`.
value:
type: number
required:
- day
- value
additionalProperties: false
- type: "null"
usual:
anyOf:
- $ref: "#/components/schemas/TopicUsualRange"
- type: "null"
state:
type: string
enum:
- within
- above
- below
- learning
- none
description: "`latest` against `usual`: `learning` while the range needs more days, `none` without a value."
href:
description: The in-app single-metric page of this signal.
type:
- string
- "null"
required:
- key
- latest
- usual
- state
- href
additionalProperties: false
TopicUsualRange:
type: object
properties:
low:
type: number
high:
type: number
center:
type: number
days:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
description: Days the band was built from.
required:
- low
- high
- center
- days
additionalProperties: false
description: "The personal usual range over the trailing 90 days: median ± 3 scaled MAD of the daily values, never
narrower than the measurement's own wobble. Needs 7 days, else null."
TopicRelationship:
type: object
properties:
id:
type: string
x:
$ref: "#/components/schemas/TopicSignalKey"
y:
$ref: "#/components/schemas/TopicSignalKey"
lagDays:
anyOf:
- type: number
const: 0
- type: number
const: 1
description: "`x` on day d is held against `y` on day d + `lagDays`."
kind:
type: string
enum:
- correlation
- split
pairedDays:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
requiredDays:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
description: "Paired days needed before anything is said: 14."
status:
type: string
enum:
- insufficient
- no_clear_link
- linked
description: "`linked` only at p < 0.05 (Pearson, or Welch for a split); a pattern, never a cause."
r:
type:
- number
- "null"
pValue:
type:
- number
- "null"
split:
anyOf:
- type: object
properties:
threshold:
type: number
belowDays:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
otherDays:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
belowMean:
type: number
otherMean:
type: number
difference:
type: number
required:
- threshold
- belowDays
- otherDays
- belowMean
- otherMean
- difference
additionalProperties: false
- type: "null"
description: "For a split: the mean of `y` on days `x` sat below `threshold` against the other days."
points:
type: array
items:
type: object
properties:
x:
type: number
y:
type: number
required:
- x
- y
additionalProperties: false
description: Paired values for a scatter, at most 120; empty when insufficient.
required:
- id
- x
- y
- lagDays
- kind
- pairedDays
- requiredDays
- status
- r
- pValue
- split
- points
additionalProperties: false
TopicTrend:
type: object
properties:
key:
$ref: "#/components/schemas/TopicSignalKey"
status:
type: string
enum:
- ok
- insufficient
days:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
change:
description: The least-squares change across the trailing 90 days, in the stored unit. Needs 14 days.
type:
- number
- "null"
weekly:
type: array
items:
type: object
properties:
day:
type: string
description: A local calendar day, `YYYY-MM-DD`.
value:
type: number
required:
- day
- value
additionalProperties: false
description: Weekly means, each keyed by its Monday.
required:
- key
- status
- days
- change
- weekly
additionalProperties: false
TopicLabPanel:
type: object
properties:
markers:
type: array
items:
$ref: "#/components/schemas/TopicLabMarker"
description: At most 24, newest reading first.
inRange:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
withRange:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
latestOn:
anyOf:
- type: string
description: A local calendar day, `YYYY-MM-DD`.
- type: "null"
required:
- markers
- inRange
- withRange
- latestOn
additionalProperties: false
TopicLabMarker:
type: object
properties:
biomarkerId:
type: string
name:
type: string
unit:
type: string
latest:
type: number
takenOn:
type: string
description: A local calendar day, `YYYY-MM-DD`.
low:
type:
- number
- "null"
high:
type:
- number
- "null"
status:
type: string
enum:
- in-range
- below
- above
- unknown
readings:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
trend:
$ref: "#/components/schemas/BiomarkerTrend"
required:
- biomarkerId
- name
- unit
- latest
- takenOn
- low
- high
- status
- readings
- trend
additionalProperties: false
TopicCycleSummary:
type: object
properties:
today:
anyOf:
- type: object
properties:
dayOfCycle:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
phase:
type:
- string
- "null"
required:
- dayOfCycle
- phase
additionalProperties: false
- type: "null"
lengths:
type: array
items:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
averageLength:
type:
- number
- "null"
periodsLogged:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
required:
- today
- lengths
- averageLength
- periodsLogged
additionalProperties: false
__schema0:
anyOf:
- type: string