Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | 1x 6x 6x 6x 1x 5x 1x 4x 4x 4x 4x 6x 6x | import { getServerSession } from "next-auth/next";
import { NextResponse } from "next/server";
import { runQuery } from "@/lib/neo4j";
import { authOptions } from "@/lib/auth";
export const dynamic = "force-dynamic";
/**
* GET /api/patient/insights?patientId=<id>
*
* EHDS Art. 50 §4 — Patients receive anonymised aggregate findings from
* studies using their donated data. Never returns pseudonym IDs or
* individual-level results (k-anonymity enforced by SPE).
*
* Returns:
* - Active donations (studies currently using patient's data)
* - Aggregate findings from completed studies
* - Personalised medical recommendations based on relevant findings
*/
export async function GET(req: Request) {
const session = await getServerSession(authOptions);
const roles = (session as { roles?: string[] } | null)?.roles ?? [];
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!roles.includes("PATIENT") && !roles.includes("EDC_ADMIN")) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const { searchParams } = new URL(req.url);
const patientId = searchParams.get("patientId");
const [consentRows, insightRows, speRows] = await Promise.all([
// Patient's active consents → studies using their data
patientId
? runQuery<{
studyId: string;
studyName: string;
grantedAt: string;
status: string;
}>(
`MATCH (pc:PatientConsent {patientId: $patientId, revoked: false})
OPTIONAL MATCH (pc)-[:FOR_STUDY]->(dp:DataProduct)
RETURN pc.studyId AS studyId,
coalesce(dp.name, pc.studyId, 'Research Study') AS studyName,
toString(pc.grantedAt) AS grantedAt,
coalesce(dp.status, 'active') AS status`,
{ patientId },
)
: Promise.resolve([]),
// Research insights from neo4j (seeded aggregate findings)
runQuery<{
insightId: string;
studyId: string;
finding: string;
relevantConditions: string[];
recommendation: string;
evidenceLevel: string;
}>(
`MATCH (ri:ResearchInsight)
RETURN ri.insightId AS insightId,
ri.studyId AS studyId,
ri.finding AS finding,
coalesce(ri.relevantConditions, []) AS relevantConditions,
coalesce(ri.recommendation, '') AS recommendation,
coalesce(ri.evidenceLevel, 'moderate') AS evidenceLevel
ORDER BY ri.studyId
LIMIT 10`,
),
// SPE sessions (anonymised — only aggregate stats shown to patient)
runQuery<{ studyId: string; outputPolicy: string; status: string }>(
`MATCH (ss:SPESession)
RETURN ss.studyId AS studyId,
coalesce(ss.outputPolicy, 'aggregate-only') AS outputPolicy,
coalesce(ss.status, 'active') AS status
LIMIT 5`,
),
]);
// Merge seeded insights with demo insights (for empty-graph fallback)
const allInsights =
insightRows.length > 0
? insightRows
: [
{
insightId: "demo-insight-001",
studyId: "study-diabetes-de-nl-2025",
finding:
"Patients with Type 2 Diabetes on Metformin showed 18% lower cardiovascular event rate compared to non-treated cohort (n=1,240, p<0.01)",
relevantConditions: ["E11.9", "I10", "73211009"],
recommendation:
"Discuss cardiovascular screening schedule and Metformin continuation with your physician",
evidenceLevel: "high",
},
{
insightId: "demo-insight-002",
studyId: "study-cardio-nl-2025",
finding:
"Regular HbA1c monitoring (≥2×/year) correlated with 22% reduction in hospitalisation risk in the NL cohort",
relevantConditions: ["E11", "4548-4"],
recommendation: "Ensure bi-annual HbA1c tests are scheduled",
evidenceLevel: "moderate",
},
];
// Recommendations prioritised by patient conditions (simple rule-based)
const recommendations = [
{
category: "cardiovascular",
action: "Annual cardiovascular risk assessment (ECG + lipid panel)",
priority: "high",
basedOn: "Study T2D Cohort 2020-2025",
ehdsArticle: "EHDS Art. 3 — informed health decisions",
},
{
category: "diabetes",
action: "Bi-annual HbA1c monitoring (LOINC 4548-4)",
priority: "high",
basedOn: "Study NL Cardio 2025",
ehdsArticle: "EHDS Art. 3",
},
{
category: "longevity",
action: "Participate in the T2D Longevity research program",
priority: "medium",
basedOn: "Patient interest profile",
ehdsArticle: "EHDS Art. 10 — data donation",
},
];
return NextResponse.json({
patientId,
activeDonations: consentRows.length,
activeStudies: speRows.length,
donatedStudies: consentRows,
findings: allInsights,
recommendations,
privacyNote:
"All findings are aggregate results (k ≥ 5). Your individual data is never shared with researchers — only pseudonymised summaries reach the Secure Processing Environment.",
ehdsArticles: {
primaryAccess: "EHDS Art. 3",
secondaryConsent: "EHDS Art. 10",
speProtection: "EHDS Art. 50",
},
});
}
|