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
patientIdis the active Patient’s ID and theObservationpolicy 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/dLadjacent 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