Skip to main content

Step 9: Build screening results and history

What we are building​

A result view that shows the latest reviewed FINDRISC category and latest glucose measurement separately, plus a history list that preserves each resource’s date and provenance.

What you should already have​

  • Saved completed QuestionnaireResponses from step 7.
  • Saved blood-glucose Observations from step 8.
  • The reviewed local scoring function from step 5.

The implementation​

Query the Patient’s questionnaire history​

Create app/(patient)/history.tsx:

import {
MeasurementTypeKey,
useCurrentPatientProfile,
useObservations,
usePatientQuestionnaireResponses,
usePatientQuestionnaires,
} from "@ovok/core";
import { Text, View } from "react-native";

import { ovokConfig } from "../../src/config/ovok";
import { scoreFindrisc } from "../../src/features/findrisc/scoring";

const glucoseTypes = [MeasurementTypeKey.bloodGlucose] as const;

export default function HistoryScreen() {
const { data: current } = useCurrentPatientProfile();
const patientId = current?.profile.id;
const { data: forms, loading: formsLoading } = usePatientQuestionnaires({
_id: ovokConfig.questionnaireId,
});
const questionnaire = forms?.find(
(form) => form.id === ovokConfig.questionnaireId,
);
const { data: responses = [], loading: responsesLoading } =
usePatientQuestionnaireResponses({
...(patientId ? { patient: `Patient/${patientId}` } : {}),
questionnaire: `Questionnaire/${ovokConfig.questionnaireId}`,
_sort: "-authored",
_count: 50,
});
const {
measurements,
loading: glucoseLoading,
error: glucoseError,
} = useObservations({
types: glucoseTypes,
patientId,
count: 50,
sort: "-date",
});

const assessments =
questionnaire?.url === ovokConfig.questionnaireUrl &&
questionnaire.version === ovokConfig.questionnaireVersion
? responses.flatMap((response) => {
try {
return [
{
response,
score: scoreFindrisc(questionnaire, response, {
url: ovokConfig.questionnaireUrl,
version: ovokConfig.questionnaireVersion,
}),
},
];
} catch {
return [];
}
})
: [];

const latestAssessment = assessments[0];
const latestGlucose = measurements[0];

return (
<View>
<Text>Screening results</Text>
{formsLoading || responsesLoading ? (
<Text>Loading assessment history…</Text>
) : latestAssessment ? (
<Text>
FINDRISC: {latestAssessment.score.total} · {latestAssessment.score.category} ·{" "}
{latestAssessment.response.authored ?? "Date unavailable"}
</Text>
) : (
<Text>No completed FINDRISC assessment is available.</Text>
)}

<Text>Blood-glucose measurements</Text>
{glucoseLoading ? (
<Text>Loading readings…</Text>
) : glucoseError ? (
<Text accessibilityRole="alert">Readings could not be loaded.</Text>
) : latestGlucose ? (
<Text>
{latestGlucose.bloodGlucose ?? "Value unavailable"} mg/dL ·{" "}
{latestGlucose.recordedAt?.toISOString() ?? "Time unavailable"}
</Text>
) : (
<Text>No blood-glucose readings are available.</Text>
)}

<Text>Assessment history</Text>
{assessments.map(({ response, score }) => (
<Text key={response.id}>
FINDRISC · {score.total} · {score.category} · {response.authored ?? "Date unavailable"}
</Text>
))}

<Text>Measurement history</Text>
{measurements.map((reading, index) => (
<Text key={reading.id ?? `${reading.recordedAt?.toISOString()}-${index}`}>
Blood glucose · {reading.bloodGlucose ?? "Value unavailable"} mg/dL ·{" "}
{reading.recordedAt?.toISOString() ?? "Time unavailable"}
</Text>
))}
</View>
);
}

The questionnaire history uses the typed Patient hook, scoped to the signed-in Patient and questionnaire. useObservations maps supported blood-glucose Observations to typed measurements. It returns the original resources too when a view needs FHIR metadata; see useObservations.

The example uses the SDK’s mapped recordedAt when available and otherwise says the time is unavailable. Replace ISO timestamps with a locale-aware date formatter from your UI. For long histories, add date filters and FHIR pagination rather than silently showing an incomplete list.

Keep the result language neutral​

The result card may say:

FINDRISC is a risk assessment. It does not diagnose diabetes. A blood-glucose reading is shown separately and is not interpreted here.

Translate this app-owned copy through Ovok CMS. If a particular validated instrument release includes different approved result language, use the authorized wording and clinical review for that release. Do not add risk probabilities or follow-up advice from a different version of the questionnaire.

Important Ovok decisions​

  • A FINDRISC QuestionnaireResponse and a glucose Observation are separate FHIR records with separate meaning and provenance.
  • Recompute each assessment with the matching reviewed questionnaire version. If it cannot be verified, omit the score and retain the response for review.
  • Do not combine the score and glucose into a new “diabetes score.”
  • A single glucose value in this UI does not diagnose diabetes.
  • The current example requests up to 50 records. Implement pagination if a deployment can exceed that limit.

Expected result​

The patient sees the latest FINDRISC result and glucose reading as separate cards, plus a history list whose rows identify the source type and date.

Common errors and troubleshooting​

  • An old assessment has no score: confirm its Questionnaire reference and version; do not score it with a newer map.
  • No glucose reading appears after a successful save: confirm patientId is the active Patient’s ID and the Observation policy allows that Patient to read their own record.
  • The history is truncated: add paging or date bounds; do not present the first 50 records as “all history.”
  • A glucose number is displayed without its unit: render mg/dL adjacent to the numeric value.
  • The app suggests a diagnosis or treatment action: remove that logic; this tutorial presents risk and measurement data without clinical interpretation.

Previous / Next​

Previous: connect TeleBGM and save readings · Next: settings, localisation, and validation