mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
test(insights): pin the comprehensive overview before moving its builder
This commit is contained in:
1 parent
c6238cbe14
commit
2e13b7754e
12 files changed
+9220
No files matched your search
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"status": 401,
|
||||
"body": {
|
||||
"data": null,
|
||||
"error": "Not authenticated",
|
||||
"meta": {
|
||||
"errorCode": "auth.missing",
|
||||
"requestId": "<requestId>"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 401,
|
||||
"action": null,
|
||||
"meta": null,
|
||||
"warnings": null,
|
||||
"error": {
|
||||
"type": "HttpError",
|
||||
"message": "Not authenticated"
|
||||
}
|
||||
}
|
||||
],
|
||||
"effects": []
|
||||
}
|
||||
+1303
File diff suppressed because it is too large.
Load diff
+1274
File diff suppressed because it is too large.
Load diff
+1287
File diff suppressed because it is too large.
Load diff
+1274
File diff suppressed because it is too large.
Load diff
+1274
File diff suppressed because it is too large.
Load diff
+1303
File diff suppressed because it is too large.
Load diff
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"status": 403,
|
||||
"body": {
|
||||
"data": null,
|
||||
"error": "Account access denied",
|
||||
"meta": {
|
||||
"errorCode": "sharing.access.denied",
|
||||
"requestId": "<requestId>"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 403,
|
||||
"action": null,
|
||||
"meta": {
|
||||
"sharing_refusal": "insufficient_access"
|
||||
},
|
||||
"warnings": null,
|
||||
"error": {
|
||||
"type": "SharingAccessDeniedError",
|
||||
"message": "Account access denied"
|
||||
}
|
||||
}
|
||||
],
|
||||
"effects": []
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"status": 429,
|
||||
"body": {
|
||||
"data": null,
|
||||
"error": "Too many analytics requests. Please retry later.",
|
||||
"meta": {
|
||||
"errorCode": "http.rate_limited",
|
||||
"requestId": "<requestId>"
|
||||
}
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 429,
|
||||
"action": null,
|
||||
"meta": null,
|
||||
"warnings": null,
|
||||
"error": null
|
||||
}
|
||||
],
|
||||
"effects": []
|
||||
}
|
||||
+884
@@ -0,0 +1,884 @@
|
||||
{
|
||||
"status": 200,
|
||||
"body": {
|
||||
"data": {
|
||||
"summaries": {
|
||||
"WEIGHT": {
|
||||
"count": 47,
|
||||
"latest": 70.3,
|
||||
"min": 70,
|
||||
"max": 71.2,
|
||||
"mean": 70.63,
|
||||
"median": null,
|
||||
"avg7": 70.55,
|
||||
"avg30": 70.62,
|
||||
"slope7": {
|
||||
"slope": 0.043,
|
||||
"direction": "up",
|
||||
"confidence": 0.03
|
||||
},
|
||||
"slope30": {
|
||||
"slope": -0.004,
|
||||
"direction": "stable",
|
||||
"confidence": 0.01
|
||||
},
|
||||
"anomalyCount": 0,
|
||||
"avg30LastMonth": 70.63,
|
||||
"avg30LastYear": null
|
||||
},
|
||||
"BLOOD_PRESSURE_SYS": {
|
||||
"count": 47,
|
||||
"latest": 134,
|
||||
"min": 131,
|
||||
"max": 163,
|
||||
"mean": 139.66,
|
||||
"median": null,
|
||||
"avg7": 139.67,
|
||||
"avg30": 139.93,
|
||||
"slope7": {
|
||||
"slope": -2.057,
|
||||
"direction": "down",
|
||||
"confidence": 0.18
|
||||
},
|
||||
"slope30": {
|
||||
"slope": -0.03,
|
||||
"direction": "down",
|
||||
"confidence": 0
|
||||
},
|
||||
"anomalyCount": 4,
|
||||
"avg30LastMonth": 139.26,
|
||||
"avg30LastYear": null
|
||||
},
|
||||
"BLOOD_PRESSURE_DIA": {
|
||||
"count": 47,
|
||||
"latest": 86,
|
||||
"min": 84,
|
||||
"max": 90,
|
||||
"mean": 87.04,
|
||||
"median": null,
|
||||
"avg7": 87.5,
|
||||
"avg30": 87.07,
|
||||
"slope7": {
|
||||
"slope": -0.2,
|
||||
"direction": "down",
|
||||
"confidence": 0.04
|
||||
},
|
||||
"slope30": {
|
||||
"slope": 0.034,
|
||||
"direction": "up",
|
||||
"confidence": 0.02
|
||||
},
|
||||
"anomalyCount": 0,
|
||||
"avg30LastMonth": 87,
|
||||
"avg30LastYear": null
|
||||
},
|
||||
"PULSE": {
|
||||
"count": 141,
|
||||
"latest": 93,
|
||||
"min": 66,
|
||||
"max": 96,
|
||||
"mean": 81.67,
|
||||
"median": null,
|
||||
"avg7": 81.61,
|
||||
"avg30": 81.61,
|
||||
"slope7": {
|
||||
"slope": 0.186,
|
||||
"direction": "up",
|
||||
"confidence": 0
|
||||
},
|
||||
"slope30": {
|
||||
"slope": 0.027,
|
||||
"direction": "up",
|
||||
"confidence": 0
|
||||
},
|
||||
"anomalyCount": 0,
|
||||
"avg30LastMonth": 81.75,
|
||||
"avg30LastYear": null
|
||||
}
|
||||
},
|
||||
"bmi": null,
|
||||
"bmiClassification": null,
|
||||
"bpClassification": {
|
||||
"category": "High-normal",
|
||||
"color": "var(--dracula-yellow)",
|
||||
"severity": "elevated"
|
||||
},
|
||||
"bpPctInTarget": 0,
|
||||
"bpTargets": {
|
||||
"sysLow": 130,
|
||||
"sysHigh": 139,
|
||||
"diaLow": 70,
|
||||
"diaHigh": 79
|
||||
},
|
||||
"weightBpCorrelation": {
|
||||
"r": -0.631,
|
||||
"strength": "moderat",
|
||||
"n": 47
|
||||
},
|
||||
"scatterData": [
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 156
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 139
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 136
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 133
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 163
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 159
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 139
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 136
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 141
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 162
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 139
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 158
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 133
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 141
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 154
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 161
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 136
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 133
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"weight": 70,
|
||||
"sysBP": 157
|
||||
},
|
||||
{
|
||||
"weight": 71.2,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"weight": 70.9,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"weight": 70.6,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"weight": 70.3,
|
||||
"sysBP": 134
|
||||
}
|
||||
],
|
||||
"bpMedicationCorrelation": {
|
||||
"r": -0.941,
|
||||
"strength": "stark",
|
||||
"n": 28,
|
||||
"medicationCount": 1
|
||||
},
|
||||
"bpMedicationScatterData": [
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"continuityPct": 0,
|
||||
"sysBP": 159
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 136
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 141
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"continuityPct": 0,
|
||||
"sysBP": 162
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 139
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 133
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 141
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"continuityPct": 0,
|
||||
"sysBP": 154
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"continuityPct": 0,
|
||||
"sysBP": 161
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 136
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"continuityPct": 0,
|
||||
"sysBP": 157
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"continuityPct": 100,
|
||||
"sysBP": 134
|
||||
}
|
||||
],
|
||||
"moodSummary": {
|
||||
"count": 29,
|
||||
"latest": 3,
|
||||
"min": 1,
|
||||
"max": 5,
|
||||
"mean": 3.07,
|
||||
"median": 3,
|
||||
"avg7": 3,
|
||||
"avg30": 3.04,
|
||||
"slope7": {
|
||||
"slope": 0.286,
|
||||
"direction": "up",
|
||||
"confidence": 0.14
|
||||
},
|
||||
"slope30": {
|
||||
"slope": 0,
|
||||
"direction": "stable",
|
||||
"confidence": 0
|
||||
},
|
||||
"anomalyCount": 0,
|
||||
"avg30LastMonth": 4,
|
||||
"avg30LastYear": null
|
||||
},
|
||||
"moodBpCorrelation": {
|
||||
"r": -0.683,
|
||||
"strength": "moderat",
|
||||
"n": 28
|
||||
},
|
||||
"moodBpScatterData": [
|
||||
{
|
||||
"mood": 4,
|
||||
"sysBP": 141
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"sysBP": 162
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"sysBP": 139
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"sysBP": 158
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"sysBP": 133
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"sysBP": 141
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"sysBP": 135
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"sysBP": 154
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"sysBP": 134
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"sysBP": 131
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"sysBP": 161
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"sysBP": 136
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"sysBP": 133
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"sysBP": 138
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"sysBP": 157
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"sysBP": 132
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"sysBP": 140
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"sysBP": 137
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"sysBP": 134
|
||||
}
|
||||
],
|
||||
"moodWeightCorrelation": {
|
||||
"r": 0.482,
|
||||
"strength": "moderat",
|
||||
"n": 28
|
||||
},
|
||||
"moodWeightScatterData": [
|
||||
{
|
||||
"mood": 4,
|
||||
"weight": 71.2
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"weight": 70.9
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"weight": 70.6
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"weight": 70.3
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"weight": 70
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"weight": 71.2
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"weight": 70.9
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"weight": 70.6
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"weight": 70.3
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"weight": 70
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"weight": 71.2
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"weight": 70.9
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"weight": 70.6
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"weight": 70.3
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"weight": 70
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"weight": 71.2
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"weight": 70.9
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"weight": 70.6
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"weight": 70.3
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"weight": 70
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"weight": 71.2
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"weight": 70.9
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"weight": 70.3
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"weight": 70
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"weight": 71.2
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"weight": 70.9
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"weight": 70.6
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"weight": 70.3
|
||||
}
|
||||
],
|
||||
"moodPulseCorrelation": null,
|
||||
"moodPulseScatterData": [
|
||||
{
|
||||
"mood": 2,
|
||||
"pulse": 72
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"pulse": 71
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"pulse": 78
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"pulse": 77
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"pulse": 76
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"pulse": 75
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"pulse": 74
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"pulse": 73
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"pulse": 72
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"pulse": 74
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"pulse": 73
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"pulse": 77
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"pulse": 76
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"pulse": 75
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"pulse": 74
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"pulse": 73
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"pulse": 75
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"pulse": 74
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"pulse": 73
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"pulse": 72
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"pulse": 76
|
||||
},
|
||||
{
|
||||
"mood": 1,
|
||||
"pulse": 76
|
||||
},
|
||||
{
|
||||
"mood": 4,
|
||||
"pulse": 75
|
||||
},
|
||||
{
|
||||
"mood": 2,
|
||||
"pulse": 74
|
||||
},
|
||||
{
|
||||
"mood": 5,
|
||||
"pulse": 73
|
||||
},
|
||||
{
|
||||
"mood": 3,
|
||||
"pulse": 72
|
||||
}
|
||||
],
|
||||
"medications": [
|
||||
{
|
||||
"id": "<id:1>",
|
||||
"name": "Amlodipine",
|
||||
"dose": "5mg",
|
||||
"category": "BLOOD_PRESSURE",
|
||||
"categoryLabel": null,
|
||||
"compliance7": 67,
|
||||
"compliance30": 65,
|
||||
"streak": 4,
|
||||
"taken7": 4,
|
||||
"skipped7": 1,
|
||||
"missed7": 2
|
||||
}
|
||||
],
|
||||
"alerts": [
|
||||
{
|
||||
"level": "warning",
|
||||
"title": "Blood pressure often outside target range",
|
||||
"message": "Only 0% of your readings are within the target range."
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"title": "Low compliance: Amlodipine",
|
||||
"message": "Your 7-day compliance for Amlodipine is only 67%. Regular intake is important."
|
||||
}
|
||||
],
|
||||
"hasProvider": false,
|
||||
"dataSpanDays": 50,
|
||||
"totalMeasurements": 282,
|
||||
"revalidating": false,
|
||||
"ai": {
|
||||
"briefing": {
|
||||
"available": false,
|
||||
"reason": "no_provider",
|
||||
"onDeviceAllowed": true
|
||||
},
|
||||
"statusText": {
|
||||
"available": false,
|
||||
"reason": "no_provider",
|
||||
"onDeviceAllowed": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 200,
|
||||
"action": {
|
||||
"name": "insights.comprehensive"
|
||||
},
|
||||
"meta": {
|
||||
"cache.analytics.key_hash": 2016842294,
|
||||
"cache.analytics.outcome": "miss",
|
||||
"insights": {
|
||||
"slope_source": "rollup",
|
||||
"anomaly_source": "live",
|
||||
"regression_source": "rollup"
|
||||
},
|
||||
"insights.sub_bpRaw_ms": "<insights.sub_bpRaw_ms>",
|
||||
"insights.sub_buckets_ms": "<insights.sub_buckets_ms>",
|
||||
"insights.sub_coverage_ms": "<insights.sub_coverage_ms>",
|
||||
"insights.sub_firstAt_ms": "<insights.sub_firstAt_ms>",
|
||||
"insights.sub_latests_ms": "<insights.sub_latests_ms>",
|
||||
"insights.sub_narrow_ms": "<insights.sub_narrow_ms>",
|
||||
"medications": 1,
|
||||
"moodEntries": 29,
|
||||
"totalMeasurements": 282
|
||||
},
|
||||
"warnings": null,
|
||||
"error": null
|
||||
}
|
||||
],
|
||||
"effects": [
|
||||
{
|
||||
"effect": "ensureUserRollupsFresh",
|
||||
"args": [
|
||||
"<id:2>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "ensureUserMoodRollupsFresh",
|
||||
"args": [
|
||||
"<id:2>"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"status": 200,
|
||||
"body": {
|
||||
"data": {
|
||||
"summaries": {},
|
||||
"bmi": null,
|
||||
"bmiClassification": null,
|
||||
"bpClassification": null,
|
||||
"bpPctInTarget": null,
|
||||
"bpTargets": null,
|
||||
"weightBpCorrelation": null,
|
||||
"scatterData": [],
|
||||
"bpMedicationCorrelation": null,
|
||||
"bpMedicationScatterData": [],
|
||||
"moodSummary": null,
|
||||
"moodBpCorrelation": null,
|
||||
"moodBpScatterData": [],
|
||||
"moodWeightCorrelation": null,
|
||||
"moodWeightScatterData": [],
|
||||
"moodPulseCorrelation": null,
|
||||
"moodPulseScatterData": [],
|
||||
"medications": [],
|
||||
"alerts": [],
|
||||
"hasProvider": false,
|
||||
"dataSpanDays": 0,
|
||||
"totalMeasurements": 0,
|
||||
"revalidating": false,
|
||||
"ai": {
|
||||
"briefing": {
|
||||
"available": false,
|
||||
"reason": "no_provider",
|
||||
"onDeviceAllowed": true
|
||||
},
|
||||
"statusText": {
|
||||
"available": false,
|
||||
"reason": "no_provider",
|
||||
"onDeviceAllowed": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": null
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"status": 200,
|
||||
"action": {
|
||||
"name": "insights.comprehensive"
|
||||
},
|
||||
"meta": {
|
||||
"cache.analytics.key_hash": 461242359,
|
||||
"cache.analytics.outcome": "miss",
|
||||
"insights.sub_bpRaw_ms": "<insights.sub_bpRaw_ms>",
|
||||
"insights.sub_buckets_ms": "<insights.sub_buckets_ms>",
|
||||
"insights.sub_coverage_ms": "<insights.sub_coverage_ms>",
|
||||
"insights.sub_heavy_ms": "<insights.sub_heavy_ms>",
|
||||
"insights.sub_latests_ms": "<insights.sub_latests_ms>",
|
||||
"medications": 0,
|
||||
"moodEntries": 0,
|
||||
"totalMeasurements": 0
|
||||
},
|
||||
"warnings": null,
|
||||
"error": null
|
||||
}
|
||||
],
|
||||
"effects": [
|
||||
{
|
||||
"effect": "ensureUserRollupsFresh",
|
||||
"args": [
|
||||
"<id:1>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"effect": "ensureUserMoodRollupsFresh",
|
||||
"args": [
|
||||
"<id:1>"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
/**
|
||||
* Parity for `GET /api/insights/comprehensive` while the overview builder
|
||||
* moves out of the route into `src/lib/insights/`.
|
||||
*
|
||||
* One account with a full record (see `record-fixture.ts`) walks through the
|
||||
* cases the route tells apart, in order, each request seeing the cache the
|
||||
* earlier ones filled: an anonymous call, the overview cold, warm, and stale
|
||||
* after a measurement write (served while one rebuild runs behind it), the
|
||||
* `ai` block after the AI analysis opt-out and after the operator's master
|
||||
* switch (both off the cached body), a delegate with MANAGE access reading
|
||||
* the record cold, one with READ access refused, and the shared analytics
|
||||
* read ceiling. A second account in a zone west of UTC carries the sparse
|
||||
* shapes: mood entries with no rollup tier behind them, pulse without a
|
||||
* resting series, a medication with no schedule, an as-needed one, and no
|
||||
* height. An account with no data closes the walk.
|
||||
*
|
||||
* The clock is pinned (`Date` only) to noon of the day the test runs. The
|
||||
* aggregator cuts its 7-, 30- and 60-day windows in SQL against the
|
||||
* database clock, which the pin cannot reach, so the record's measurements
|
||||
* on exactly those days back are removed: a reading there would fall in or
|
||||
* out of a window with the hour the test runs. Every instant and day key in
|
||||
* the snapshot is written relative to the start of the day.
|
||||
*
|
||||
* The rollup refreshes the route starts and does not await are recorded
|
||||
* instead of run; the record's rollups are built once before the walk.
|
||||
*/
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { GET } from "@/app/api/insights/comprehensive/route";
|
||||
import { __resetAllCachesForTests } from "@/lib/cache/server-cache";
|
||||
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
|
||||
import { settleBackgroundTasks } from "@/lib/logging/background-tasks";
|
||||
|
||||
import { anonymous, callRoute, type RouteAuth } from "../../../kit/call-route";
|
||||
import {
|
||||
makeGrant,
|
||||
makeSession,
|
||||
makeUser,
|
||||
type SessionFixture,
|
||||
} from "../../../kit/factories";
|
||||
import {
|
||||
effectRecorder,
|
||||
eventFacts,
|
||||
expectParity,
|
||||
relativeToAnchor,
|
||||
stable,
|
||||
startOfUtcToday,
|
||||
wideEventRecorder,
|
||||
} from "../../../kit/parity";
|
||||
import {
|
||||
getPrismaClient,
|
||||
switchSessionTo,
|
||||
truncateAllTables,
|
||||
} from "../../setup";
|
||||
import { parityId, seedParityRecord } from "./record-fixture";
|
||||
|
||||
vi.mock("@/lib/logging/transports", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("@/lib/logging/transports")>();
|
||||
const { wideEventRecorder: recorder } = await import("../../../kit/parity");
|
||||
return {
|
||||
...actual,
|
||||
emitIfSampled: (event: (typeof recorder.events)[number]) => {
|
||||
recorder.events.push(event);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/rollups/measurement-rollups", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("@/lib/rollups/measurement-rollups")>();
|
||||
const { effectRecorder } = await import("../../../kit/parity");
|
||||
return {
|
||||
...actual,
|
||||
ensureUserRollupsFresh: async (...args: unknown[]) => {
|
||||
effectRecorder.record("ensureUserRollupsFresh", args);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/rollups/mood-rollups", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("@/lib/rollups/mood-rollups")>();
|
||||
const { effectRecorder } = await import("../../../kit/parity");
|
||||
return {
|
||||
...actual,
|
||||
ensureUserMoodRollupsFresh: async (...args: unknown[]) => {
|
||||
effectRecorder.record("ensureUserMoodRollupsFresh", args);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const prisma = getPrismaClient();
|
||||
const DAY = 86_400_000;
|
||||
const ANCHOR = startOfUtcToday();
|
||||
const NOW = new Date(ANCHOR.getTime() + 12 * 60 * 60 * 1000);
|
||||
/** Days back on which a SQL window edge falls (see the header). */
|
||||
const EDGE_DAYS = [7, 30, 60];
|
||||
|
||||
const OWNER_ID = parityId("insightscomp", 1);
|
||||
const SPARSE_ID = parityId("insightscomp", 2);
|
||||
const EMPTY_ID = parityId("insightscomp", 3);
|
||||
const MANAGER_ID = parityId("insightscomp", 4);
|
||||
const READER_ID = parityId("insightscomp", 5);
|
||||
|
||||
let owner: SessionFixture;
|
||||
let sparse: RouteAuth;
|
||||
let empty: RouteAuth;
|
||||
let manager: SessionFixture;
|
||||
let reader: SessionFixture;
|
||||
|
||||
/**
|
||||
* Keys of the aggregator's per-phase timings, which the clock and the load
|
||||
* decide. Their presence is kept, their value is not.
|
||||
*/
|
||||
function timingKeys(value: unknown, out = new Set<string>()): Set<string> {
|
||||
if (Array.isArray(value)) value.forEach((v) => timingKeys(v, out));
|
||||
else if (value && typeof value === "object") {
|
||||
for (const [k, v] of Object.entries(value)) {
|
||||
if (/_ms$/.test(k)) out.add(k);
|
||||
timingKeys(v, out);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* A stale serve starts one rebuild and returns without waiting for it. The
|
||||
* rebuild annotates the request's event too, and whether it gets there before
|
||||
* the event is emitted is a race between two sets of queries. On that case
|
||||
* only the cache outcome is kept, which the route decides alone.
|
||||
*/
|
||||
function cacheFactsOnly(facts: unknown): unknown {
|
||||
const f = facts as { meta?: Record<string, unknown> | null };
|
||||
const meta = Object.fromEntries(
|
||||
Object.entries(f.meta ?? {}).filter(([k]) => k.startsWith("cache.")),
|
||||
);
|
||||
return { ...f, action: "<raced>", meta };
|
||||
}
|
||||
|
||||
/**
|
||||
* The aggregator's sub-queries run in parallel and annotate as each one
|
||||
* finishes, so the order of their keys in the event follows the database, not
|
||||
* the code. The keys are sorted; what they hold is unchanged.
|
||||
*/
|
||||
function sortedMeta(facts: unknown): unknown {
|
||||
const f = facts as { meta?: Record<string, unknown> | null };
|
||||
if (!f.meta) return facts;
|
||||
const meta = Object.fromEntries(
|
||||
Object.entries(f.meta).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
||||
);
|
||||
return { ...f, meta };
|
||||
}
|
||||
|
||||
async function step(
|
||||
name: string,
|
||||
options: { auth?: RouteAuth; raced?: boolean } = {},
|
||||
): Promise<void> {
|
||||
wideEventRecorder.take();
|
||||
effectRecorder.take();
|
||||
const res = await callRoute(GET, {
|
||||
method: "GET",
|
||||
route: "/api/insights/comprehensive",
|
||||
auth: options.auth ?? owner,
|
||||
});
|
||||
// A stale serve leaves one rebuild running; it finishes before the
|
||||
// snapshot so the next case starts from the same cache.
|
||||
await settleBackgroundTasks();
|
||||
const events = wideEventRecorder.take();
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
const facts = events
|
||||
.map(eventFacts)
|
||||
.map(sortedMeta)
|
||||
.map((f) => (options.raced ? cacheFactsOnly(f) : f));
|
||||
await expectParity(
|
||||
`insights-comprehensive.${name}`,
|
||||
stable(
|
||||
relativeToAnchor(
|
||||
{
|
||||
status: res.status,
|
||||
body: res.body,
|
||||
events: facts,
|
||||
effects: effectRecorder.take(),
|
||||
},
|
||||
ANCHOR,
|
||||
),
|
||||
{ since: NOW, volatileKeys: [...timingKeys(facts)] },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** Remove the record's measurements on the SQL window edge days. */
|
||||
async function dropEdgeDays(userId: string): Promise<void> {
|
||||
for (const d of EDGE_DAYS) {
|
||||
await prisma.measurement.deleteMany({
|
||||
where: {
|
||||
userId,
|
||||
measuredAt: {
|
||||
gte: new Date(ANCHOR.getTime() - d * DAY),
|
||||
lt: new Date(ANCHOR.getTime() - (d - 1) * DAY),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Mood without rollups, pulse without a resting series, odd medications. */
|
||||
async function seedSparseRecord(userId: string): Promise<void> {
|
||||
const at = (d: number, hh: number, mm = 0) =>
|
||||
new Date(ANCHOR.getTime() - d * DAY + (hh * 60 + mm) * 60_000);
|
||||
const rows = [];
|
||||
for (let d = 1; d < 50; d++) {
|
||||
if (EDGE_DAYS.includes(d)) continue;
|
||||
rows.push(
|
||||
{
|
||||
userId,
|
||||
type: "WEIGHT" as const,
|
||||
value: 70 + (d % 5) * 0.3,
|
||||
unit: "kg",
|
||||
measuredAt: at(d, 3, 30),
|
||||
},
|
||||
{
|
||||
userId,
|
||||
type: "BLOOD_PRESSURE_SYS" as const,
|
||||
// Higher on the days the morning dose was skipped, so the
|
||||
// continuity correlation has something to find.
|
||||
value: 131 + ((d * 3) % 11) + (d % 5 === 0 ? 22 : 0),
|
||||
unit: "mmHg",
|
||||
measuredAt: at(d, 4, 10),
|
||||
},
|
||||
{
|
||||
userId,
|
||||
type: "BLOOD_PRESSURE_DIA" as const,
|
||||
value: 84 + ((d * 2) % 7),
|
||||
unit: "mmHg",
|
||||
measuredAt: at(d, 4, 10),
|
||||
},
|
||||
{
|
||||
userId,
|
||||
type: "PULSE" as const,
|
||||
value: 66 + (d % 9),
|
||||
unit: "bpm",
|
||||
measuredAt: at(d, 4, 0),
|
||||
},
|
||||
{
|
||||
userId,
|
||||
type: "PULSE" as const,
|
||||
value: 92 + (d % 5),
|
||||
unit: "bpm",
|
||||
measuredAt: at(d, 18, 0),
|
||||
},
|
||||
{
|
||||
userId,
|
||||
type: "PULSE" as const,
|
||||
value: 78 + (d % 7),
|
||||
unit: "bpm",
|
||||
measuredAt: at(d, 15, 0),
|
||||
},
|
||||
);
|
||||
}
|
||||
await prisma.measurement.createMany({ data: rows });
|
||||
const moods = ["LAUSIG", "SCHLECHT", "OKAY", "GUT", "SUPER_GUT"];
|
||||
const moodRows = [];
|
||||
for (let d = 1; d < 30; d++) {
|
||||
const loggedAt = at(d, 2, 15);
|
||||
const score = 1 + ((d * 2) % 5);
|
||||
moodRows.push({
|
||||
userId,
|
||||
// 02:15 UTC is the evening before in Bogotá.
|
||||
date: new Date(loggedAt.getTime() - 5 * 60 * 60 * 1000)
|
||||
.toISOString()
|
||||
.slice(0, 10),
|
||||
mood: moods[score - 1],
|
||||
score,
|
||||
moodLoggedAt: loggedAt,
|
||||
tz: "America/Bogota",
|
||||
createdAt: loggedAt,
|
||||
});
|
||||
}
|
||||
await prisma.moodEntry.createMany({ data: moodRows });
|
||||
const created = new Date(ANCHOR.getTime() - 60 * DAY);
|
||||
await prisma.medication.create({
|
||||
data: {
|
||||
id: parityId("insightscompmed", 1),
|
||||
userId,
|
||||
name: "Amlodipine",
|
||||
dose: "5mg",
|
||||
createdAt: created,
|
||||
schedules: {
|
||||
create: [
|
||||
{
|
||||
id: parityId("insightscompsch", 1),
|
||||
windowStart: "07:00",
|
||||
windowEnd: "09:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
categoryAssignment: { create: { category: "BLOOD_PRESSURE" } },
|
||||
},
|
||||
});
|
||||
await prisma.medication.create({
|
||||
data: {
|
||||
id: parityId("insightscompmed", 2),
|
||||
userId,
|
||||
name: "No schedule",
|
||||
dose: "1 tab",
|
||||
createdAt: created,
|
||||
},
|
||||
});
|
||||
await prisma.medication.create({
|
||||
data: {
|
||||
id: parityId("insightscompmed", 3),
|
||||
userId,
|
||||
name: "Ibuprofen",
|
||||
dose: "400mg",
|
||||
asNeeded: true,
|
||||
createdAt: created,
|
||||
},
|
||||
});
|
||||
const intakes = [];
|
||||
for (let d = 1; d <= 40; d++) {
|
||||
if (d % 4 === 0 || EDGE_DAYS.includes(d)) continue;
|
||||
intakes.push({
|
||||
id: parityId("insightscompie", d),
|
||||
userId,
|
||||
medicationId: parityId("insightscompmed", 1),
|
||||
scheduledFor: at(d, 12, 0),
|
||||
takenAt: d % 5 === 0 ? null : at(d, 12, 30),
|
||||
skipped: d % 5 === 0,
|
||||
createdAt: at(d, 12, 0),
|
||||
});
|
||||
}
|
||||
await prisma.medicationIntakeEvent.createMany({ data: intakes });
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
vi.useFakeTimers({ toFake: ["Date"], now: NOW });
|
||||
await truncateAllTables(prisma);
|
||||
__resetAllCachesForTests();
|
||||
const ownerRow = await makeUser({
|
||||
id: OWNER_ID,
|
||||
timezone: "UTC",
|
||||
locale: "en",
|
||||
heightCm: 178,
|
||||
dateOfBirth: new Date("1975-04-02T00:00:00.000Z"),
|
||||
gender: "male",
|
||||
aiProvider: "ANTHROPIC",
|
||||
aiModel: "claude-sonnet-4-6",
|
||||
// Presence only: the overview never decrypts a key.
|
||||
aiAnthropicKeyEncrypted: "v1:presence-only",
|
||||
});
|
||||
await prisma.consentReceipt.create({
|
||||
data: {
|
||||
userId: OWNER_ID,
|
||||
kind: "ai_full",
|
||||
artefact: "test",
|
||||
signedAt: NOW,
|
||||
},
|
||||
});
|
||||
await seedParityRecord({ userId: OWNER_ID, now: NOW, tz: "UTC", tag: "ic" });
|
||||
await dropEdgeDays(OWNER_ID);
|
||||
const rollups = await vi.importActual<
|
||||
typeof import("@/lib/rollups/measurement-rollups")
|
||||
>("@/lib/rollups/measurement-rollups");
|
||||
await rollups.ensureUserRollupsFresh(OWNER_ID);
|
||||
const moodRollups = await vi.importActual<
|
||||
typeof import("@/lib/rollups/mood-rollups")
|
||||
>("@/lib/rollups/mood-rollups");
|
||||
await moodRollups.ensureUserMoodRollupsFresh(OWNER_ID);
|
||||
await settleBackgroundTasks();
|
||||
owner = await makeSession(ownerRow);
|
||||
|
||||
const sparseRow = await makeUser({
|
||||
id: SPARSE_ID,
|
||||
timezone: "America/Bogota",
|
||||
locale: "de",
|
||||
dateOfBirth: new Date("1950-08-20T00:00:00.000Z"),
|
||||
});
|
||||
await seedSparseRecord(SPARSE_ID);
|
||||
// Measurement rollups only: the mood tier stays empty on purpose.
|
||||
await rollups.ensureUserRollupsFresh(SPARSE_ID);
|
||||
await settleBackgroundTasks();
|
||||
sparse = await makeSession(sparseRow);
|
||||
|
||||
const emptyRow = await makeUser({ id: EMPTY_ID, timezone: "UTC" });
|
||||
empty = await makeSession(emptyRow);
|
||||
|
||||
const managerRow = await makeUser({ id: MANAGER_ID, timezone: "UTC" });
|
||||
await makeGrant(ownerRow, managerRow, { access: "MANAGE" });
|
||||
manager = await makeSession(managerRow);
|
||||
await switchSessionTo(manager.sessionId, OWNER_ID);
|
||||
|
||||
const readerRow = await makeUser({ id: READER_ID, timezone: "UTC" });
|
||||
await makeGrant(ownerRow, readerRow, { access: "READ" });
|
||||
reader = await makeSession(readerRow);
|
||||
await switchSessionTo(reader.sessionId, OWNER_ID);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("GET /api/insights/comprehensive parity", () => {
|
||||
it("01 an anonymous call", async () => {
|
||||
await step("01-anonymous", { auth: anonymous });
|
||||
});
|
||||
|
||||
it("02 the overview, cold", async () => {
|
||||
await step("02-cold");
|
||||
});
|
||||
|
||||
it("03 the overview, warm", async () => {
|
||||
await step("03-warm");
|
||||
});
|
||||
|
||||
it("04 the overview, stale after a measurement write", async () => {
|
||||
invalidateUserMeasurements(OWNER_ID);
|
||||
await step("04-stale", { raced: true });
|
||||
});
|
||||
|
||||
it("05 the ai block after the AI analysis opt-out", async () => {
|
||||
await prisma.user.update({
|
||||
where: { id: OWNER_ID },
|
||||
data: { modulePreferencesJson: { insights: false } },
|
||||
});
|
||||
await step("05-ai-analysis-off");
|
||||
await prisma.user.update({
|
||||
where: { id: OWNER_ID },
|
||||
data: { modulePreferencesJson: {} },
|
||||
});
|
||||
});
|
||||
|
||||
it("06 the ai block with the operator's master switch off", async () => {
|
||||
await prisma.appSettings.upsert({
|
||||
where: { id: "singleton" },
|
||||
create: { id: "singleton", assistantEnabled: false },
|
||||
update: { assistantEnabled: false },
|
||||
});
|
||||
await step("06-operator-off");
|
||||
await prisma.appSettings.update({
|
||||
where: { id: "singleton" },
|
||||
data: { assistantEnabled: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("07 a delegate with MANAGE access, cold", async () => {
|
||||
__resetAllCachesForTests();
|
||||
await step("07-manage-delegate", { auth: manager });
|
||||
});
|
||||
|
||||
it("08 a delegate with READ access", async () => {
|
||||
await step("08-read-delegate", { auth: reader });
|
||||
});
|
||||
|
||||
it("09 the shared analytics read ceiling", async () => {
|
||||
await prisma.$executeRaw`
|
||||
INSERT INTO rate_limits (key, count, reset_at)
|
||||
VALUES (${`analytics-read:${OWNER_ID}`}, 100000, (NOW() AT TIME ZONE 'UTC') + INTERVAL '1 hour')
|
||||
ON CONFLICT (key) DO UPDATE SET count = 100000`;
|
||||
await step("09-rate-limited");
|
||||
await prisma.$executeRaw`DELETE FROM rate_limits WHERE key = ${`analytics-read:${OWNER_ID}`}`;
|
||||
});
|
||||
|
||||
it("10 a sparse record west of UTC", async () => {
|
||||
await step("10-sparse", { auth: sparse });
|
||||
});
|
||||
|
||||
it("11 an account with no data", async () => {
|
||||
await step("11-empty", { auth: empty });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user