5. Build the five-domain wellness model
What we are building
A dashboard model that keeps five areas visible while preserving where every value came from. A reflection remains a reflection; an imported sample remains a measurement.
What you should already have
- An authenticated member and optional health import from steps 3 and 4.
- The FHIR Observation, Questionnaire and QuestionnaireResponse references.
The implementation
Use a small presentation model to drive the five cards. It describes source and meaning; it does not create a second persistence format.
export type WellnessDomain =
| "sleep"
| "movement"
| "nutrition"
| "stress"
| "recovery";
type DomainCard = {
id: WellnessDomain;
titleKey: string;
sources: Array<"questionnaire-response" | "observation">;
sourceLabelKey: string;
summaryKey: string;
};
export const wellnessDomains: DomainCard[] = [
{
id: "sleep",
titleKey: "domains.sleep.title",
sources: ["questionnaire-response", "observation"],
sourceLabelKey: "sources.selfReported",
summaryKey: "domains.sleep.reflection",
},
{
id: "movement",
titleKey: "domains.movement.title",
sources: ["questionnaire-response", "observation"],
sourceLabelKey: "sources.selfReported",
summaryKey: "domains.movement.reflection",
},
{
id: "nutrition",
titleKey: "domains.nutrition.title",
sources: ["questionnaire-response"],
sourceLabelKey: "sources.selfReported",
summaryKey: "domains.nutrition.reflection",
},
{
id: "stress",
titleKey: "domains.stress.title",
sources: ["questionnaire-response"],
sourceLabelKey: "sources.selfReported",
summaryKey: "domains.stress.reflection",
},
{
id: "recovery",
titleKey: "domains.recovery.title",
sources: ["questionnaire-response"],
sourceLabelKey: "sources.selfReported",
summaryKey: "domains.recovery.reflection",
},
];
Keep measured context in its own view model. Sleep sessions and step totals are imported in step 4; resting heart rate is another optional measurement. They complement the five domains but do not become additional reflections or a recovery score.
In each card, show the source label near the value:
Self-reportedfor a member's QuestionnaireResponse.Imported from Apple HealthorImported from Health Connectfor a supported Observation.No data yetwhen there is no value. Do not fill the gap with a fabricated zero.
Important Ovok decisions
- Nutrition, stress, perceived sleep quality, movement intentions and perceived recovery are captured in FHIR QuestionnaireResponses, not as passively measured Observations.
- Imported sleep-session intervals, local-day step totals and resting heart rate are separate FHIR Observations with their source coding and time semantics intact.
- The SDK uses source coding systems for sleep and step records; do not describe them as LOINC-coded or treat an elapsed sleep interval as time asleep.
- A QuestionnaireResponse is the historical answer record; changing a reflection creates a later response rather than rewriting how the person previously felt.
- The model does not calculate a composite or clinical score.
- Keep app-owned labels in Ovok CMS translation groups. The stable keys above are examples of app keys, not server FHIR fields.
Expected result
Your dashboard has five consistent cards and a separate measured-data area. Each visible value makes clear whether it was reported by the member or imported.
Common errors and troubleshooting
| Symptom | Check |
|---|---|
| A reflection is stored as an Observation | Keep subjective answers in QuestionnaireResponse resources. |
| A missing metric displays as zero | Use an explicit empty state; absence is not a measured zero. |
| The UI says resting heart rate indicates recovery | Remove the inference. Display the measurement without a readiness label. |
| The cards repeat raw FHIR JSON | Map the response to a presentation model while retaining the original resource for audit and navigation. |