mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
fix(backup): carry the import ledger, and the keys of deleted imports
The backup carried neither the import ledger nor the tombstones of deleted documents, so after a restore onto a fresh instance a token importer stored every imported document the person had deleted on purpose again. The document filing section now carries documentImportKeys: the ledger's rows plus the own and alias keys of the documents waiting in Recently deleted, which still do not travel as documents. The restore writes them back, minus any key a restored live document holds, and counts them in the restore summary and the preview. DocumentImportKey moves from the excluded models to the two-ended ones, and the round trip, the column audit and the parity goldens carry a ledger row.
This commit is contained in:
1 parent
988af10a34
commit
205d8f257c
25 files changed
+906
-348
No files matched your search
@@ -44015,6 +44015,34 @@ components:
|
||||
- createdAt
|
||||
- updatedAt
|
||||
additionalProperties: {}
|
||||
documentImportKeys:
|
||||
default: []
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
sourceSystem:
|
||||
type: string
|
||||
enum:
|
||||
- PAPERLESS
|
||||
- PAPRA
|
||||
- OTHER
|
||||
sourceId:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 128
|
||||
sourceInstance:
|
||||
anyOf:
|
||||
- type: string
|
||||
maxLength: 512
|
||||
- type: "null"
|
||||
createdAt:
|
||||
type: string
|
||||
minLength: 1
|
||||
required:
|
||||
- sourceSystem
|
||||
- sourceId
|
||||
additionalProperties: {}
|
||||
nutrientDays:
|
||||
default: []
|
||||
type: array
|
||||
@@ -46536,6 +46564,7 @@ components:
|
||||
- documents
|
||||
- documentConditionLinks
|
||||
- extractedFacts
|
||||
- documentImportKeys
|
||||
- nutrientDays
|
||||
- healthProfile
|
||||
- healthProfileFacts
|
||||
|
||||
@@ -75,6 +75,7 @@ vi.mock("@/lib/db", () => ({
|
||||
// Empty for the same reason as the sections above.
|
||||
documentConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
extractedFact: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
documentImportKey: { findMany: vi.fn(async () => []) },
|
||||
ecgRecording: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
symptomDefinition: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
lifeEvent: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
|
||||
@@ -76,6 +76,7 @@ vi.mock("@/lib/db", () => ({
|
||||
// What a document was filed against, and what was read out of it.
|
||||
documentConditionLink: { findMany: vi.fn() },
|
||||
extractedFact: { findMany: vi.fn() },
|
||||
documentImportKey: { findMany: vi.fn(async () => []) },
|
||||
ecgRecording: { findMany: vi.fn() },
|
||||
symptomDefinition: { findMany: vi.fn() },
|
||||
lifeEvent: { findMany: vi.fn() },
|
||||
@@ -305,6 +306,24 @@ describe("v1.4.41 W-DELETED-2 — soft-delete invisibility", () => {
|
||||
const documentCall = vi.mocked(prisma.inboundDocument.findMany).mock
|
||||
.calls[0][0] as { select?: Record<string, unknown> };
|
||||
expect(documentCall.select).not.toHaveProperty("contentEncrypted");
|
||||
// The import ledger reads the source keys of documents waiting in
|
||||
// Recently deleted, and nothing else of them: no tombstone travels.
|
||||
for (const [args] of vi.mocked(prisma.inboundDocument.findMany).mock
|
||||
.calls) {
|
||||
const call = args as {
|
||||
where?: { deletedAt?: unknown };
|
||||
select?: Record<string, unknown>;
|
||||
};
|
||||
if (call.where?.deletedAt === null) continue;
|
||||
expect(Object.keys(call.select ?? {}).sort()).toEqual([
|
||||
"deletedAt",
|
||||
"sourceAliases",
|
||||
"sourceId",
|
||||
"sourceInstance",
|
||||
"sourceSystem",
|
||||
"userId",
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it("/api/export (legacy) scopes the measurement read to deletedAt: null", async () => {
|
||||
|
||||
@@ -67,6 +67,7 @@ vi.mock("@/lib/db", () => ({
|
||||
// Empty for the same reason as the sections above.
|
||||
documentConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
extractedFact: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
documentImportKey: { findMany: vi.fn(async () => []) },
|
||||
ecgRecording: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
symptomDefinition: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
lifeEvent: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
|
||||
@@ -39,7 +39,7 @@ interface ImportSourceKey {
|
||||
}
|
||||
|
||||
/** The columns a row needs for {@link importKeysOf}. */
|
||||
const SOURCE_KEY_SELECT = {
|
||||
export const SOURCE_KEY_SELECT = {
|
||||
userId: true,
|
||||
sourceSystem: true,
|
||||
sourceId: true,
|
||||
@@ -62,7 +62,7 @@ interface KeyedRow {
|
||||
}
|
||||
|
||||
/** Every import source key a document holds: its own, then its aliases. */
|
||||
function importKeysOf(row: KeyedRow): ImportSourceKey[] {
|
||||
export function importKeysOf(row: KeyedRow): ImportSourceKey[] {
|
||||
return [
|
||||
...(row.sourceSystem && row.sourceId
|
||||
? [
|
||||
|
||||
@@ -463,6 +463,8 @@ function makePrisma() {
|
||||
// Empty for the same reason as the sections above.
|
||||
documentConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
extractedFact: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
documentImportKey: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
inboundDocument: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
// The ECG strips. Empty for the same reason as the sections above:
|
||||
// an account whose watch has never taken one is the ordinary case the
|
||||
// builder must still answer for.
|
||||
|
||||
@@ -322,6 +322,7 @@ function sourceClient() {
|
||||
// Empty for the same reason as the sections above.
|
||||
documentConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
extractedFact: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
documentImportKey: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
ecgRecording: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
symptomDefinition: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
lifeEvent: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
|
||||
@@ -159,6 +159,7 @@ export const BACKED_UP_MODELS = [
|
||||
"InboundDocument",
|
||||
"DocumentConditionLink",
|
||||
"ExtractedFact",
|
||||
"DocumentImportKey", // Deleted imports' keys: `document-filing-backup.ts`.
|
||||
|
||||
// ── Visits ────────────────────────────────────────────────────────────────
|
||||
"Practitioner",
|
||||
@@ -502,6 +503,7 @@ export const TWO_ENDED_MODELS = [
|
||||
// nulled together with its type when it resolves to nothing.
|
||||
"DocumentConditionLink",
|
||||
"ExtractedFact",
|
||||
"DocumentImportKey",
|
||||
// What the account earned. The register called the bests "recomputable in
|
||||
// principle", which is true and does not help: nothing recomputes them, and
|
||||
// a recomputation over a restored history would not find the same rows:
|
||||
@@ -768,12 +770,10 @@ export const NOT_IN_BACKUP_MODELS: Readonly<Record<string, string>> = {
|
||||
"That this host's one-time fold repair has recomputed the account's hourly and daily means, which lets the compaction-tombstone purge delete the tombstones the repair reads. It describes this database, not the file: a restore of a file exported before the repair ran writes back the means and tombstones of that moment, so the restore removes the row and the repair runs again before anything is purged.",
|
||||
BackupPassAttempt:
|
||||
"When this host's backup passes last started and finished this account, kept so a pass the process died under is taken last next time and named on the backups page. It describes this deployment's own runs, so restoring it elsewhere would report an interrupted run that host never had, or hide one it did.",
|
||||
DocumentImportKey:
|
||||
"The source keys of imported documents the person deleted and the purge then removed, kept so a re-run of the importer does not store them again. The backup carries only live documents, so the tombstones that hold the same memory before the purge do not travel either; carrying the ledger alone would restore half of one decision. After a restore, a deliberate re-import can bring such a document back, and the person deletes it again.",
|
||||
DocumentAiRun:
|
||||
"A ticket for one background read (a document read with AI, a lab scan), deleted an hour after it finishes. The document and the committed rows travel; the ticket does not, and a restored run would point at a worker job that does not exist on the receiving host.",
|
||||
DocumentSourceAlias:
|
||||
"Further import source keys that were answered with an already stored document because the bytes matched. The next import run writes the same alias again the moment it meets the same bytes, so a restore loses nothing a re-run does not rebuild; and for a deleted document it restores no less than the tombstones and ledger beside it do, which do not travel either.",
|
||||
"Further import source keys that were answered with an already stored document because the bytes matched. The next import run writes the same alias again the moment it meets the same bytes, so a restore loses nothing a re-run does not rebuild. A deleted document's aliases travel as import-ledger entries (`DocumentImportKey`), which is what they mean once the document is gone.",
|
||||
ImportJob:
|
||||
"A job record pointing at an uploaded file that the backup does not carry, so restoring it would resurrect a task with nothing to work on.",
|
||||
HealthKitTypeSync:
|
||||
|
||||
@@ -167,6 +167,7 @@ const SUMMARY_COUNTS = [
|
||||
"documents",
|
||||
"documentConditionLinks",
|
||||
"extractedFacts",
|
||||
"documentImportKeys",
|
||||
"healthProfile",
|
||||
"healthProfileFactRevisions",
|
||||
"customMetrics",
|
||||
|
||||
@@ -12,6 +12,11 @@ import {
|
||||
ExtractedFactType,
|
||||
} from "@/generated/prisma/enums";
|
||||
import { base64BytesSchema, isoDateTime } from "@/lib/export/backup-schema-kit";
|
||||
import {
|
||||
DOCUMENT_SOURCE_ID_MAX,
|
||||
DOCUMENT_SOURCE_INSTANCE_MAX,
|
||||
DOCUMENT_SOURCE_SYSTEMS,
|
||||
} from "@/lib/validations/inbound-documents";
|
||||
|
||||
/** One document filed against one condition episode. */
|
||||
export const documentConditionLinkBackupSchema = z
|
||||
@@ -54,3 +59,21 @@ export const extractedFactBackupSchema = z
|
||||
updatedAt: isoDateTime,
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
/**
|
||||
* One import source key the person deleted the document of (the import
|
||||
* ledger). Closed and bounded like the upload's own fields, so a restore
|
||||
* cannot write a key the upload could never have stored.
|
||||
*/
|
||||
export const documentImportKeyBackupSchema = z
|
||||
.object({
|
||||
sourceSystem: z.enum(DOCUMENT_SOURCE_SYSTEMS),
|
||||
sourceId: z.string().min(1).max(DOCUMENT_SOURCE_ID_MAX),
|
||||
sourceInstance: z
|
||||
.string()
|
||||
.max(DOCUMENT_SOURCE_INSTANCE_MAX)
|
||||
.nullable()
|
||||
.optional(),
|
||||
createdAt: isoDateTime.optional(),
|
||||
})
|
||||
.passthrough();
|
||||
@@ -14,6 +14,12 @@
|
||||
* entries read "a restored vault is unsorted" and "re-derivable only by
|
||||
* re-running the extraction against a provider, at the operator's cost".
|
||||
*
|
||||
* A third rides beside them: the import ledger (`DocumentImportKey`), the
|
||||
* source keys of imported documents the person deleted, with the keys of the
|
||||
* documents still waiting in Recently deleted folded in (those documents do
|
||||
* not travel). Without it a restore onto a fresh instance let a token importer
|
||||
* store every deliberately deleted document again.
|
||||
*
|
||||
* ## The state on a fact is worth more than the fact
|
||||
*
|
||||
* `status`, `needsReview` and the pair `committedRecordId` /
|
||||
@@ -71,6 +77,7 @@
|
||||
import type { Prisma, PrismaClient } from "@/generated/prisma/client";
|
||||
|
||||
import { decryptFromBytes, encryptToBytes } from "@/lib/ai/coach/bytes-codec";
|
||||
import { importKeysOf, SOURCE_KEY_SELECT } from "@/lib/documents/tombstones";
|
||||
import {
|
||||
recordUnknownKeys,
|
||||
type RestoreSkipLog,
|
||||
@@ -112,14 +119,28 @@ export interface ExtractedFactBackupEntry {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One import source key the person deleted the document of. The ledger's own
|
||||
* rows (documents the purge removed) and the keys of the documents still
|
||||
* waiting in Recently deleted, which the file does not carry as documents.
|
||||
*/
|
||||
export interface DocumentImportKeyBackupEntry {
|
||||
sourceSystem: string;
|
||||
sourceId: string;
|
||||
sourceInstance: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface DocumentFilingBackupSection {
|
||||
documentConditionLinks: DocumentConditionLinkBackupEntry[];
|
||||
extractedFacts: ExtractedFactBackupEntry[];
|
||||
documentImportKeys: DocumentImportKeyBackupEntry[];
|
||||
}
|
||||
|
||||
export interface DocumentFilingBackupCounts {
|
||||
documentConditionLinks: number;
|
||||
extractedFacts: number;
|
||||
documentImportKeys: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,8 +186,72 @@ function decryptJsonSoft(bytes: Uint8Array): unknown {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What the person deleted on purpose among their imported documents, as the
|
||||
* import ledger would hold it once every deletion is purged: the ledger's own
|
||||
* rows, then the keys (own and alias) of every document still waiting in
|
||||
* Recently deleted. Those documents do not travel, as no tombstone ever has;
|
||||
* without their keys a restore onto a fresh instance would let an importer
|
||||
* store each one again. A key that appears twice is carried once.
|
||||
*/
|
||||
async function buildDeletedImportKeys(
|
||||
prisma: Pick<PrismaClient, "documentImportKey" | "inboundDocument">,
|
||||
userId: string,
|
||||
): Promise<DocumentImportKeyBackupEntry[]> {
|
||||
const [ledger, tombstones] = await Promise.all([
|
||||
prisma.documentImportKey.findMany({
|
||||
where: { userId },
|
||||
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
|
||||
select: {
|
||||
sourceSystem: true,
|
||||
sourceId: true,
|
||||
sourceInstance: true,
|
||||
createdAt: true,
|
||||
},
|
||||
}),
|
||||
prisma.inboundDocument.findMany({
|
||||
where: {
|
||||
userId,
|
||||
deletedAt: { not: null },
|
||||
OR: [
|
||||
{ sourceSystem: { not: null }, sourceId: { not: null } },
|
||||
{ sourceAliases: { some: {} } },
|
||||
],
|
||||
},
|
||||
orderBy: [{ deletedAt: "asc" }, { id: "asc" }],
|
||||
select: { ...SOURCE_KEY_SELECT, deletedAt: true },
|
||||
}),
|
||||
]);
|
||||
const out: DocumentImportKeyBackupEntry[] = [];
|
||||
const seen = new Set<string>();
|
||||
const add = (
|
||||
key: Omit<DocumentImportKeyBackupEntry, "createdAt">,
|
||||
at: Date,
|
||||
) => {
|
||||
const id = JSON.stringify([
|
||||
key.sourceSystem,
|
||||
key.sourceInstance,
|
||||
key.sourceId,
|
||||
]);
|
||||
if (seen.has(id)) return;
|
||||
seen.add(id);
|
||||
out.push({ ...key, createdAt: at.toISOString() });
|
||||
};
|
||||
for (const row of ledger) add(row, row.createdAt);
|
||||
for (const row of tombstones) {
|
||||
for (const key of importKeysOf(row)) add(key, row.deletedAt!);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function buildDocumentFilingBackupSection(
|
||||
prisma: Pick<PrismaClient, "documentConditionLink" | "extractedFact">,
|
||||
prisma: Pick<
|
||||
PrismaClient,
|
||||
| "documentConditionLink"
|
||||
| "extractedFact"
|
||||
| "documentImportKey"
|
||||
| "inboundDocument"
|
||||
>,
|
||||
userId: string,
|
||||
options: DocumentFilingBackupOptions = {},
|
||||
): Promise<DocumentFilingBackupSection> {
|
||||
@@ -177,7 +262,7 @@ export async function buildDocumentFilingBackupSection(
|
||||
// skips tombstoned documents in BOTH purposes, and the episode reader skips
|
||||
// tombstoned episodes in a portable export only — so the filters here mirror
|
||||
// exactly what `records-backup.ts` decided to carry.
|
||||
const [linkRows, factRows] = await Promise.all([
|
||||
const [linkRows, factRows, documentImportKeys] = await Promise.all([
|
||||
prisma.documentConditionLink.findMany({
|
||||
where: {
|
||||
userId,
|
||||
@@ -192,6 +277,7 @@ export async function buildDocumentFilingBackupSection(
|
||||
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
|
||||
select: EXTRACTED_FACT_BACKUP_SELECT,
|
||||
}),
|
||||
buildDeletedImportKeys(prisma, userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -223,6 +309,7 @@ export async function buildDocumentFilingBackupSection(
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
})),
|
||||
documentImportKeys,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -233,6 +320,7 @@ export function countDocumentFilingBackupSection(
|
||||
return {
|
||||
documentConditionLinks: section.documentConditionLinks.length,
|
||||
extractedFacts: section.extractedFacts.length,
|
||||
documentImportKeys: section.documentImportKeys.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -240,6 +328,7 @@ export function countDocumentFilingBackupSection(
|
||||
export interface DocumentFilingRestoreCleared {
|
||||
documentConditionLinks: number;
|
||||
extractedFacts: number;
|
||||
documentImportKeys: number;
|
||||
}
|
||||
|
||||
type OptionalNullable<T> = { [K in keyof T]?: T[K] | undefined };
|
||||
@@ -279,6 +368,18 @@ export type RestoredExtractedFact = Pick<
|
||||
export interface DocumentFilingRestoreInput {
|
||||
documentConditionLinks: DocumentConditionLinkBackupEntry[];
|
||||
extractedFacts: RestoredExtractedFact[];
|
||||
documentImportKeys: Array<
|
||||
Pick<DocumentImportKeyBackupEntry, "sourceSystem" | "sourceId"> & {
|
||||
sourceInstance?: string | null;
|
||||
createdAt?: string;
|
||||
}
|
||||
>;
|
||||
/** The restored documents' own keys: a live document's key is not deleted. */
|
||||
documents: Array<{
|
||||
sourceSystem?: string | null;
|
||||
sourceId?: string | null;
|
||||
sourceInstance?: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,10 +420,12 @@ export async function restoreDocumentFilingData(
|
||||
refs: DocumentFilingRestoreRefs,
|
||||
skips: RestoreSkipLog,
|
||||
): Promise<DocumentFilingRestoreCleared> {
|
||||
const [clearedLinks, clearedFacts] = await Promise.all([
|
||||
const [clearedLinks, clearedFacts, clearedKeys] = await Promise.all([
|
||||
tx.documentConditionLink.deleteMany({ where: { userId: ownerId } }),
|
||||
tx.extractedFact.deleteMany({ where: { userId: ownerId } }),
|
||||
tx.documentImportKey.deleteMany({ where: { userId: ownerId } }),
|
||||
]);
|
||||
await restoreDeletedImportKeys(tx, ownerId, payload);
|
||||
|
||||
const droppedFilings: string[] = [];
|
||||
const writableLinks = payload.documentConditionLinks.filter((link) => {
|
||||
@@ -416,9 +519,46 @@ export async function restoreDocumentFilingData(
|
||||
return {
|
||||
documentConditionLinks: clearedLinks.count,
|
||||
extractedFacts: clearedFacts.count,
|
||||
documentImportKeys: clearedKeys.count,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The import ledger back, as the file carries it, minus any key a restored
|
||||
* live document holds: the ledger says "the person deleted this", and a
|
||||
* document in the vault under that key says otherwise. A file written before
|
||||
* the ledger travelled carries none, and the restore then remembers no
|
||||
* deletion, as before.
|
||||
*/
|
||||
async function restoreDeletedImportKeys(
|
||||
tx: Prisma.TransactionClient,
|
||||
ownerId: string,
|
||||
payload: DocumentFilingRestoreInput,
|
||||
): Promise<void> {
|
||||
const keyOf = (k: {
|
||||
sourceSystem?: string | null;
|
||||
sourceId?: string | null;
|
||||
sourceInstance?: string | null;
|
||||
}) => JSON.stringify([k.sourceSystem, k.sourceInstance ?? null, k.sourceId]);
|
||||
const live = new Set(
|
||||
payload.documents
|
||||
.filter((d) => d.sourceSystem && d.sourceId)
|
||||
.map((d) => keyOf(d)),
|
||||
);
|
||||
const keys = payload.documentImportKeys.filter((k) => !live.has(keyOf(k)));
|
||||
if (keys.length === 0) return;
|
||||
await tx.documentImportKey.createMany({
|
||||
data: keys.map((k) => ({
|
||||
userId: ownerId,
|
||||
sourceSystem: k.sourceSystem,
|
||||
sourceId: k.sourceId,
|
||||
sourceInstance: k.sourceInstance ?? null,
|
||||
...(k.createdAt ? { createdAt: new Date(k.createdAt) } : {}),
|
||||
})),
|
||||
skipDuplicates: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A fact column's stored bytes, whichever end of the contract the file came
|
||||
* from.
|
||||
|
||||
@@ -72,6 +72,7 @@ export const RESTORE_CLEARED_KEYS = [
|
||||
"documents",
|
||||
"documentConditionLinks",
|
||||
"extractedFacts",
|
||||
"documentImportKeys",
|
||||
"healthProfile",
|
||||
"healthProfileFactRevisions",
|
||||
"customMetrics",
|
||||
|
||||
@@ -59,6 +59,8 @@ export interface BackupSummary {
|
||||
documentConditionLinks: number;
|
||||
/** Facts staged out of a document, with their review decision. */
|
||||
extractedFacts: number;
|
||||
/** Import source keys of documents the person deleted. */
|
||||
documentImportKeys: number;
|
||||
/** 1 when the account's durable self-context rides the file, 0 otherwise. */
|
||||
healthProfile: number;
|
||||
/** Effective-dated structured health-profile revisions. */
|
||||
@@ -159,6 +161,7 @@ export function summarizeBackup(payload: BackupPayload): BackupSummary {
|
||||
// did for visits.
|
||||
documentConditionLinks: payload.documentConditionLinks.length,
|
||||
extractedFacts: payload.extractedFacts.length,
|
||||
documentImportKeys: payload.documentImportKeys.length,
|
||||
healthProfile: payload.healthProfile ? 1 : 0,
|
||||
healthProfileFactRevisions: payload.healthProfileFacts.length,
|
||||
customMetrics: payload.customMetrics.length,
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
import {
|
||||
documentConditionLinkBackupSchema,
|
||||
extractedFactBackupSchema,
|
||||
documentImportKeyBackupSchema,
|
||||
} from "@/lib/export/document-filing-backup-schema";
|
||||
import { documentBackupSchema } from "@/lib/export/documents-backup-schema";
|
||||
import { ecgRecordingBackupSchema } from "@/lib/export/ecg-backup-schema";
|
||||
@@ -182,6 +183,9 @@ export const backupPayloadSchema = z
|
||||
.array(documentConditionLinkBackupSchema)
|
||||
.default([]),
|
||||
extractedFacts: z.array(extractedFactBackupSchema).default([]),
|
||||
// The source keys of imported documents the person deleted. Defaulted:
|
||||
// a file written before the import ledger travelled carries no key.
|
||||
documentImportKeys: z.array(documentImportKeyBackupSchema).default([]),
|
||||
nutrientDays: z.array(nutrientDaySchema).default([]),
|
||||
// Durable self-context and user-defined series. Defaulted so files written
|
||||
// before either rode the wire still parse; an account with neither writes
|
||||
|
||||
@@ -539,7 +539,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:21>",
|
||||
@@ -554,7 +554,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": 2,
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:22>",
|
||||
@@ -569,14 +569,14 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"efficacyTargets": [
|
||||
{
|
||||
"id": "<id:23>",
|
||||
"createdAt": "2026-05-06T06:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T06:26:00.000Z",
|
||||
"createdAt": "2026-05-06T08:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:06:00.000Z",
|
||||
"measurementType": "WEIGHT",
|
||||
"biomarkerName": "Ferritin",
|
||||
"primary": false
|
||||
@@ -972,7 +972,7 @@
|
||||
"name": "Ferr.",
|
||||
"source": "IMPORT_CHOICE",
|
||||
"id": "<id:40>",
|
||||
"createdAt": "2026-05-06T11:26:00.000Z"
|
||||
"createdAt": "2026-05-06T13:06:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -985,7 +985,7 @@
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"illnessEpisodes": [
|
||||
@@ -1194,8 +1194,8 @@
|
||||
"sourceSystem": "PAPERLESS",
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"createdAt": "2026-05-04T01:24:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:25:00.000Z",
|
||||
"createdAt": "2026-05-04T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:26:00.000Z",
|
||||
"contentEncrypted": "<sealed:v1:bytes:cm91bmQtdHJpcCBkb2N1bWVudCBieXRlcw==>",
|
||||
"contentSha256": "0a738470f0945033965858e9895ee721ce981a25f013514d587d0a7a1946be7a",
|
||||
"contentCodec": "binary2",
|
||||
@@ -1206,7 +1206,8 @@
|
||||
"summaryState": "PENDING",
|
||||
"lastIndexAttemptAt": "2026-06-30T12:34:56.789Z",
|
||||
"lastIndexOutcome": "rt-InboundDocument-lastIndexOutcome",
|
||||
"aiReadDeferred": true
|
||||
"aiReadDeferred": true,
|
||||
"extractionIncomplete": true
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
@@ -1456,8 +1457,8 @@
|
||||
],
|
||||
"onboardingRecord": {
|
||||
"id": "<id:65>",
|
||||
"createdAt": "2026-05-06T09:47:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:48:00.000Z",
|
||||
"createdAt": "2026-05-06T11:27:00.000Z",
|
||||
"updatedAt": "2026-05-06T11:28:00.000Z",
|
||||
"needs": {
|
||||
"recordTarget": "me",
|
||||
"areas": [
|
||||
@@ -1990,6 +1991,14 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentImportKeys": [
|
||||
{
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"personalRecords": [
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -2001,7 +2010,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -2013,7 +2022,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WEIGHT",
|
||||
@@ -2025,19 +2034,19 @@
|
||||
"sourceMeasurementId": "<id:4>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"userAchievements": [
|
||||
{
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
},
|
||||
{
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"environmentContexts": [
|
||||
@@ -2083,8 +2092,8 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
@@ -2128,8 +2137,8 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"environmentTravelLocations": [
|
||||
@@ -2140,8 +2149,8 @@
|
||||
"lon": null,
|
||||
"label": null,
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"dawarichDays": [
|
||||
@@ -2156,8 +2165,8 @@
|
||||
"countryCodeEncrypted": "<sealed:v1:country:ES>",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"ecgRecordings": [
|
||||
@@ -2174,8 +2183,8 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:5>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"symptomDefinitions": [
|
||||
@@ -2271,35 +2280,35 @@
|
||||
{
|
||||
"labResultId": "<id:37>",
|
||||
"documentId": "<id:53>",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterMedicationLinks": [
|
||||
{
|
||||
"encounterId": "<id:68>",
|
||||
"medicationId": "<id:6>",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterVaccinationLinks": [
|
||||
{
|
||||
"encounterId": "<id:68>",
|
||||
"vaccinationId": "<id:73>",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationDocumentLinks": [
|
||||
{
|
||||
"medicationId": "<id:6>",
|
||||
"documentId": "<id:53>",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationConditionLinks": [
|
||||
{
|
||||
"medicationId": "<id:6>",
|
||||
"episodeId": "<id:44>",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"nutrientDays": [
|
||||
@@ -2367,6 +2376,7 @@
|
||||
"consentReceipts": 2,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"personalRecords": 3,
|
||||
"userAchievements": 2,
|
||||
"environmentContexts": 2,
|
||||
@@ -2430,6 +2440,7 @@
|
||||
"consentReceipts": 2,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"personalRecords": 3,
|
||||
"userAchievements": 2,
|
||||
"environmentContexts": 2,
|
||||
@@ -2942,8 +2953,8 @@
|
||||
"measurementType": "WEIGHT",
|
||||
"biomarkerName": "Ferritin",
|
||||
"primary": false,
|
||||
"createdAt": "2026-05-06T06:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T06:26:00.000Z"
|
||||
"createdAt": "2026-05-06T08:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"scheduleRevisions": [
|
||||
@@ -2960,7 +2971,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:21>",
|
||||
@@ -2975,7 +2986,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": 2,
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:22>",
|
||||
@@ -2990,7 +3001,7 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"courses": [
|
||||
@@ -3412,7 +3423,7 @@
|
||||
"id": "<id:40>",
|
||||
"name": "Ferr.",
|
||||
"source": "IMPORT_CHOICE",
|
||||
"createdAt": "2026-05-06T11:26:00.000Z"
|
||||
"createdAt": "2026-05-06T13:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"createdAt": "2026-05-02T12:30:00.000Z",
|
||||
@@ -3427,7 +3438,7 @@
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"illnessEpisodes": [
|
||||
@@ -3736,8 +3747,9 @@
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"aiReadDeferred": true,
|
||||
"createdAt": "2026-05-04T01:24:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:25:00.000Z"
|
||||
"extractionIncomplete": true,
|
||||
"createdAt": "2026-05-04T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:26:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentConditionLinks": [
|
||||
@@ -3763,6 +3775,14 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentImportKeys": [
|
||||
{
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"nutrientDays": [
|
||||
{
|
||||
"day": "2026-07-01",
|
||||
@@ -4064,8 +4084,8 @@
|
||||
},
|
||||
"modulesDerivedAt": "2026-07-01T08:00:00.000Z",
|
||||
"completedAt": "2026-07-01T08:00:00.000Z",
|
||||
"createdAt": "2026-05-06T09:47:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:48:00.000Z"
|
||||
"createdAt": "2026-05-06T11:27:00.000Z",
|
||||
"updatedAt": "2026-05-06T11:28:00.000Z"
|
||||
},
|
||||
"practitioners": [
|
||||
{
|
||||
@@ -4226,35 +4246,35 @@
|
||||
{
|
||||
"labResultId": "<id:37>",
|
||||
"documentId": "<id:53>",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterMedicationLinks": [
|
||||
{
|
||||
"encounterId": "<id:68>",
|
||||
"medicationId": "<id:6>",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterVaccinationLinks": [
|
||||
{
|
||||
"encounterId": "<id:68>",
|
||||
"vaccinationId": "<id:73>",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationDocumentLinks": [
|
||||
{
|
||||
"medicationId": "<id:6>",
|
||||
"documentId": "<id:53>",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationConditionLinks": [
|
||||
{
|
||||
"medicationId": "<id:6>",
|
||||
"episodeId": "<id:44>",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"coachConversations": [
|
||||
@@ -4560,7 +4580,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -4572,7 +4592,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WEIGHT",
|
||||
@@ -4584,19 +4604,19 @@
|
||||
"sourceMeasurementId": "<id:4>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"userAchievements": [
|
||||
{
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
},
|
||||
{
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"environmentContexts": [
|
||||
@@ -4642,8 +4662,8 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
@@ -4687,8 +4707,8 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"environmentTravelLocations": [
|
||||
@@ -4699,8 +4719,8 @@
|
||||
"lon": null,
|
||||
"label": null,
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"dawarichDays": [
|
||||
@@ -4715,8 +4735,8 @@
|
||||
"countryCodeEncrypted": "<sealed:v1:country:ES>",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"ecgRecordings": [
|
||||
@@ -4733,8 +4753,8 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:5>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
@@ -4781,6 +4801,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 2,
|
||||
"customMetrics": 2,
|
||||
@@ -4837,6 +4858,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 2,
|
||||
"customMetrics": 2,
|
||||
|
||||
@@ -345,7 +345,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"validFrom": "2026-02-01T00:00:00.000Z",
|
||||
@@ -359,7 +359,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": 2,
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"validFrom": "2026-03-01T00:00:00.000Z",
|
||||
@@ -373,7 +373,7 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"efficacyTargets": [
|
||||
@@ -576,7 +576,7 @@
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"illnessEpisodes": [
|
||||
@@ -700,7 +700,7 @@
|
||||
"sourceSystem": "PAPERLESS",
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"createdAt": "2026-05-04T01:24:00.000Z",
|
||||
"createdAt": "2026-05-04T01:25:00.000Z",
|
||||
"summary": "rt-InboundDocument-summaryEncrypted"
|
||||
}
|
||||
],
|
||||
@@ -1228,6 +1228,14 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentImportKeys": [
|
||||
{
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"personalRecords": [
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -1239,7 +1247,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -1251,7 +1259,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WEIGHT",
|
||||
@@ -1263,19 +1271,19 @@
|
||||
"sourceMeasurementId": "<id:3>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"userAchievements": [
|
||||
{
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
},
|
||||
{
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"environmentContexts": [
|
||||
@@ -1320,8 +1328,8 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
@@ -1364,8 +1372,8 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"environmentTravelLocations": [
|
||||
@@ -1375,8 +1383,8 @@
|
||||
"lat": 41.4,
|
||||
"lon": 2.2,
|
||||
"label": "Barcelona",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"dawarichDays": [
|
||||
@@ -1390,8 +1398,8 @@
|
||||
"countryCode": "ES",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"ecgRecordings": [
|
||||
@@ -1416,8 +1424,8 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:4>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"symptomDefinitions": [
|
||||
@@ -1492,35 +1500,35 @@
|
||||
{
|
||||
"labResultId": "<id:8>",
|
||||
"documentId": "<id:15>",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterMedicationLinks": [
|
||||
{
|
||||
"encounterId": "<id:17>",
|
||||
"medicationId": "<id:5>",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterVaccinationLinks": [
|
||||
{
|
||||
"encounterId": "<id:17>",
|
||||
"vaccinationId": "<id:20>",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationDocumentLinks": [
|
||||
{
|
||||
"medicationId": "<id:5>",
|
||||
"documentId": "<id:15>",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationConditionLinks": [
|
||||
{
|
||||
"medicationId": "<id:5>",
|
||||
"episodeId": "<id:11>",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"nutrientDays": [
|
||||
@@ -1587,6 +1595,7 @@
|
||||
"consentReceipts": 0,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"personalRecords": 3,
|
||||
"userAchievements": 2,
|
||||
"environmentContexts": 2,
|
||||
@@ -1650,6 +1659,7 @@
|
||||
"consentReceipts": 0,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"personalRecords": 3,
|
||||
"userAchievements": 2,
|
||||
"environmentContexts": 2,
|
||||
@@ -1989,7 +1999,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"validFrom": "2026-02-01T00:00:00.000Z",
|
||||
@@ -2003,7 +2013,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": 2,
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"validFrom": "2026-03-01T00:00:00.000Z",
|
||||
@@ -2017,7 +2027,7 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"courses": [
|
||||
@@ -2238,7 +2248,7 @@
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"illnessEpisodes": [
|
||||
@@ -2431,7 +2441,7 @@
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"summary": "rt-InboundDocument-summaryEncrypted",
|
||||
"createdAt": "2026-05-04T01:24:00.000Z"
|
||||
"createdAt": "2026-05-04T01:25:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentConditionLinks": [
|
||||
@@ -2473,6 +2483,14 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentImportKeys": [
|
||||
{
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"nutrientDays": [
|
||||
{
|
||||
"day": "2026-07-01",
|
||||
@@ -2790,35 +2808,35 @@
|
||||
{
|
||||
"labResultId": "<id:8>",
|
||||
"documentId": "<id:15>",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterMedicationLinks": [
|
||||
{
|
||||
"encounterId": "<id:17>",
|
||||
"medicationId": "<id:5>",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterVaccinationLinks": [
|
||||
{
|
||||
"encounterId": "<id:17>",
|
||||
"vaccinationId": "<id:20>",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationDocumentLinks": [
|
||||
{
|
||||
"medicationId": "<id:5>",
|
||||
"documentId": "<id:15>",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationConditionLinks": [
|
||||
{
|
||||
"medicationId": "<id:5>",
|
||||
"episodeId": "<id:11>",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"coachConversations": [
|
||||
@@ -2996,7 +3014,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -3008,7 +3026,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WEIGHT",
|
||||
@@ -3020,19 +3038,19 @@
|
||||
"sourceMeasurementId": "<id:3>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"userAchievements": [
|
||||
{
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
},
|
||||
{
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"environmentContexts": [
|
||||
@@ -3077,8 +3095,8 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
@@ -3121,8 +3139,8 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"environmentTravelLocations": [
|
||||
@@ -3132,8 +3150,8 @@
|
||||
"lat": 41.4,
|
||||
"lon": 2.2,
|
||||
"label": "Barcelona",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"dawarichDays": [
|
||||
@@ -3147,8 +3165,8 @@
|
||||
"countryCode": "ES",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"ecgRecordings": [
|
||||
@@ -3173,8 +3191,8 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:4>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
@@ -3221,6 +3239,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 1,
|
||||
@@ -3277,6 +3296,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 1,
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 2,
|
||||
"customMetrics": 2,
|
||||
@@ -83,6 +84,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 2,
|
||||
"customMetrics": 2,
|
||||
@@ -299,6 +301,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 2,
|
||||
"customMetrics": 2,
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"documents": 1,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 2,
|
||||
"customMetrics": 2,
|
||||
@@ -83,6 +84,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -299,6 +301,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -1641,8 +1644,9 @@
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"aiReadDeferred": true,
|
||||
"createdAt": "2026-05-04T01:24:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:25:00.000Z",
|
||||
"extractionIncomplete": true,
|
||||
"createdAt": "2026-05-04T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:26:00.000Z",
|
||||
"deletedAt": null
|
||||
}
|
||||
],
|
||||
@@ -2245,10 +2249,20 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"PersonalRecord": [
|
||||
"DocumentImportKey": [
|
||||
{
|
||||
"id": "<id:104>",
|
||||
"userId": "round-trip-owner",
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"PersonalRecord": [
|
||||
{
|
||||
"id": "<id:105>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
"metricSlot": "running_10km_time",
|
||||
"direction": "MIN",
|
||||
@@ -2258,10 +2272,10 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:105>",
|
||||
"id": "<id:106>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
"metricSlot": "running_5km_time",
|
||||
@@ -2272,10 +2286,10 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:106>",
|
||||
"id": "<id:107>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WEIGHT",
|
||||
"metricSlot": null,
|
||||
@@ -2286,28 +2300,28 @@
|
||||
"sourceMeasurementId": "<id:5>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"UserAchievement": [
|
||||
{
|
||||
"id": "<id:107>",
|
||||
"id": "<id:108>",
|
||||
"userId": "round-trip-owner",
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:108>",
|
||||
"id": "<id:109>",
|
||||
"userId": "round-trip-owner",
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
}
|
||||
],
|
||||
"EnvironmentContext": [
|
||||
{
|
||||
"id": "<id:109>",
|
||||
"id": "<id:110>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-06-15",
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
@@ -2347,11 +2361,11 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:110>",
|
||||
"id": "<id:111>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-07-01",
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":52.5,\"lon\":13.4,\"label\":\"Berlin\"}>",
|
||||
@@ -2391,24 +2405,24 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"EnvironmentTravelLocation": [
|
||||
{
|
||||
"id": "<id:111>",
|
||||
"id": "<id:112>",
|
||||
"userId": "round-trip-owner",
|
||||
"startDate": "2026-06-10",
|
||||
"endDate": "2026-06-20",
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"DawarichDay": [
|
||||
{
|
||||
"id": "<id:112>",
|
||||
"id": "<id:113>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-06-15",
|
||||
"totalDistanceM": 48300,
|
||||
@@ -2419,13 +2433,13 @@
|
||||
"countryCodeEncrypted": "<sealed:v1:country:ES>",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationScheduleRevision": [
|
||||
{
|
||||
"id": "<id:113>",
|
||||
"id": "<id:114>",
|
||||
"medicationId": "<id:7>",
|
||||
"validFrom": "2026-01-01T00:00:00.000Z",
|
||||
"validUntil": "2026-03-01T00:00:00.000Z",
|
||||
@@ -2438,10 +2452,10 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByRevisionId": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:114>",
|
||||
"id": "<id:115>",
|
||||
"medicationId": "<id:7>",
|
||||
"validFrom": "2026-02-01T00:00:00.000Z",
|
||||
"validUntil": "2026-03-01T00:00:00.000Z",
|
||||
@@ -2453,11 +2467,11 @@
|
||||
}
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByRevisionId": "<id:115>",
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"supersededByRevisionId": "<id:116>",
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:115>",
|
||||
"id": "<id:116>",
|
||||
"medicationId": "<id:7>",
|
||||
"validFrom": "2026-03-01T00:00:00.000Z",
|
||||
"validUntil": "2026-05-01T00:00:00.000Z",
|
||||
@@ -2470,23 +2484,23 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByRevisionId": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationEfficacyTarget": [
|
||||
{
|
||||
"id": "<id:116>",
|
||||
"id": "<id:117>",
|
||||
"medicationId": "<id:7>",
|
||||
"measurementType": "WEIGHT",
|
||||
"biomarkerId": "<id:26>",
|
||||
"primary": false,
|
||||
"createdAt": "2026-05-06T06:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T06:26:00.000Z"
|
||||
"createdAt": "2026-05-06T08:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"EcgRecording": [
|
||||
{
|
||||
"id": "<id:117>",
|
||||
"id": "<id:118>",
|
||||
"userId": "round-trip-owner",
|
||||
"source": "MANUAL",
|
||||
"externalRecordingId": "round-trip-signal-1",
|
||||
@@ -2499,13 +2513,13 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:2>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"OnboardingRecord": [
|
||||
{
|
||||
"id": "<id:118>",
|
||||
"id": "<id:119>",
|
||||
"userId": "round-trip-owner",
|
||||
"needsJson": {
|
||||
"areas": [
|
||||
@@ -2568,80 +2582,80 @@
|
||||
},
|
||||
"modulesDerivedAt": "2026-07-01T08:00:00.000Z",
|
||||
"completedAt": "2026-07-01T08:00:00.000Z",
|
||||
"createdAt": "2026-05-06T09:47:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:48:00.000Z"
|
||||
"createdAt": "2026-05-06T11:27:00.000Z",
|
||||
"updatedAt": "2026-05-06T11:28:00.000Z"
|
||||
}
|
||||
],
|
||||
"BiomarkerAlias": [
|
||||
{
|
||||
"id": "<id:119>",
|
||||
"id": "<id:120>",
|
||||
"userId": "round-trip-owner",
|
||||
"biomarkerId": "<id:26>",
|
||||
"name": "Ferr.",
|
||||
"nameFolded": "ferr.",
|
||||
"source": "IMPORT_CHOICE",
|
||||
"createdAt": "2026-05-06T11:26:00.000Z"
|
||||
"createdAt": "2026-05-06T13:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"BiomarkerMerge": [
|
||||
{
|
||||
"id": "<id:120>",
|
||||
"id": "<id:121>",
|
||||
"userId": "round-trip-owner",
|
||||
"targetBiomarkerId": "<id:26>",
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"LabResultDocumentLink": [
|
||||
{
|
||||
"id": "<id:121>",
|
||||
"id": "<id:122>",
|
||||
"labResultId": "<id:25>",
|
||||
"documentId": "<id:65>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"EncounterMedicationLink": [
|
||||
{
|
||||
"id": "<id:122>",
|
||||
"encounterId": "<id:69>",
|
||||
"medicationId": "<id:7>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"EncounterVaccinationLink": [
|
||||
"EncounterMedicationLink": [
|
||||
{
|
||||
"id": "<id:123>",
|
||||
"encounterId": "<id:69>",
|
||||
"vaccinationId": "<id:75>",
|
||||
"medicationId": "<id:7>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationDocumentLink": [
|
||||
"EncounterVaccinationLink": [
|
||||
{
|
||||
"id": "<id:124>",
|
||||
"medicationId": "<id:7>",
|
||||
"documentId": "<id:65>",
|
||||
"encounterId": "<id:69>",
|
||||
"vaccinationId": "<id:75>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationConditionLink": [
|
||||
"MedicationDocumentLink": [
|
||||
{
|
||||
"id": "<id:125>",
|
||||
"medicationId": "<id:7>",
|
||||
"episodeId": "<id:32>",
|
||||
"documentId": "<id:65>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"AccountGrant": [
|
||||
"MedicationConditionLink": [
|
||||
{
|
||||
"id": "<id:126>",
|
||||
"medicationId": "<id:7>",
|
||||
"episodeId": "<id:32>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"AccountGrant": [
|
||||
{
|
||||
"id": "<id:127>",
|
||||
"grantorId": "round-trip-owner",
|
||||
"granteeId": "parity-delegate",
|
||||
"access": "READ",
|
||||
@@ -3444,7 +3458,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:21>",
|
||||
@@ -3459,7 +3473,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": 2,
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:22>",
|
||||
@@ -3474,14 +3488,14 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"efficacyTargets": [
|
||||
{
|
||||
"id": "<id:23>",
|
||||
"createdAt": "2026-05-06T06:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T06:26:00.000Z",
|
||||
"createdAt": "2026-05-06T08:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:06:00.000Z",
|
||||
"measurementType": "WEIGHT",
|
||||
"biomarkerName": "Ferritin",
|
||||
"primary": false
|
||||
@@ -3877,7 +3891,7 @@
|
||||
"name": "Ferr.",
|
||||
"source": "IMPORT_CHOICE",
|
||||
"id": "<id:40>",
|
||||
"createdAt": "2026-05-06T11:26:00.000Z"
|
||||
"createdAt": "2026-05-06T13:06:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3890,7 +3904,7 @@
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"illnessEpisodes": [
|
||||
@@ -4099,8 +4113,8 @@
|
||||
"sourceSystem": "PAPERLESS",
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"createdAt": "2026-05-04T01:24:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:25:00.000Z",
|
||||
"createdAt": "2026-05-04T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:26:00.000Z",
|
||||
"contentEncrypted": "<sealed:v1:bytes:cm91bmQtdHJpcCBkb2N1bWVudCBieXRlcw==>",
|
||||
"contentSha256": "0a738470f0945033965858e9895ee721ce981a25f013514d587d0a7a1946be7a",
|
||||
"contentCodec": "binary2",
|
||||
@@ -4111,7 +4125,8 @@
|
||||
"summaryState": "PENDING",
|
||||
"lastIndexAttemptAt": "2026-06-30T12:34:56.789Z",
|
||||
"lastIndexOutcome": "rt-InboundDocument-lastIndexOutcome",
|
||||
"aiReadDeferred": true
|
||||
"aiReadDeferred": true,
|
||||
"extractionIncomplete": true
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
@@ -4361,8 +4376,8 @@
|
||||
],
|
||||
"onboardingRecord": {
|
||||
"id": "<id:65>",
|
||||
"createdAt": "2026-05-06T09:47:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:48:00.000Z",
|
||||
"createdAt": "2026-05-06T11:27:00.000Z",
|
||||
"updatedAt": "2026-05-06T11:28:00.000Z",
|
||||
"needs": {
|
||||
"recordTarget": "me",
|
||||
"areas": [
|
||||
@@ -4895,6 +4910,14 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"documentImportKeys": [
|
||||
{
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"personalRecords": [
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -4906,7 +4929,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -4918,7 +4941,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WEIGHT",
|
||||
@@ -4930,19 +4953,19 @@
|
||||
"sourceMeasurementId": "<id:4>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"userAchievements": [
|
||||
{
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
},
|
||||
{
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"environmentContexts": [
|
||||
@@ -4988,8 +5011,8 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
@@ -5033,8 +5056,8 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"environmentTravelLocations": [
|
||||
@@ -5045,8 +5068,8 @@
|
||||
"lon": null,
|
||||
"label": null,
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"dawarichDays": [
|
||||
@@ -5061,8 +5084,8 @@
|
||||
"countryCodeEncrypted": "<sealed:v1:country:ES>",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"ecgRecordings": [
|
||||
@@ -5079,8 +5102,8 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:5>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"symptomDefinitions": [
|
||||
@@ -5176,35 +5199,35 @@
|
||||
{
|
||||
"labResultId": "<id:37>",
|
||||
"documentId": "<id:53>",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterMedicationLinks": [
|
||||
{
|
||||
"encounterId": "<id:68>",
|
||||
"medicationId": "<id:6>",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterVaccinationLinks": [
|
||||
{
|
||||
"encounterId": "<id:68>",
|
||||
"vaccinationId": "<id:73>",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationDocumentLinks": [
|
||||
{
|
||||
"medicationId": "<id:6>",
|
||||
"documentId": "<id:53>",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationConditionLinks": [
|
||||
{
|
||||
"medicationId": "<id:6>",
|
||||
"episodeId": "<id:44>",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"nutrientDays": [
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 1,
|
||||
"extractedFacts": 1,
|
||||
"documentImportKeys": 1,
|
||||
"healthProfile": 1,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 1,
|
||||
@@ -129,6 +130,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -345,6 +347,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -1756,10 +1759,20 @@
|
||||
"ConsentReceipt": [],
|
||||
"DocumentConditionLink": [],
|
||||
"ExtractedFact": [],
|
||||
"PersonalRecord": [
|
||||
"DocumentImportKey": [
|
||||
{
|
||||
"id": "<id:69>",
|
||||
"userId": "round-trip-owner",
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"PersonalRecord": [
|
||||
{
|
||||
"id": "<id:70>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
"metricSlot": "running_10km_time",
|
||||
"direction": "MIN",
|
||||
@@ -1769,10 +1782,10 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:70>",
|
||||
"id": "<id:71>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
"metricSlot": "running_5km_time",
|
||||
@@ -1783,10 +1796,10 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:71>",
|
||||
"id": "<id:72>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WEIGHT",
|
||||
"metricSlot": null,
|
||||
@@ -1797,28 +1810,28 @@
|
||||
"sourceMeasurementId": "<id:4>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"UserAchievement": [
|
||||
{
|
||||
"id": "<id:72>",
|
||||
"id": "<id:73>",
|
||||
"userId": "round-trip-owner",
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:73>",
|
||||
"id": "<id:74>",
|
||||
"userId": "round-trip-owner",
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
}
|
||||
],
|
||||
"EnvironmentContext": [
|
||||
{
|
||||
"id": "<id:74>",
|
||||
"id": "<id:75>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-06-15",
|
||||
"locationEncrypted": "<sealed:v2:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
@@ -1858,11 +1871,11 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:75>",
|
||||
"id": "<id:76>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-07-01",
|
||||
"locationEncrypted": "<sealed:v2:{\"lat\":52.5,\"lon\":13.4,\"label\":\"Berlin\"}>",
|
||||
@@ -1902,24 +1915,24 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"EnvironmentTravelLocation": [
|
||||
{
|
||||
"id": "<id:76>",
|
||||
"id": "<id:77>",
|
||||
"userId": "round-trip-owner",
|
||||
"startDate": "2026-06-10",
|
||||
"endDate": "2026-06-20",
|
||||
"locationEncrypted": "<sealed:v2:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"DawarichDay": [
|
||||
{
|
||||
"id": "<id:77>",
|
||||
"id": "<id:78>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-06-15",
|
||||
"totalDistanceM": 48300,
|
||||
@@ -1930,13 +1943,13 @@
|
||||
"countryCodeEncrypted": "<sealed:v2:country:ES>",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationScheduleRevision": [
|
||||
{
|
||||
"id": "<id:78>",
|
||||
"id": "<id:79>",
|
||||
"medicationId": "<id:6>",
|
||||
"validFrom": "2026-01-01T00:00:00.000Z",
|
||||
"validUntil": "2026-03-01T00:00:00.000Z",
|
||||
@@ -1949,10 +1962,10 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByRevisionId": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:79>",
|
||||
"id": "<id:80>",
|
||||
"medicationId": "<id:6>",
|
||||
"validFrom": "2026-02-01T00:00:00.000Z",
|
||||
"validUntil": "2026-03-01T00:00:00.000Z",
|
||||
@@ -1964,11 +1977,11 @@
|
||||
}
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByRevisionId": "<id:80>",
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"supersededByRevisionId": "<id:81>",
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:80>",
|
||||
"id": "<id:81>",
|
||||
"medicationId": "<id:6>",
|
||||
"validFrom": "2026-03-01T00:00:00.000Z",
|
||||
"validUntil": "2026-05-01T00:00:00.000Z",
|
||||
@@ -1981,12 +1994,12 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByRevisionId": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationEfficacyTarget": [
|
||||
{
|
||||
"id": "<id:81>",
|
||||
"id": "<id:82>",
|
||||
"medicationId": "<id:6>",
|
||||
"measurementType": "WEIGHT",
|
||||
"biomarkerId": "<id:23>",
|
||||
@@ -1997,7 +2010,7 @@
|
||||
],
|
||||
"EcgRecording": [
|
||||
{
|
||||
"id": "<id:82>",
|
||||
"id": "<id:83>",
|
||||
"userId": "round-trip-owner",
|
||||
"source": "MANUAL",
|
||||
"externalRecordingId": "round-trip-signal-1",
|
||||
@@ -2010,13 +2023,13 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:2>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"OnboardingRecord": [
|
||||
{
|
||||
"id": "<id:83>",
|
||||
"id": "<id:84>",
|
||||
"userId": "round-trip-owner",
|
||||
"needsJson": {
|
||||
"areas": [
|
||||
@@ -2085,7 +2098,7 @@
|
||||
],
|
||||
"BiomarkerAlias": [
|
||||
{
|
||||
"id": "<id:84>",
|
||||
"id": "<id:85>",
|
||||
"userId": "round-trip-owner",
|
||||
"biomarkerId": "<id:23>",
|
||||
"name": "Ferr.",
|
||||
@@ -2096,47 +2109,47 @@
|
||||
],
|
||||
"BiomarkerMerge": [
|
||||
{
|
||||
"id": "<id:85>",
|
||||
"id": "<id:86>",
|
||||
"userId": "round-trip-owner",
|
||||
"targetBiomarkerId": "<id:23>",
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"LabResultDocumentLink": [],
|
||||
"EncounterMedicationLink": [
|
||||
{
|
||||
"id": "<id:86>",
|
||||
"id": "<id:87>",
|
||||
"encounterId": "<id:49>",
|
||||
"medicationId": "<id:6>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"EncounterVaccinationLink": [
|
||||
{
|
||||
"id": "<id:87>",
|
||||
"id": "<id:88>",
|
||||
"encounterId": "<id:49>",
|
||||
"vaccinationId": "<id:52>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationDocumentLink": [],
|
||||
"MedicationConditionLink": [
|
||||
{
|
||||
"id": "<id:88>",
|
||||
"id": "<id:89>",
|
||||
"medicationId": "<id:6>",
|
||||
"episodeId": "<id:26>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"AccountGrant": [
|
||||
{
|
||||
"id": "<id:89>",
|
||||
"id": "<id:90>",
|
||||
"grantorId": "round-trip-owner",
|
||||
"granteeId": "parity-delegate",
|
||||
"access": "READ",
|
||||
@@ -2745,7 +2758,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"validFrom": "2026-02-01T00:00:00.000Z",
|
||||
@@ -2759,7 +2772,7 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByIndex": 2,
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"validFrom": "2026-03-01T00:00:00.000Z",
|
||||
@@ -2773,7 +2786,7 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByIndex": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"efficacyTargets": [
|
||||
@@ -2976,7 +2989,7 @@
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"illnessEpisodes": [
|
||||
@@ -3551,6 +3564,14 @@
|
||||
"consentReceipts": [],
|
||||
"documentConditionLinks": [],
|
||||
"extractedFacts": [],
|
||||
"documentImportKeys": [
|
||||
{
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"personalRecords": [
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -3562,7 +3583,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
@@ -3574,7 +3595,7 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"metricType": "WEIGHT",
|
||||
@@ -3586,19 +3607,19 @@
|
||||
"sourceMeasurementId": "<id:3>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"userAchievements": [
|
||||
{
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
},
|
||||
{
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"environmentContexts": [
|
||||
@@ -3643,8 +3664,8 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"date": "2026-07-01",
|
||||
@@ -3687,8 +3708,8 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"environmentTravelLocations": [
|
||||
@@ -3698,8 +3719,8 @@
|
||||
"lat": 41.4,
|
||||
"lon": 2.2,
|
||||
"label": "Barcelona",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"dawarichDays": [
|
||||
@@ -3713,8 +3734,8 @@
|
||||
"countryCode": "ES",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"ecgRecordings": [
|
||||
@@ -3739,8 +3760,8 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:4>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"symptomDefinitions": [
|
||||
@@ -3816,14 +3837,14 @@
|
||||
{
|
||||
"encounterId": "<id:16>",
|
||||
"medicationId": "<id:5>",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"encounterVaccinationLinks": [
|
||||
{
|
||||
"encounterId": "<id:16>",
|
||||
"vaccinationId": "<id:19>",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"medicationDocumentLinks": [],
|
||||
@@ -3831,7 +3852,7 @@
|
||||
{
|
||||
"medicationId": "<id:5>",
|
||||
"episodeId": "<id:11>",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"nutrientDays": [
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -144,6 +145,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -360,6 +362,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -737,6 +740,7 @@
|
||||
"ConsentReceipt": [],
|
||||
"DocumentConditionLink": [],
|
||||
"ExtractedFact": [],
|
||||
"DocumentImportKey": [],
|
||||
"PersonalRecord": [],
|
||||
"UserAchievement": [],
|
||||
"EnvironmentContext": [],
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -83,6 +84,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -299,6 +301,7 @@
|
||||
"documents": 0,
|
||||
"documentConditionLinks": 0,
|
||||
"extractedFacts": 0,
|
||||
"documentImportKeys": 0,
|
||||
"healthProfile": 0,
|
||||
"healthProfileFactRevisions": 0,
|
||||
"customMetrics": 0,
|
||||
@@ -912,6 +915,7 @@
|
||||
"ConsentReceipt": [],
|
||||
"DocumentConditionLink": [],
|
||||
"ExtractedFact": [],
|
||||
"DocumentImportKey": [],
|
||||
"PersonalRecord": [],
|
||||
"UserAchievement": [],
|
||||
"EnvironmentContext": [],
|
||||
@@ -1800,6 +1804,7 @@
|
||||
"consentReceipts": [],
|
||||
"documentConditionLinks": [],
|
||||
"extractedFacts": [],
|
||||
"documentImportKeys": [],
|
||||
"personalRecords": [],
|
||||
"userAchievements": [],
|
||||
"environmentContexts": [],
|
||||
|
||||
@@ -1239,8 +1239,9 @@
|
||||
"sourceId": "4711",
|
||||
"sourceInstance": "https://paperless.example.test",
|
||||
"aiReadDeferred": true,
|
||||
"createdAt": "2026-05-04T01:24:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:25:00.000Z",
|
||||
"extractionIncomplete": true,
|
||||
"createdAt": "2026-05-04T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-04T01:26:00.000Z",
|
||||
"deletedAt": null
|
||||
},
|
||||
{
|
||||
@@ -1268,8 +1269,9 @@
|
||||
"sourceId": null,
|
||||
"sourceInstance": null,
|
||||
"aiReadDeferred": false,
|
||||
"createdAt": "2026-05-04T01:24:01.000Z",
|
||||
"updatedAt": "2026-05-04T01:25:01.000Z",
|
||||
"extractionIncomplete": false,
|
||||
"createdAt": "2026-05-04T01:25:01.000Z",
|
||||
"updatedAt": "2026-05-04T01:26:01.000Z",
|
||||
"deletedAt": "<tombstone>"
|
||||
}
|
||||
],
|
||||
@@ -1872,10 +1874,20 @@
|
||||
"updatedAt": "2026-05-05T18:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"PersonalRecord": [
|
||||
"DocumentImportKey": [
|
||||
{
|
||||
"id": "<id:105>",
|
||||
"userId": "round-trip-owner",
|
||||
"sourceSystem": "PAPRA",
|
||||
"sourceId": "doc_deleted",
|
||||
"sourceInstance": "https://papra.example",
|
||||
"createdAt": "2026-07-03T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"PersonalRecord": [
|
||||
{
|
||||
"id": "<id:106>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
"metricSlot": "running_10km_time",
|
||||
"direction": "MIN",
|
||||
@@ -1885,10 +1897,10 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-10km",
|
||||
"createdAt": "2026-05-05T20:31:02.000Z"
|
||||
"createdAt": "2026-05-05T22:11:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:106>",
|
||||
"id": "<id:107>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WALKING_RUNNING_DISTANCE",
|
||||
"metricSlot": "running_5km_time",
|
||||
@@ -1899,10 +1911,10 @@
|
||||
"sourceMeasurementId": null,
|
||||
"source": "APPLE_HEALTH",
|
||||
"externalId": "workout-pr-5km",
|
||||
"createdAt": "2026-05-05T20:31:01.000Z"
|
||||
"createdAt": "2026-05-05T22:11:01.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:107>",
|
||||
"id": "<id:108>",
|
||||
"userId": "round-trip-owner",
|
||||
"metricType": "WEIGHT",
|
||||
"metricSlot": null,
|
||||
@@ -1913,28 +1925,28 @@
|
||||
"sourceMeasurementId": "<id:5>",
|
||||
"source": "MANUAL",
|
||||
"externalId": null,
|
||||
"createdAt": "2026-05-05T20:31:00.000Z"
|
||||
"createdAt": "2026-05-05T22:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"UserAchievement": [
|
||||
{
|
||||
"id": "<id:108>",
|
||||
"id": "<id:109>",
|
||||
"userId": "round-trip-owner",
|
||||
"achievementId": "intake-total-10",
|
||||
"unlockedAt": "2026-02-21T10:15:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:00.000Z"
|
||||
"createdAt": "2026-05-05T23:44:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:109>",
|
||||
"id": "<id:110>",
|
||||
"userId": "round-trip-owner",
|
||||
"achievementId": "retired-badge-from-an-older-release",
|
||||
"unlockedAt": "2025-11-05T08:00:00.000Z",
|
||||
"createdAt": "2026-05-05T22:04:01.000Z"
|
||||
"createdAt": "2026-05-05T23:44:01.000Z"
|
||||
}
|
||||
],
|
||||
"EnvironmentContext": [
|
||||
{
|
||||
"id": "<id:110>",
|
||||
"id": "<id:111>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-06-15",
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
@@ -1974,11 +1986,11 @@
|
||||
"aqHours": 3,
|
||||
"aqFetchedAt": "2026-06-30T12:34:56.789Z",
|
||||
"fetchedAt": "2026-06-16T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:00.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:00.000Z"
|
||||
"createdAt": "2026-05-06T02:00:00.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:111>",
|
||||
"id": "<id:112>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-07-01",
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":52.5,\"lon\":13.4,\"label\":\"Berlin\"}>",
|
||||
@@ -2018,24 +2030,24 @@
|
||||
"aqHours": null,
|
||||
"aqFetchedAt": null,
|
||||
"fetchedAt": "2026-07-02T03:15:00.000Z",
|
||||
"createdAt": "2026-05-06T00:20:01.000Z",
|
||||
"updatedAt": "2026-05-06T00:21:01.000Z"
|
||||
"createdAt": "2026-05-06T02:00:01.000Z",
|
||||
"updatedAt": "2026-05-06T02:01:01.000Z"
|
||||
}
|
||||
],
|
||||
"EnvironmentTravelLocation": [
|
||||
{
|
||||
"id": "<id:112>",
|
||||
"id": "<id:113>",
|
||||
"userId": "round-trip-owner",
|
||||
"startDate": "2026-06-10",
|
||||
"endDate": "2026-06-20",
|
||||
"locationEncrypted": "<sealed:v1:{\"lat\":41.4,\"lon\":2.2,\"label\":\"Barcelona\"}>",
|
||||
"createdAt": "2026-05-06T01:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T01:26:00.000Z"
|
||||
"createdAt": "2026-05-06T03:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"DawarichDay": [
|
||||
{
|
||||
"id": "<id:113>",
|
||||
"id": "<id:114>",
|
||||
"userId": "round-trip-owner",
|
||||
"date": "2026-06-15",
|
||||
"totalDistanceM": 48300,
|
||||
@@ -2046,13 +2058,13 @@
|
||||
"countryCodeEncrypted": "<sealed:v1:country:ES>",
|
||||
"isTravelDay": true,
|
||||
"fetchedAt": "2026-06-16T03:20:00.000Z",
|
||||
"createdAt": "2026-05-06T03:11:00.000Z",
|
||||
"updatedAt": "2026-05-06T03:12:00.000Z"
|
||||
"createdAt": "2026-05-06T04:51:00.000Z",
|
||||
"updatedAt": "2026-05-06T04:52:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationScheduleRevision": [
|
||||
{
|
||||
"id": "<id:114>",
|
||||
"id": "<id:115>",
|
||||
"medicationId": "<id:7>",
|
||||
"validFrom": "2026-01-01T00:00:00.000Z",
|
||||
"validUntil": "2026-03-01T00:00:00.000Z",
|
||||
@@ -2065,10 +2077,10 @@
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByRevisionId": null,
|
||||
"createdAt": "2026-05-06T04:47:00.000Z"
|
||||
"createdAt": "2026-05-06T06:27:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:115>",
|
||||
"id": "<id:116>",
|
||||
"medicationId": "<id:7>",
|
||||
"validFrom": "2026-02-01T00:00:00.000Z",
|
||||
"validUntil": "2026-03-01T00:00:00.000Z",
|
||||
@@ -2080,11 +2092,11 @@
|
||||
}
|
||||
],
|
||||
"source": "ARCHIVED",
|
||||
"supersededByRevisionId": "<id:116>",
|
||||
"createdAt": "2026-05-06T04:47:02.000Z"
|
||||
"supersededByRevisionId": "<id:117>",
|
||||
"createdAt": "2026-05-06T06:27:02.000Z"
|
||||
},
|
||||
{
|
||||
"id": "<id:116>",
|
||||
"id": "<id:117>",
|
||||
"medicationId": "<id:7>",
|
||||
"validFrom": "2026-03-01T00:00:00.000Z",
|
||||
"validUntil": "2026-05-01T00:00:00.000Z",
|
||||
@@ -2097,23 +2109,23 @@
|
||||
],
|
||||
"source": "MANUAL",
|
||||
"supersededByRevisionId": null,
|
||||
"createdAt": "2026-05-06T04:47:01.000Z"
|
||||
"createdAt": "2026-05-06T06:27:01.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationEfficacyTarget": [
|
||||
{
|
||||
"id": "<id:117>",
|
||||
"id": "<id:118>",
|
||||
"medicationId": "<id:7>",
|
||||
"measurementType": "WEIGHT",
|
||||
"biomarkerId": "<id:26>",
|
||||
"primary": false,
|
||||
"createdAt": "2026-05-06T06:25:00.000Z",
|
||||
"updatedAt": "2026-05-06T06:26:00.000Z"
|
||||
"createdAt": "2026-05-06T08:05:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"EcgRecording": [
|
||||
{
|
||||
"id": "<id:118>",
|
||||
"id": "<id:119>",
|
||||
"userId": "round-trip-owner",
|
||||
"source": "MANUAL",
|
||||
"externalRecordingId": "round-trip-signal-1",
|
||||
@@ -2126,13 +2138,13 @@
|
||||
"averageHeartRate": 88,
|
||||
"rhythmClassification": "IRREGULAR",
|
||||
"measurementId": "<id:2>",
|
||||
"createdAt": "2026-05-06T08:13:00.000Z",
|
||||
"updatedAt": "2026-05-06T08:14:00.000Z"
|
||||
"createdAt": "2026-05-06T09:53:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:54:00.000Z"
|
||||
}
|
||||
],
|
||||
"OnboardingRecord": [
|
||||
{
|
||||
"id": "<id:119>",
|
||||
"id": "<id:120>",
|
||||
"userId": "round-trip-owner",
|
||||
"needsJson": {
|
||||
"areas": [
|
||||
@@ -2171,80 +2183,80 @@
|
||||
},
|
||||
"modulesDerivedAt": "2026-07-01T08:00:00.000Z",
|
||||
"completedAt": "2026-07-01T08:00:00.000Z",
|
||||
"createdAt": "2026-05-06T09:47:00.000Z",
|
||||
"updatedAt": "2026-05-06T09:48:00.000Z"
|
||||
"createdAt": "2026-05-06T11:27:00.000Z",
|
||||
"updatedAt": "2026-05-06T11:28:00.000Z"
|
||||
}
|
||||
],
|
||||
"BiomarkerAlias": [
|
||||
{
|
||||
"id": "<id:120>",
|
||||
"id": "<id:121>",
|
||||
"userId": "round-trip-owner",
|
||||
"biomarkerId": "<id:26>",
|
||||
"name": "Ferr.",
|
||||
"nameFolded": "ferr.",
|
||||
"source": "IMPORT_CHOICE",
|
||||
"createdAt": "2026-05-06T11:26:00.000Z"
|
||||
"createdAt": "2026-05-06T13:06:00.000Z"
|
||||
}
|
||||
],
|
||||
"BiomarkerMerge": [
|
||||
{
|
||||
"id": "<id:121>",
|
||||
"id": "<id:122>",
|
||||
"userId": "round-trip-owner",
|
||||
"targetBiomarkerId": "<id:26>",
|
||||
"sourceBiomarkerId": "merged-away-marker",
|
||||
"sourceName": "Ferritin (Serum)",
|
||||
"readingCount": 3,
|
||||
"mergedAt": "2026-05-06T13:06:00.000Z"
|
||||
"mergedAt": "2026-05-06T14:46:00.000Z"
|
||||
}
|
||||
],
|
||||
"LabResultDocumentLink": [
|
||||
{
|
||||
"id": "<id:122>",
|
||||
"id": "<id:123>",
|
||||
"labResultId": "<id:25>",
|
||||
"documentId": "<id:65>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T14:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"EncounterMedicationLink": [
|
||||
{
|
||||
"id": "<id:123>",
|
||||
"encounterId": "<id:70>",
|
||||
"medicationId": "<id:7>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T16:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"EncounterVaccinationLink": [
|
||||
"EncounterMedicationLink": [
|
||||
{
|
||||
"id": "<id:124>",
|
||||
"encounterId": "<id:70>",
|
||||
"vaccinationId": "<id:76>",
|
||||
"medicationId": "<id:7>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T18:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationDocumentLink": [
|
||||
"EncounterVaccinationLink": [
|
||||
{
|
||||
"id": "<id:125>",
|
||||
"medicationId": "<id:7>",
|
||||
"documentId": "<id:65>",
|
||||
"encounterId": "<id:70>",
|
||||
"vaccinationId": "<id:76>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T19:44:00.000Z"
|
||||
}
|
||||
],
|
||||
"MedicationConditionLink": [
|
||||
"MedicationDocumentLink": [
|
||||
{
|
||||
"id": "<id:126>",
|
||||
"medicationId": "<id:7>",
|
||||
"episodeId": "<id:32>",
|
||||
"documentId": "<id:65>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T21:24:00.000Z"
|
||||
}
|
||||
],
|
||||
"AccountGrant": [
|
||||
"MedicationConditionLink": [
|
||||
{
|
||||
"id": "<id:127>",
|
||||
"medicationId": "<id:7>",
|
||||
"episodeId": "<id:32>",
|
||||
"userId": "round-trip-owner",
|
||||
"createdAt": "2026-05-06T23:04:00.000Z"
|
||||
}
|
||||
],
|
||||
"AccountGrant": [
|
||||
{
|
||||
"id": "<id:128>",
|
||||
"grantorId": "round-trip-owner",
|
||||
"granteeId": "parity-delegate",
|
||||
"access": "WRITE",
|
||||
@@ -2253,13 +2265,13 @@
|
||||
"measurements"
|
||||
]
|
||||
},
|
||||
"invitedAt": "2026-05-07T02:25:00.000Z",
|
||||
"invitedAt": "2026-05-07T04:05:00.000Z",
|
||||
"acceptedAt": "2026-07-02T09:00:00.000Z",
|
||||
"revokedAt": null,
|
||||
"revokedBy": null,
|
||||
"expiresAt": null,
|
||||
"lastUsedAt": null,
|
||||
"createdAt": "2026-05-07T02:31:00.000Z"
|
||||
"createdAt": "2026-05-07T04:11:00.000Z"
|
||||
}
|
||||
],
|
||||
"User": {
|
||||
@@ -2271,7 +2283,7 @@
|
||||
"oidcIssuer": "rt-User-oidcIssuer",
|
||||
"oidcSub": "rt-User-oidcSub",
|
||||
"createdAt": "2026-06-30T12:34:56.789Z",
|
||||
"updatedAt": "2026-05-06T23:08:00.000Z",
|
||||
"updatedAt": "2026-05-07T00:48:00.000Z",
|
||||
"managedProfileAt": "2026-06-30T12:34:56.789Z",
|
||||
"heightCm": 2.5,
|
||||
"dateOfBirth": "2026-06-30T12:34:56.789Z",
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* The memory that the person deleted an imported document, through a backup
|
||||
* and a restore onto an instance that has never seen the account.
|
||||
*
|
||||
* Deleting an imported document leaves its source keys behind: on the
|
||||
* tombstone while it waits in Recently deleted, then in the import ledger
|
||||
* (`DocumentImportKey`) once the purge removes it. An importer re-sending
|
||||
* such a key is answered "deleted" and nothing is stored. The backup carried
|
||||
* neither, so after a restore onto a fresh instance a token importer brought
|
||||
* back every document the person had deleted on purpose.
|
||||
*
|
||||
* The file now carries the ledger, and the keys of the documents waiting in
|
||||
* Recently deleted as ledger entries (the documents themselves stay out, as
|
||||
* they always have): restored, both are answered "deleted", while a live
|
||||
* document's key stays the live document's.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
process.env.ENCRYPTION_KEY =
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
|
||||
import { encrypt } from "@/lib/crypto";
|
||||
import { encryptDocumentContent } from "@/lib/documents/store";
|
||||
import { buildFullBackupPayload } from "@/lib/export/full-backup-payload";
|
||||
import { parseBackupPayload } from "@/lib/validations/backup";
|
||||
import { POST as RESTORE } from "./restore-job-driver";
|
||||
|
||||
import { cookieJar, headerJar } from "./mock-next-headers";
|
||||
import { getPrismaClient, truncateAllTables } from "./setup";
|
||||
|
||||
vi.mock("next/headers", async () => {
|
||||
const { cookieJar, headerJar } = await import("./mock-next-headers");
|
||||
return {
|
||||
headers: vi.fn(async () => ({
|
||||
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
|
||||
})),
|
||||
cookies: vi.fn(async () => ({
|
||||
get: (name: string) => {
|
||||
const value = cookieJar.get(name);
|
||||
return value ? { name, value } : undefined;
|
||||
},
|
||||
set: (name: string, value: string) => cookieJar.set(name, value),
|
||||
delete: (name: string) => cookieJar.delete(name),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/db-compat", () => ({
|
||||
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/cache/invalidate", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("@/lib/cache/invalidate")>();
|
||||
return { ...actual, invalidateUserData: vi.fn() };
|
||||
});
|
||||
|
||||
const INSTANCE = "https://paperless.example";
|
||||
const pdf = (tag: string) =>
|
||||
Buffer.from(`%PDF-1.7\n% ${tag}\n1 0 obj\n<<>>\nendobj\n%%EOF\n`);
|
||||
|
||||
beforeEach(async () => {
|
||||
await truncateAllTables(getPrismaClient());
|
||||
cookieJar.clear();
|
||||
headerJar.clear();
|
||||
});
|
||||
|
||||
async function seedDocument(
|
||||
userId: string,
|
||||
sourceId: string,
|
||||
deletedAt: Date | null,
|
||||
) {
|
||||
const bytes = pdf(sourceId);
|
||||
const { content, codec } = encryptDocumentContent(bytes);
|
||||
return getPrismaClient().inboundDocument.create({
|
||||
data: {
|
||||
userId,
|
||||
mimeType: "application/pdf",
|
||||
byteSize: bytes.byteLength,
|
||||
contentEncrypted: content,
|
||||
contentCodec: codec,
|
||||
sourceSystem: "PAPERLESS",
|
||||
sourceId,
|
||||
sourceInstance: INSTANCE,
|
||||
deletedAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function signIn(userId: string) {
|
||||
const session = await getPrismaClient().session.create({
|
||||
data: { userId, expiresAt: new Date(Date.now() + 600_000) },
|
||||
});
|
||||
cookieJar.set("healthlog_session", session.id);
|
||||
}
|
||||
|
||||
async function importAgain(sourceId: string) {
|
||||
const { POST } = await import("@/app/api/documents/inbound/route");
|
||||
const bytes = pdf(sourceId);
|
||||
const payload = new Uint8Array(bytes.byteLength);
|
||||
payload.set(bytes);
|
||||
const form = new FormData();
|
||||
form.append("file", new Blob([payload]), "letter.pdf");
|
||||
form.append("sourceSystem", "PAPERLESS");
|
||||
form.append("sourceId", sourceId);
|
||||
form.append("sourceInstance", INSTANCE);
|
||||
const res = await (POST as unknown as (r: Request) => Promise<Response>)(
|
||||
new Request("http://localhost/api/documents/inbound", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
}),
|
||||
);
|
||||
return { status: res.status, body: await res.json() };
|
||||
}
|
||||
|
||||
describe("a restore onto a fresh instance", () => {
|
||||
it("still answers a deliberately deleted import as deleted", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
const owner = await prisma.user.create({
|
||||
data: {
|
||||
username: "ledger-owner",
|
||||
email: "ledger-owner@example.test",
|
||||
role: "ADMIN",
|
||||
modulePreferencesJson: { inboundDocuments: true },
|
||||
},
|
||||
});
|
||||
// Waiting in Recently deleted, with a second key remembered as an alias.
|
||||
const waiting = await seedDocument(owner.id, "412", new Date());
|
||||
await prisma.documentSourceAlias.create({
|
||||
data: {
|
||||
userId: owner.id,
|
||||
documentId: waiting.id,
|
||||
sourceSystem: "PAPERLESS",
|
||||
sourceId: "413",
|
||||
sourceInstance: INSTANCE,
|
||||
},
|
||||
});
|
||||
// Purged already: only the ledger remembers it.
|
||||
await prisma.documentImportKey.create({
|
||||
data: {
|
||||
userId: owner.id,
|
||||
sourceSystem: "PAPERLESS",
|
||||
sourceId: "500",
|
||||
sourceInstance: INSTANCE,
|
||||
},
|
||||
});
|
||||
// Live.
|
||||
await seedDocument(owner.id, "600", null);
|
||||
|
||||
const { payload: built } = await buildFullBackupPayload(prisma, owner.id, {
|
||||
purpose: "disaster-recovery",
|
||||
});
|
||||
const parsed = parseBackupPayload(JSON.stringify(built));
|
||||
|
||||
// The fresh instance: the account as the file brings it, nothing else.
|
||||
await prisma.documentImportKey.deleteMany({ where: { userId: owner.id } });
|
||||
await prisma.inboundDocument.deleteMany({ where: { userId: owner.id } });
|
||||
const backup = await prisma.dataBackup.create({
|
||||
data: {
|
||||
userId: owner.id,
|
||||
type: "IMPORT_LEDGER_ROUND_TRIP",
|
||||
data: encrypt(JSON.stringify(parsed)),
|
||||
},
|
||||
});
|
||||
await signIn(owner.id);
|
||||
const response = await RESTORE(
|
||||
new Request(`http://localhost/api/admin/backups/${backup.id}/restore`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ confirm: "RESTORE" }),
|
||||
}) as unknown as Parameters<typeof RESTORE>[0],
|
||||
{ params: Promise.resolve({ id: backup.id }) },
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
expect(
|
||||
(
|
||||
await prisma.documentImportKey.findMany({
|
||||
where: { userId: owner.id },
|
||||
select: { sourceSystem: true, sourceId: true, sourceInstance: true },
|
||||
orderBy: { sourceId: "asc" },
|
||||
})
|
||||
).map((k) => [k.sourceSystem, k.sourceId, k.sourceInstance]),
|
||||
).toEqual([
|
||||
["PAPERLESS", "412", INSTANCE],
|
||||
["PAPERLESS", "413", INSTANCE],
|
||||
["PAPERLESS", "500", INSTANCE],
|
||||
]);
|
||||
|
||||
await signIn(owner.id);
|
||||
for (const sourceId of ["412", "413", "500"]) {
|
||||
const again = await importAgain(sourceId);
|
||||
expect(again.status, sourceId).toBe(200);
|
||||
expect(again.body.meta, sourceId).toEqual({
|
||||
duplicate: true,
|
||||
deleted: true,
|
||||
});
|
||||
}
|
||||
// The live document's key is still the live document's.
|
||||
const live = await importAgain("600");
|
||||
expect(live.status).toBe(200);
|
||||
expect(live.body.meta).toMatchObject({ duplicate: true });
|
||||
expect(live.body.meta.deleted).toBeUndefined();
|
||||
expect(
|
||||
await prisma.inboundDocument.count({ where: { userId: owner.id } }),
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -847,6 +847,19 @@ export async function seedEveryTwoEndedModel(
|
||||
},
|
||||
});
|
||||
|
||||
// An imported page the person deleted and the purge then removed: only the
|
||||
// ledger remembers it, and a restore that dropped the row would let the
|
||||
// importer store it again.
|
||||
await prisma.documentImportKey.create({
|
||||
data: {
|
||||
userId: OWNER_ID,
|
||||
sourceSystem: "PAPRA",
|
||||
sourceId: "doc_deleted",
|
||||
sourceInstance: "https://papra.example",
|
||||
createdAt: AT("2026-07-03T09:00:00.000Z"),
|
||||
},
|
||||
});
|
||||
|
||||
// The page filed under the SECOND condition, not the first. A restore that
|
||||
// kept the count and lost the pairing would leave the vault sorted wrongly
|
||||
// rather than visibly unsorted, which is harder to notice and worse.
|
||||
|
||||
@@ -286,6 +286,8 @@ const COUNT_BACK: Record<
|
||||
DocumentConditionLink: (p, userId) =>
|
||||
p.documentConditionLink.count({ where: { userId } }),
|
||||
ExtractedFact: (p, userId) => p.extractedFact.count({ where: { userId } }),
|
||||
DocumentImportKey: (p, userId) =>
|
||||
p.documentImportKey.count({ where: { userId } }),
|
||||
PersonalRecord: (p, userId) => p.personalRecord.count({ where: { userId } }),
|
||||
UserAchievement: (p, userId) =>
|
||||
p.userAchievement.count({ where: { userId } }),
|
||||
|
||||
Reference in new issue
Block a user