// Local browser QA only. Every person, clinic, number and record here is fictional.
import type * as T from "../../src/api/types";
import type { Source } from "../../src/types";
import { ADAEZE_RECORDS, MARIAM_APP_RECORD, MARIAM_RECORDS, TUNDE_RECORDS } from "./records";
export const FIXTURE_EMAIL = "emeka.okafor@example.test";
/** The fixture's email code and second-factor code. Fixture-only values. */
export const FIXTURE_EMAIL_CODE = "246810";
export const FIXTURE_TOTP_CODE = "135790";
export const FIXTURE_TOTP_SECRET = "JBSWY3DPEHPK3PXP";
/** clarifies: the original line a clarification record is linked to (REV-5). */
export type StoredRecord = T.ContextRecord & { text: string | null; clarifies?: Source["clarification"] };
export type Shared = {
medicines: Omit<T.SharedMedicines, "asOf" | "lastLookedAt">;
doses: Omit<T.SharedDoses, "asOf" | "lastLookedAt">;
daily: T.DailyCheckIn[];
records: Omit<T.SharedRecords, "asOf" | "lastLookedAt">;
};
export type StoredPatient = {
base: Omit<
T.PatientDetail,
"medicines" | "doses" | "attention" | "checkInLimits" | "activeCheckInId" | "lastCheckIn"
>;
shared: Shared | null;
lastDaily: string | null;
lastLooked: Record<string, string | null>;
newSinceLastCheck: boolean;
usedThisWeekBefore: number;
variant: "film" | "urgent";
activity: T.ActivityEvent[];
};
const DAY = 86_400_000;
export const iso = (ms: number) => new Date(ms).toISOString();
let counter = 0;
export const nextId = (prefix: string) => `${prefix}-${Date.now().toString(36)}${(counter++).toString(36)}`;
const weekdays = (open: string, close: string, saturday?: [string, string]) => [
...[1, 2, 3, 4, 5].map((day) => ({ day, open, close })),
...(saturday ? [{ day: 6, open: saturday[0], close: saturday[1] }] : []),
];
const allDay = [0, 1, 2, 3, 4, 5, 6].map((day) => ({ day, open: "00:00", close: "24:00" }));
export function clinicianSeed(): T.ClinicianProfile {
return {
id: "clin-okafor",
email: FIXTURE_EMAIL,
title: "Dr",
fullName: "Dr Emeka Okafor",
firstName: "Emeka",
lastName: "Okafor",
spokenName: "Doctor Okafor",
pronoun: "he",
profession: "doctor",
licensingBody: "MDCN",
licenceNumber: "MDCN/R/104233",
licenceExpiresAt: "2027-03-31",
verifiedAt: "2026-10-01T09:00:00Z",
photoUrl: null,
mobilePhone: "+234 803 555 0101",
idleLockMinutes: 15,
notifications: { browser: false, email: true },
termsAcceptedAt: null,
};
}
export function clinicSeed(alwaysOpen: boolean): T.ClinicProfile {
return {
id: "clinic-surulere",
role: "owner",
version: 1,
name: "Surulere Family Clinic",
address: "12 Example Close, Surulere, Lagos",
phone: "+234 1 555 0142",
country: "NG",
timeZone: "Africa/Lagos",
openingHours: alwaysOpen ? allDay : weekdays("08:00", "18:00", ["09:00", "14:00"]),
alertWatchHours: alwaysOpen ? allDay : weekdays("08:00", "18:00", ["09:00", "14:00"]),
backupContact: { name: "Nurse Bisi Ade", phone: "+2348035550199", email: "bisi.ade@surulere-clinic.example.test" },
emergencyNumbers: [
{ number: "767", label: "Lagos State emergency", testedAt: "2026-10-01T10:00:00Z", source: "clinic" },
{ number: "112", label: "National emergency", testedAt: "2026-10-01T10:05:00Z", source: "clinic" },
],
};
}
function base(
id: string,
name: string,
kind: T.PatientKind,
dob: string | null,
now: number,
change: Partial<StoredPatient["base"]> = {},
): StoredPatient["base"] {
const age = dob ? Math.floor((now - Date.parse(dob)) / (365.25 * DAY)) : null;
return {
id,
kind,
name,
firstName: name.split(" ")[0],
age,
sharing: null,
lastActivityAt: iso(now - 3 * DAY),
dateOfBirth: dob,
birthYear: dob ? Number(dob.slice(0, 4)) : null,
sex: null,
phone: null,
pronoun: null,
preferredTitle: null,
language: "en",
hasOwnAccount: kind === "connected",
identityOnFile: dob ? "date_of_birth" : "none",
timeZone: "Africa/Lagos",
quietHours: { start: "22:00", end: "07:00" },
blockedTimes: [],
lastCheckId: null,
newInformation: null,
...change,
};
}
const sharing = (
sharedBy: string[],
scopes: T.ShareScope[],
calls: boolean,
changedAt: string,
status: T.SharingStatus = "connected",
): T.Sharing => ({ status, sharedBy, scopes, allowsCheckInCalls: calls, withheldSome: false, changedAt });
const counts = (onTime: number, late: number, skipped: number, missed: number, expected: number): T.DoseCounts => ({
onTime,
late,
skipped,
missed,
expected,
});
/** Mariam's shared data once she is connected (SHR-2 to SHR-5). */
export function mariamShared(now: number): Shared {
const by = (who: string, daysAgo: number) => ({ addedBy: who, addedAt: iso(now - daysAgo * DAY), changedAt: iso(now - daysAgo * DAY) });
return {
medicines: {
withheldSome: false,
medicines: [
{ id: "m-metformin", name: "Metformin", strength: "500 mg", schedule: "1 tablet twice a day", startDate: "2024-03-10", purpose: "Diabetes", origin: "prescribed", status: "current", stoppedAt: null, prescriber: "Dr Emeka Okafor", ...by("Funke", 28) },
{ id: "m-amlodipine", name: "Amlodipine", strength: "5 mg", schedule: "1 tablet once a day", startDate: "2023-11-01", purpose: "Blood pressure", origin: "prescribed", status: "current", stoppedAt: null, prescriber: null, ...by("Mariam", 60) },
{ id: "m-amoxicillin", name: "Amoxicillin", strength: "500 mg", schedule: "1 capsule three times a day", startDate: "2026-10-23", purpose: "Chest infection", origin: "prescribed", status: "current", stoppedAt: null, prescriber: "Dr Emeka Okafor", ...by("Funke", 7) },
],
allergies: [
{ id: "a-penicillin", substance: "Penicillin", reaction: "Hives", onset: "Within a day", severity: null, year: 2019, treatment: null, recordedWhere: "Ikeja hospital", ...by("Funke", 28) },
],
conditions: [
{ id: "c-diabetes", name: "Type 2 diabetes", ...by("Funke", 28) },
{ id: "c-bp", name: "Hypertension", ...by("Funke", 28) },
],
profile: { dateOfBirth: "1959-03-04", age: 67, sex: "female", weightKg: null, weightRecordedAt: null, pregnancy: null },
},
doses: {
week: { taken: 12, expected: 14, onTime: 12 },
perMedicine: [
{ medicineId: "m-metformin", name: "Metformin", schedule: "Morning and evening", week: counts(12, 0, 2, 0, 14), last30: counts(51, 3, 6, 0, 60), changedAt: iso(now - 0.4 * DAY) },
],
patterns: [{ text: "Evening Metformin skipped 2 times this week", medicineId: "m-metformin", changedAt: iso(now - 0.4 * DAY) }],
},
daily: [
{ id: "d1", date: iso(now - DAY).slice(0, 10), mood: "Okay", energy: "Low", sleep: "Fair", sideEffects: ["Loose stools"], note: "Stomach running since Sunday.", changedAt: iso(now - DAY) },
{ id: "d2", date: iso(now - 3 * DAY).slice(0, 10), mood: "Good", energy: "Fair", sleep: "Good", sideEffects: [], note: null, changedAt: iso(now - 3 * DAY) },
],
records: {
labs: [
{ id: "l-egfr", name: "eGFR", value: "38", unit: "mL/min/1.73m2", range: "Above 60", flag: "low", date: "2026-10-12", ...by("Funke", 17) },
{ id: "l-egfr-aug", name: "eGFR", value: "47", unit: "mL/min/1.73m2", range: "Above 60", flag: "low", date: "2026-08-14", ...by("Funke", 78) },
{ id: "l-egfr-apr", name: "eGFR", value: "58", unit: "mL/min/1.73m2", range: "Above 60", flag: "low", date: "2026-04-03", ...by("Funke", 211) },
{ id: "l-hba1c", name: "HbA1c", value: "7.9", unit: "%", range: "Below 7.0", flag: "high", date: "2026-09-15", ...by("Funke", 40) },
{ id: "l-hba1c-jun", name: "HbA1c", value: "7.4", unit: "%", range: "Below 7.0", flag: "high", date: "2026-06-10", ...by("Funke", 143) },
],
documents: [
{ id: "doc-yaba", title: "Yaba lab kidney report", kind: "Lab report", date: "2026-10-12", ...by("Funke", 17) },
{ id: "doc-ikeja", title: "Ikeja hospital discharge letter", kind: "Discharge letter", date: "2026-09-02", ...by("Funke", 40) },
],
},
};
}
function simpleShared(now: number, meds: [string, string, string][], taken: number, expected: number): Shared {
const at = iso(now - 2 * DAY);
return {
medicines: {
withheldSome: false,
medicines: meds.map(([name, strength, schedule], i) => ({
id: `m-${name.toLowerCase()}`, name, strength, schedule, startDate: "2025-01-10", purpose: null, origin: "prescribed" as const,
status: "current" as const, stoppedAt: null, prescriber: null, addedBy: "Patient", addedAt: iso(now - (20 + i) * DAY), changedAt: iso(now - (20 + i) * DAY),
})),
allergies: [],
conditions: [],
profile: { dateOfBirth: null, age: null, sex: null, weightKg: null, weightRecordedAt: null, pregnancy: null },
},
doses: {
week: { taken, expected, onTime: taken },
perMedicine: meds.map(([name], i) => ({ medicineId: `m-${name.toLowerCase()}`, name, schedule: null, week: counts(i ? 7 : taken - 7 * (meds.length - 1), 0, 0, i ? 0 : expected - taken, i ? 7 : expected - 7 * (meds.length - 1)), last30: counts(28, 1, 1, 0, 30), changedAt: at })),
patterns: [],
},
daily: [{ id: "d1", date: iso(now - DAY).slice(0, 10), mood: "Good", energy: "Good", sleep: "Good", sideEffects: [], note: null, changedAt: iso(now - DAY) }],
records: { labs: [], documents: [] },
};
}
export type Seed = {
patients: StoredPatient[];
records: StoredRecord[];
invitations: (T.Invitation & { token: string; connect: () => StoredPatient })[];
};
const record = (patientId: string, r: { key: string; title: string; type: T.RecordType; place: string | null; recordDate: string | null; text: string }, addedAt: number, origin: T.RecordOrigin = "upload", addedBy = "Dr Emeka Okafor"): StoredRecord => ({
id: `rec-${patientId.replace(/^p-/, "")}-${r.key}`,
patientId,
title: r.title,
type: r.type,
place: r.place,
recordDate: r.recordDate,
addedBy,
addedAt: iso(addedAt),
origin,
fileName: origin === "upload" ? `${r.title.toLowerCase().replace(/\s+/g, "-")}.txt` : null,
contentType: origin === "upload" ? "text/plain" : null,
sizeBytes: r.text.length,
pages: null,
reading: "read",
readingError: null,
readingErrorCode: null,
lineCount: r.text.split("\n").length,
heldBack: null,
clarification: null,
text: r.text,
});
const event = (now: number, minutesAgo: number, actor: string, actorKind: T.ActivityEvent["actorKind"], action: string): T.ActivityEvent => ({
id: nextId("act"),
at: iso(now - minutesAgo * 60_000),
actor,
actorKind,
action,
});
export function seed(now: number): Seed {
const mariam: StoredPatient = {
base: base("p-mariam", "Mariam Adeyemi", "clinic_added", "1959-03-04", now, { sex: "female", lastActivityAt: iso(now - 2 * 3_600_000), hasOwnAccount: false }),
shared: null,
lastDaily: null,
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 0,
variant: "film",
activity: [event(now, 180, "Dr Emeka Okafor", "clinician", "Dr Okafor added Mariam Adeyemi as a clinic patient and confirmed a care relationship.")],
};
const adaeze: StoredPatient = {
base: base("p-adaeze", "Adaeze Nwosu", "connected", "1968-05-11", now, {
sex: "female", pronoun: null, phone: "+234 803 555 0161", lastActivityAt: iso(now - 50 * 60_000),
sharing: sharing(["Adaeze"], ["medicines", "doses", "check_ins", "records"], true, iso(now - 50 * 60_000)),
newInformation: { since: iso(now - 50 * 60_000), items: ["A new potassium result from MARMAR Health"] },
}),
shared: simpleShared(now, [["Lisinopril", "10 mg", "1 tablet once a day"], ["Spironolactone", "25 mg", "1 tablet once a day"]], 13, 14),
lastDaily: iso(now - DAY),
lastLooked: { medicines: iso(now - 2 * DAY), doses: iso(now - 2 * DAY), check_ins: iso(now - 2 * DAY), records: iso(now - 2 * DAY) },
newSinceLastCheck: true,
usedThisWeekBefore: 0,
variant: "urgent",
activity: [event(now, 50, "Adaeze", "patient", "Adaeze shared a new potassium result.")],
};
adaeze.shared!.records.labs.push({ id: "l-k", name: "Potassium", value: "6.8", unit: "mmol/L", range: "3.5 to 5.0", flag: "high", date: "2026-10-23", addedBy: "Adaeze", addedAt: iso(now - 50 * 60_000), changedAt: iso(now - 50 * 60_000) });
const ngozi: StoredPatient = {
base: base("p-ngozi", "Ngozi Eze", "connected", "1981-02-19", now, {
sex: "female", pronoun: null, phone: "+234 803 555 0174", lastActivityAt: iso(now - 95 * 60_000),
sharing: sharing(["Ngozi"], ["medicines", "doses", "check_ins", "records"], true, iso(now - 95 * 60_000)),
}),
shared: simpleShared(now, [["Amlodipine", "10 mg", "1 tablet once a day"]], 7, 7),
lastDaily: iso(now - 4 * 3_600_000),
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 0,
variant: "film",
activity: [],
};
const femi: StoredPatient = {
base: base("p-femi", "Femi Balogun", "connected", "1977-08-02", now, {
pronoun: null, phone: "+234 803 555 0187", lastActivityAt: iso(now - 26 * 3_600_000),
sharing: sharing(["Femi"], ["medicines", "doses", "check_ins"], true, iso(now - 26 * 3_600_000)),
}),
shared: simpleShared(now, [["Metformin", "500 mg", "1 tablet twice a day"]], 11, 14),
lastDaily: iso(now - 2 * DAY),
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 1,
variant: "film",
activity: [],
};
const chidi: StoredPatient = {
base: base("p-chidi", "Chidi Okeke", "connected", "1987-01-30", now, {
sex: "male", pronoun: null, phone: "+234 803 555 0112", lastActivityAt: iso(now - 4 * DAY),
sharing: sharing(["Chidi"], ["medicines", "doses"], false, iso(now - 4 * DAY)),
}),
shared: simpleShared(now, [["Losartan", "50 mg", "1 tablet once a day"]], 6, 7),
lastDaily: null,
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 0,
variant: "film",
activity: [event(now, 4 * 1440, "Chidi", "patient", "Chidi shared medicines and doses taken with Dr Okafor.")],
};
const halima: StoredPatient = {
base: base("p-halima", "Halima Yusuf", "connected", "1955-06-14", now, {
sex: "female", phone: "+234 803 555 0125", lastActivityAt: iso(now - 6 * DAY),
sharing: sharing(["Halima"], ["medicines", "doses"], false, iso(now - DAY), "stopped"),
}),
shared: null,
lastDaily: null,
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 0,
variant: "film",
activity: [event(now, 1440, "Halima", "patient", "Halima stopped sharing with Dr Okafor. Live access ended.")],
};
const tunde: StoredPatient = {
base: base("p-tunde", "Tunde Bakare", "clinic_added", "1981-04-09", now, { sex: "male", lastActivityAt: iso(now - 12 * DAY), hasOwnAccount: false }),
shared: null,
lastDaily: null,
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 0,
variant: "film",
activity: [event(now, 12 * 1440, "Dr Emeka Okafor", "clinician", "Dr Okafor added Tunde Bakare as a clinic patient.")],
};
const records = [
// FIXTURE_FILM=1 leaves out the hospital note, so the demo film can add it as a photo.
...MARIAM_RECORDS.filter((r) => !(process.env.FIXTURE_FILM === "1" && r.key === "hospital")).map((r, i) => record("p-mariam", r, now - (180 - i) * 60_000)),
...ADAEZE_RECORDS.map((r, i) => record("p-adaeze", r, now - (3 * 1440 - i) * 60_000, i ? "app" : "upload", i ? "Adaeze (MARMAR Health)" : "Dr Emeka Okafor")),
...TUNDE_RECORDS.map((r) => record("p-tunde", r, now - 12 * DAY)),
];
const invitations: Seed["invitations"] = [
{
id: "inv-mariam",
token: "mariam-invite-demo",
patientName: "Mariam Adeyemi",
patientAge: 67,
patientPronoun: "she",
withPerson: { name: "Funke", relation: "daughter" },
scopes: ["medicines", "doses", "check_ins", "records"],
allowsCheckInCalls: true,
withheldSome: false,
sentAt: iso(now - 25 * 60_000),
expiresAt: iso(now + 14 * DAY - 25 * 60_000),
possibleMatch: { patientId: "p-mariam", name: "Mariam Adeyemi", dateOfBirth: "1959-03-04" },
connect: () => mariam,
},
{
id: "inv-ibrahim",
token: "ibrahim-invite-demo",
patientName: "Ibrahim Musa",
patientAge: 58,
patientPronoun: "he",
withPerson: null,
scopes: ["medicines", "doses"],
allowsCheckInCalls: false,
withheldSome: false,
sentAt: iso(now - 2 * DAY),
expiresAt: iso(now + 12 * DAY),
possibleMatch: null,
connect: () => ({
base: base("p-ibrahim", "Ibrahim Musa", "connected", "1968-09-21", now, {
sex: "male", pronoun: null, phone: "+234 803 555 0133", lastActivityAt: iso(now),
sharing: sharing(["Ibrahim"], ["medicines", "doses"], false, iso(now)),
}),
shared: simpleShared(now, [["Nifedipine", "30 mg", "1 tablet once a day"]], 7, 7),
lastDaily: null,
lastLooked: {},
newSinceLastCheck: false,
usedThisWeekBefore: 0,
variant: "film",
activity: [],
}),
},
];
return { patients: [mariam, adaeze, ngozi, femi, chidi, halima, tunde], records, invitations };
}
/** Mariam once she accepts: her shares, Funke, and the app record (REC-8). */
export function connectMariam(p: StoredPatient, now: number): StoredRecord {
p.base = {
...p.base,
kind: "connected",
hasOwnAccount: true,
pronoun: null,
preferredTitle: "Mrs Adeyemi",
phone: "+234 803 555 0147",
sharing: sharing(["Mariam", "Funke"], ["medicines", "doses", "check_ins", "records"], true, iso(now)),
lastActivityAt: iso(now),
};
p.shared = mariamShared(now);
p.lastDaily = iso(now - DAY);
return {
...record("p-mariam", { key: "app", title: "From MARMAR Health", type: "other", place: "MARMAR Health app", recordDate: "2026-10-23", text: MARIAM_APP_RECORD }, now, "app", "Funke (MARMAR Health)"),
};
}