@peopl-health/nexus 6.0.0-dev.466 → 6.0.0-dev.611
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/adapters/messageAdapters/TwilioMessageAdapter.js +17 -3
- package/lib/clinical/AssistantProcessor.js +3 -0
- package/lib/clinical/config/divergenceConfig.js +63 -0
- package/lib/clinical/helpers/divergenceHelper.js +100 -0
- package/lib/clinical/helpers/seedHelper.js +62 -0
- package/lib/clinical/index.js +7 -0
- package/lib/clinical/memory/DefaultMemoryManager.js +13 -1
- package/lib/clinical/models/divergenceModel.js +41 -0
- package/lib/clinical/models/turnTraceModel.js +11 -0
- package/lib/clinical/providers/BaseLLMProvider.js +16 -0
- package/lib/clinical/services/clinicalAgentService.js +18 -0
- package/lib/clinical/services/divergenceService.js +74 -0
- package/lib/clinical/services/promptComposerService.js +2 -1
- package/lib/clinical/services/seedService.js +21 -0
- package/lib/clinical/services/shadowMessageService.js +58 -0
- package/lib/clinical/services/shadowService.js +11 -2
- package/lib/clinical/stores/CtcaeCatalog.js +67 -0
- package/lib/controllers/conversationController.js +3 -1
- package/lib/controllers/templateController.js +27 -12
- package/lib/core/BatchingManager.js +2 -2
- package/lib/eval/EvalProvider.js +4 -3
- package/lib/fhir/config/catalogConfig.js +46 -0
- package/lib/fhir/config/connection.js +11 -0
- package/lib/fhir/config/fhirConfig.js +41 -0
- package/lib/fhir/constants/projectionSlugs.js +3 -0
- package/lib/fhir/helpers/elementHelper.js +33 -0
- package/lib/fhir/index.js +21 -0
- package/lib/fhir/models/envelopeModel.js +129 -0
- package/lib/fhir/models/historyModel.js +19 -0
- package/lib/fhir/projections/clinicalMentionProjection.js +65 -0
- package/lib/fhir/projections/registerProjectors.js +14 -0
- package/lib/fhir/resources/Appointment.js +28 -0
- package/lib/fhir/resources/Condition.js +34 -0
- package/lib/fhir/resources/MedicationStatement.js +42 -0
- package/lib/fhir/resources/Observation.js +79 -0
- package/lib/fhir/resources/Procedure.js +30 -0
- package/lib/fhir/resources/Provenance.js +28 -0
- package/lib/fhir/services/clinicalMentionService.js +18 -0
- package/lib/fhir/services/fhirService.js +31 -0
- package/lib/fhir/stores/fhirStore.js +187 -0
- package/lib/helpers/deliveryAttemptHelper.js +5 -13
- package/lib/helpers/projectorHelper.js +18 -0
- package/lib/helpers/twilioMediaHelper.js +25 -1
- package/lib/index.js +14 -0
- package/lib/services/airtableService.js +40 -14
- package/lib/shared/dtos/BaseDto.js +13 -0
- package/lib/shared/dtos/ClinicalFact.js +85 -0
- package/lib/shared/dtos/ClinicalMention.js +53 -0
- package/lib/shared/dtos/ClusterHistoryRecord.js +21 -0
- package/lib/shared/dtos/ClusterImpression.js +40 -0
- package/lib/shared/dtos/ContingencySafetyNet.js +47 -0
- package/lib/shared/dtos/DispatchedEscalation.js +44 -0
- package/lib/shared/dtos/Intervention.js +29 -0
- package/lib/shared/dtos/ManagedSymptom.js +75 -0
- package/lib/shared/dtos/MonitoringEpisode.js +25 -0
- package/lib/shared/dtos/PatientContext.js +22 -0
- package/lib/shared/dtos/PatientRiskAssessment.js +46 -0
- package/lib/shared/dtos/Reminder.js +32 -0
- package/lib/shared/dtos/RoutingDecision.js +68 -0
- package/lib/shared/dtos/SymptomEpisode.js +41 -0
- package/lib/shared/dtos/TreatmentEpisode.js +31 -0
- package/lib/utils/jsonUtils.js +34 -1
- package/lib/utils/vcardParser.js +41 -0
- package/package.json +7 -2
- package/lib/clinical/config/fhirConfig.js +0 -49
- package/lib/clinical/services/fhirService.js +0 -24
- /package/lib/{clinical → fhir}/helpers/fhirHelper.js +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
const dateOrDateTime = z.union([z.iso.date(), dateTime]);
|
|
7
|
+
|
|
8
|
+
const interpretationSchema = z.strictObject({
|
|
9
|
+
negated: z.boolean().default(false),
|
|
10
|
+
hedged: z.boolean().default(false),
|
|
11
|
+
experiencer: z.enum(['patient', 'family', 'other']).default('patient'),
|
|
12
|
+
temporal: z.enum(['current', 'historical', 'hypothetical']).default('current'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const baseFields = {
|
|
16
|
+
id: z.string(),
|
|
17
|
+
code: z.string(),
|
|
18
|
+
effective: dateTime,
|
|
19
|
+
category: z.string(),
|
|
20
|
+
interpretation: interpretationSchema.default(() => interpretationSchema.parse({})),
|
|
21
|
+
notes: z.array(z.string()).default([]),
|
|
22
|
+
confidence: z.number().nullable().default(null),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const observationFact = z.strictObject({
|
|
26
|
+
...baseFields,
|
|
27
|
+
kind: z.literal('observation'),
|
|
28
|
+
codeCtcae: z.string().nullable().default(null),
|
|
29
|
+
value: z.record(z.string(), z.unknown()).default({}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const medicationFact = z.strictObject({
|
|
33
|
+
...baseFields,
|
|
34
|
+
kind: z.literal('medication'),
|
|
35
|
+
dose: z.string().nullable().default(null),
|
|
36
|
+
route: z.string().nullable().default(null),
|
|
37
|
+
frequency: z.string().nullable().default(null),
|
|
38
|
+
adherenceStatus: z.enum([
|
|
39
|
+
'started', 'ongoing', 'completed', 'stopped',
|
|
40
|
+
'skipping_doses', 'non_adherent', 'as_needed', 'unknown',
|
|
41
|
+
]).default('unknown'),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const conditionFact = z.strictObject({
|
|
45
|
+
...baseFields,
|
|
46
|
+
kind: z.literal('condition'),
|
|
47
|
+
clinicalStatus: z.enum([
|
|
48
|
+
'active', 'recurrence', 'relapse', 'inactive', 'remission', 'resolved', 'unknown',
|
|
49
|
+
]).default('unknown'),
|
|
50
|
+
onset: dateOrDateTime.nullable().default(null),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const procedureFact = z.strictObject({
|
|
54
|
+
...baseFields,
|
|
55
|
+
kind: z.literal('procedure'),
|
|
56
|
+
venue: z.string().nullable().default(null),
|
|
57
|
+
outcome: z.string().nullable().default(null),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const appointmentFact = z.strictObject({
|
|
61
|
+
...baseFields,
|
|
62
|
+
kind: z.literal('appointment'),
|
|
63
|
+
scheduledFor: dateOrDateTime.nullable().default(null),
|
|
64
|
+
purpose: z.string().nullable().default(null),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const allergyFact = z.strictObject({
|
|
68
|
+
...baseFields,
|
|
69
|
+
kind: z.literal('allergy'),
|
|
70
|
+
clinicalStatus: z.enum(['active', 'inactive', 'resolved', 'unknown']).default('active'),
|
|
71
|
+
verificationStatus: z.enum(['unconfirmed', 'presumed', 'confirmed', 'refuted', 'unknown']).default('unconfirmed'),
|
|
72
|
+
allergenCategory: z.enum(['food', 'medication', 'environment', 'biologic', 'unknown']).default('unknown'),
|
|
73
|
+
criticality: z.enum(['low', 'high', 'unable_to_assess', 'unknown']).default('unknown'),
|
|
74
|
+
reaction: z.string().nullable().default(null),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const schema = z.discriminatedUnion('kind', [
|
|
78
|
+
observationFact, medicationFact, conditionFact, procedureFact, appointmentFact, allergyFact,
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
class ClinicalFact extends BaseDto {}
|
|
82
|
+
|
|
83
|
+
ClinicalFact.schema = schema;
|
|
84
|
+
|
|
85
|
+
module.exports = { ClinicalFact };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const CATEGORY = [
|
|
8
|
+
'symptom', 'emotion', 'lab_value', 'medication', 'adherence',
|
|
9
|
+
'treatment', 'appointment', 'indication', 'diagnosis',
|
|
10
|
+
'allergy', 'comorbidity', 'performance_status',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const assertionSchema = z.object({
|
|
14
|
+
negated: z.boolean().default(false),
|
|
15
|
+
hedged: z.boolean().default(false),
|
|
16
|
+
experiencer: z.enum(['patient', 'family', 'other']).default('patient'),
|
|
17
|
+
temporal: z.enum(['current', 'historical', 'hypothetical']).default('current'),
|
|
18
|
+
confidence: z.number().default(1),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const factSchema = z.object({
|
|
22
|
+
mentionId: z.string(),
|
|
23
|
+
patientId: z.string(),
|
|
24
|
+
category: z.enum(CATEGORY),
|
|
25
|
+
canonicalTerm: z.string(),
|
|
26
|
+
reportedAt: dateTime,
|
|
27
|
+
assertion: assertionSchema.default(() => assertionSchema.parse({})),
|
|
28
|
+
ctcaeTerm: z.string().nullable().default(null),
|
|
29
|
+
temporality: z.string().nullable().default(null),
|
|
30
|
+
observedFields: z.record(z.string(), z.unknown()).default({}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const provenanceSchema = z.object({
|
|
34
|
+
mentionId: z.string(), // mentionId/patientId also on fact; repeated for FHIR compatibility (separate resources)
|
|
35
|
+
patientId: z.string(),
|
|
36
|
+
turnId: z.string(),
|
|
37
|
+
recordedAt: dateTime,
|
|
38
|
+
source: z.string(),
|
|
39
|
+
verbatimQuotes: z.array(z.string()).default([]),
|
|
40
|
+
extractorConfidence: z.number().default(1),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const schema = z.object({
|
|
44
|
+
schemaVersion: z.string().default('1'),
|
|
45
|
+
fact: factSchema,
|
|
46
|
+
provenance: provenanceSchema,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
class ClinicalMention extends BaseDto {}
|
|
50
|
+
|
|
51
|
+
ClinicalMention.schema = schema;
|
|
52
|
+
|
|
53
|
+
module.exports = { ClinicalMention };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
clusterId: z.string(),
|
|
10
|
+
turnId: z.string(),
|
|
11
|
+
action: z.string(),
|
|
12
|
+
changes: z.record(z.string(), z.unknown()).default({}),
|
|
13
|
+
transitionReason: z.string().nullable().default(null),
|
|
14
|
+
recordedAt: dateTime,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
class ClusterHistoryRecord extends BaseDto {}
|
|
18
|
+
|
|
19
|
+
ClusterHistoryRecord.schema = schema;
|
|
20
|
+
|
|
21
|
+
module.exports = { ClusterHistoryRecord };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
clusterId: z.string(),
|
|
10
|
+
patientId: z.string(),
|
|
11
|
+
label: z.string(),
|
|
12
|
+
hypothesisType: z.enum(['ae_profile', 'symptom_syndrome', 'disease_complication', 'relational_causal']),
|
|
13
|
+
status: z.enum(['active', 'dissolved']).default('active'),
|
|
14
|
+
confidence: z.enum(['speculative', 'moderate', 'strong']),
|
|
15
|
+
clinicalSignificance: z.enum(['benign', 'moderate', 'safety_relevant', 'emergency']),
|
|
16
|
+
memberCaseIds: z.array(z.string()).min(1),
|
|
17
|
+
consultationId: z.string().nullable().default(null),
|
|
18
|
+
relationshipModel: z.string(),
|
|
19
|
+
strengthensIf: z.array(z.string()).default([]),
|
|
20
|
+
weakensIf: z.array(z.string()).default([]),
|
|
21
|
+
openingReasoning: z.string(),
|
|
22
|
+
openedAt: dateTime,
|
|
23
|
+
dissolvedAt: dateTime.nullable().default(null),
|
|
24
|
+
dissolveReason: z.string().nullable().default(null),
|
|
25
|
+
dissolveCategory: z.enum([
|
|
26
|
+
'linked_case_closed', 'confidence_dropped', 'superseded_by_other_cluster', 'evidence_insufficient', 'other',
|
|
27
|
+
]).nullable().default(null),
|
|
28
|
+
})
|
|
29
|
+
.refine((d) => (!d.dissolveReason && !d.dissolveCategory) || d.dissolvedAt !== null, {
|
|
30
|
+
message: 'dissolveReason/dissolveCategory require dissolvedAt',
|
|
31
|
+
})
|
|
32
|
+
.refine((d) => (d.status === 'dissolved') === (d.dissolvedAt !== null), {
|
|
33
|
+
message: 'status is dissolved iff dissolvedAt is set',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
class ClusterImpression extends BaseDto {}
|
|
37
|
+
|
|
38
|
+
ClusterImpression.schema = schema;
|
|
39
|
+
|
|
40
|
+
module.exports = { ClusterImpression };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const escalationStepSchema = z.strictObject({
|
|
8
|
+
recipient: z.enum(['patient', 'care_team']),
|
|
9
|
+
message: z.string(),
|
|
10
|
+
priority: z.enum(['critical', 'high', 'medium', 'low', 'routine', 'urgent', 'asap', 'stat']).nullable().default(null),
|
|
11
|
+
actionRequested: z.string().nullable().default(null),
|
|
12
|
+
firesAfterSilenceMinutes: z.number().int().nonnegative().nullable().default(null),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const carePlanSchema = z.strictObject({
|
|
16
|
+
planId: z.string(),
|
|
17
|
+
patientId: z.string(),
|
|
18
|
+
relatedCaseId: z.string().nullable().default(null),
|
|
19
|
+
relatedClusterId: z.string().nullable().default(null),
|
|
20
|
+
concern: z.string(),
|
|
21
|
+
awaitingCharacterization: z.string(),
|
|
22
|
+
whyThisMatters: z.string(),
|
|
23
|
+
creationReasoning: z.string(),
|
|
24
|
+
status: z.enum(['armed', 'fired_reminder', 'fired_team', 'auto_resolved', 'cancelled']).default('armed'),
|
|
25
|
+
createdAt: dateTime,
|
|
26
|
+
resolvedAt: dateTime.nullable().default(null),
|
|
27
|
+
resolvedReason: z.string().nullable().default(null),
|
|
28
|
+
})
|
|
29
|
+
.refine((c) => !['auto_resolved', 'cancelled'].includes(c.status) || c.resolvedAt !== null, {
|
|
30
|
+
message: 'a resolved/cancelled carePlan requires resolvedAt',
|
|
31
|
+
})
|
|
32
|
+
.refine((c) => !c.resolvedReason || c.resolvedAt !== null, {
|
|
33
|
+
message: 'resolvedReason requires resolvedAt',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const schema = z.strictObject({
|
|
37
|
+
schemaVersion: z.string().default('1'),
|
|
38
|
+
carePlan: carePlanSchema,
|
|
39
|
+
teamEscalation: escalationStepSchema,
|
|
40
|
+
patientReminder: escalationStepSchema.nullable().default(null),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
class ContingencySafetyNet extends BaseDto {}
|
|
44
|
+
|
|
45
|
+
ContingencySafetyNet.schema = schema;
|
|
46
|
+
|
|
47
|
+
module.exports = { ContingencySafetyNet };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const baseFields = {
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
eventId: z.string(),
|
|
10
|
+
patientId: z.string(),
|
|
11
|
+
severity: z.enum(['critical', 'high', 'medium', 'low']),
|
|
12
|
+
createdAt: dateTime,
|
|
13
|
+
status: z.enum(['queued', 'sent', 'acknowledged', 'failed']).default('queued'),
|
|
14
|
+
reason: z.string().nullable().default(null),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const notifyEscalation = z.strictObject({
|
|
18
|
+
...baseFields,
|
|
19
|
+
kind: z.enum(['page_navigator', 'page_oncologist', 'notify_911_guidance']),
|
|
20
|
+
recipient: z.string().nullable().default(null),
|
|
21
|
+
requiresAck: z.boolean().default(false),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const followupEscalation = z.strictObject({
|
|
25
|
+
...baseFields,
|
|
26
|
+
kind: z.literal('schedule_followup'),
|
|
27
|
+
fireAt: dateTime,
|
|
28
|
+
followupPurpose: z.string().nullable().default(null),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const ticketEscalation = z.strictObject({
|
|
32
|
+
...baseFields,
|
|
33
|
+
kind: z.literal('open_ticket'),
|
|
34
|
+
title: z.string(),
|
|
35
|
+
slaMinutes: z.number().int().nullable().default(null),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const schema = z.discriminatedUnion('kind', [notifyEscalation, followupEscalation, ticketEscalation]);
|
|
39
|
+
|
|
40
|
+
class DispatchedEscalation extends BaseDto {}
|
|
41
|
+
|
|
42
|
+
DispatchedEscalation.schema = schema;
|
|
43
|
+
|
|
44
|
+
module.exports = { DispatchedEscalation };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
kind: z.enum(['otc', 'self_care', 'other', 'education', 'team_escalation', 'follow_up_check']),
|
|
10
|
+
interventionId: z.string(),
|
|
11
|
+
patientId: z.string(),
|
|
12
|
+
caseId: z.string(),
|
|
13
|
+
interventionLabel: z.string(),
|
|
14
|
+
rationale: z.string().nullable().default(null),
|
|
15
|
+
deliveredAt: dateTime,
|
|
16
|
+
outcome: z.enum(['pending', 'improved', 'no_relief', 'side_effects', 'worsened', 'unknown']).default('pending'),
|
|
17
|
+
outcomeNotes: z.string().nullable().default(null),
|
|
18
|
+
recipientKind: z.enum(['patient', 'related_person']).default('patient'),
|
|
19
|
+
recipientRefId: z.string().nullable().default(null),
|
|
20
|
+
})
|
|
21
|
+
.refine((d) => d.recipientKind !== 'related_person' || d.recipientRefId !== null, {
|
|
22
|
+
message: 'a related_person recipient requires recipientRefId',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
class Intervention extends BaseDto {}
|
|
26
|
+
|
|
27
|
+
Intervention.schema = schema;
|
|
28
|
+
|
|
29
|
+
module.exports = { Intervention };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const gradeEstimateSchema = z.strictObject({
|
|
8
|
+
bestEstimate: z.number().int().min(1).max(5).nullable().default(null),
|
|
9
|
+
confidence: z.enum(['low', 'moderate', 'high']).nullable().default(null),
|
|
10
|
+
possibleRange: z.tuple([z.number().int().min(1).max(5), z.number().int().min(1).max(5)]).nullable().default(null),
|
|
11
|
+
reasoning: z.string().default(''),
|
|
12
|
+
})
|
|
13
|
+
.refine((g) => g.possibleRange === null || g.possibleRange[0] <= g.possibleRange[1], {
|
|
14
|
+
message: 'possibleRange must be ordered [min, max]',
|
|
15
|
+
})
|
|
16
|
+
.refine(
|
|
17
|
+
(g) => g.bestEstimate === null || g.possibleRange === null
|
|
18
|
+
|| (g.bestEstimate >= g.possibleRange[0] && g.bestEstimate <= g.possibleRange[1]),
|
|
19
|
+
{ message: 'bestEstimate must fall within possibleRange' },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const symptomConditionSchema = z.strictObject({
|
|
23
|
+
conditionId: z.string(),
|
|
24
|
+
patientId: z.string(),
|
|
25
|
+
ctcaeTerm: z.string(),
|
|
26
|
+
clinicalStatus: z.enum(['characterizing', 'monitoring', 'resolved', 'inactive', 'closed']).default('characterizing'),
|
|
27
|
+
onsetAt: dateTime,
|
|
28
|
+
abatementAt: dateTime.nullable().default(null),
|
|
29
|
+
closeReason: z.string().nullable().default(null),
|
|
30
|
+
temporality: z.string().nullable().default(null),
|
|
31
|
+
evidenceEpisodeIds: z.array(z.string()).default([]),
|
|
32
|
+
currentEpisodeId: z.string().nullable().default(null),
|
|
33
|
+
evidenceMentionIds: z.array(z.string()).default([]),
|
|
34
|
+
lastTransitionReason: z.string().nullable().default(null),
|
|
35
|
+
recurrenceCount: z.number().int().nonnegative().default(0),
|
|
36
|
+
reactivatedAt: dateTime.nullable().default(null),
|
|
37
|
+
})
|
|
38
|
+
.refine((c) => !c.closeReason || c.abatementAt !== null, {
|
|
39
|
+
message: 'closeReason requires abatementAt',
|
|
40
|
+
})
|
|
41
|
+
.refine((c) => c.abatementAt === null || ['resolved', 'inactive', 'closed'].includes(c.clinicalStatus), {
|
|
42
|
+
message: 'abatementAt requires a terminal clinicalStatus (resolved/inactive/closed)',
|
|
43
|
+
})
|
|
44
|
+
.refine((c) => c.abatementAt === null || new Date(c.abatementAt) >= new Date(c.onsetAt), {
|
|
45
|
+
message: 'abatementAt must be >= onsetAt',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const symptomAssessmentSchema = z.strictObject({
|
|
49
|
+
assessmentId: z.string(),
|
|
50
|
+
conditionId: z.string(),
|
|
51
|
+
patientId: z.string(),
|
|
52
|
+
performedAt: dateTime,
|
|
53
|
+
previousId: z.string().nullable().default(null),
|
|
54
|
+
grade: gradeEstimateSchema,
|
|
55
|
+
trajectory: z.string().nullable().default(null),
|
|
56
|
+
decisionPosture: z.string().nullable().default(null),
|
|
57
|
+
gradeVerification: z.string().default('provisional'),
|
|
58
|
+
reasoning: z.string().nullable().default(null),
|
|
59
|
+
postureReason: z.string().nullable().default(null),
|
|
60
|
+
problemRepresentation: z.string().nullable().default(null),
|
|
61
|
+
gradeThresholdClause: z.string().nullable().default(null),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const schema = z.strictObject({
|
|
65
|
+
schemaVersion: z.string().default('1'),
|
|
66
|
+
condition: symptomConditionSchema,
|
|
67
|
+
latestAssessment: symptomAssessmentSchema,
|
|
68
|
+
priorAssessments: z.array(symptomAssessmentSchema).default([]),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
class ManagedSymptom extends BaseDto {}
|
|
72
|
+
|
|
73
|
+
ManagedSymptom.schema = schema;
|
|
74
|
+
|
|
75
|
+
module.exports = { ManagedSymptom };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
episodeId: z.string(),
|
|
10
|
+
patientId: z.string(),
|
|
11
|
+
managingOrgId: z.string(),
|
|
12
|
+
episodeType: z.literal('home-monitoring-programme').default('home-monitoring-programme'),
|
|
13
|
+
status: z.enum(['planned', 'waitlist', 'active', 'onhold', 'finished', 'cancelled', 'entered-in-error']),
|
|
14
|
+
periodStart: dateTime,
|
|
15
|
+
periodEnd: dateTime.nullable().default(null),
|
|
16
|
+
})
|
|
17
|
+
.refine((d) => d.periodEnd === null || new Date(d.periodEnd) >= new Date(d.periodStart), {
|
|
18
|
+
message: 'periodEnd must be >= periodStart',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
class MonitoringEpisode extends BaseDto {}
|
|
22
|
+
|
|
23
|
+
MonitoringEpisode.schema = schema;
|
|
24
|
+
|
|
25
|
+
module.exports = { MonitoringEpisode };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
patientId: z.string(),
|
|
10
|
+
assembledAt: dateTime,
|
|
11
|
+
activeRegimen: z.string().nullable().default(null),
|
|
12
|
+
activePhase: z.string().nullable().default(null),
|
|
13
|
+
profileMedications: z.array(z.string()).default([]),
|
|
14
|
+
profileAllergies: z.array(z.string()).default([]),
|
|
15
|
+
profileComorbidities: z.array(z.string()).default([]),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
class PatientContext extends BaseDto {}
|
|
19
|
+
|
|
20
|
+
PatientContext.schema = schema;
|
|
21
|
+
|
|
22
|
+
module.exports = { PatientContext };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const predictedAeSchema = z.strictObject({
|
|
8
|
+
aeName: z.string(),
|
|
9
|
+
severityIfConfirmed: z.enum(['emergency', 'safety_relevant', 'moderate']),
|
|
10
|
+
minimumSymptomSet: z.array(z.string()).default([]),
|
|
11
|
+
why: z.string().nullable().default(null),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const predictedDiseaseRiskSchema = z.strictObject({
|
|
15
|
+
bodySite: z.string(),
|
|
16
|
+
complications: z.array(z.string()).default([]),
|
|
17
|
+
warningSymptoms: z.array(z.string()).default([]),
|
|
18
|
+
amplifierComorbidities: z.array(z.string()).default([]),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const predictedComorbidityAmplifierSchema = z.strictObject({
|
|
22
|
+
comorbidity: z.string(),
|
|
23
|
+
interaction: z.string(),
|
|
24
|
+
clinicalNote: z.string().nullable().default(null),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const schema = z.strictObject({
|
|
28
|
+
schemaVersion: z.string().default('1'),
|
|
29
|
+
assessmentId: z.string(),
|
|
30
|
+
patientId: z.string(),
|
|
31
|
+
status: z.literal('final').default('final'),
|
|
32
|
+
sourceRegimen: z.string().nullable().default(null),
|
|
33
|
+
sourceDiagnosis: z.string().nullable().default(null),
|
|
34
|
+
sourcePhase: z.string().nullable().default(null),
|
|
35
|
+
predictedAes: z.array(predictedAeSchema).default([]),
|
|
36
|
+
diseaseRisks: z.array(predictedDiseaseRiskSchema).default([]),
|
|
37
|
+
comorbidityAmplifiers: z.array(predictedComorbidityAmplifierSchema).default([]),
|
|
38
|
+
assessedAt: dateTime,
|
|
39
|
+
assessedByModel: z.string().nullable().default(null),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
class PatientRiskAssessment extends BaseDto {}
|
|
43
|
+
|
|
44
|
+
PatientRiskAssessment.schema = schema;
|
|
45
|
+
|
|
46
|
+
module.exports = { PatientRiskAssessment };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
reminderId: z.string(),
|
|
10
|
+
patientId: z.string(),
|
|
11
|
+
kind: z.enum(['medication', 'appointment', 'exam', 'self_care_action', 'check_in', 'other']),
|
|
12
|
+
message: z.string(),
|
|
13
|
+
fireAt: dateTime,
|
|
14
|
+
channel: z.string().default('whatsapp'),
|
|
15
|
+
status: z.enum(['scheduled', 'cancelled', 'expired']).default('scheduled'),
|
|
16
|
+
authoredAt: dateTime,
|
|
17
|
+
cancelReason: z.string().nullable().default(null),
|
|
18
|
+
recipientKind: z.enum(['patient', 'related_person']).default('patient'),
|
|
19
|
+
recipientRefId: z.string().nullable().default(null),
|
|
20
|
+
})
|
|
21
|
+
.refine((d) => (d.status === 'cancelled') === (d.cancelReason !== null), {
|
|
22
|
+
message: 'cancelReason is set iff status is cancelled',
|
|
23
|
+
})
|
|
24
|
+
.refine((d) => d.recipientKind !== 'related_person' || d.recipientRefId !== null, {
|
|
25
|
+
message: 'a related_person recipient requires recipientRefId',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
class Reminder extends BaseDto {}
|
|
29
|
+
|
|
30
|
+
Reminder.schema = schema;
|
|
31
|
+
|
|
32
|
+
module.exports = { Reminder };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const mentionFindingSchema = z.strictObject({
|
|
8
|
+
caseId: z.string(),
|
|
9
|
+
clinicalReasoning: z.string(),
|
|
10
|
+
decisionAction: z.string(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const secondaryActionSchema = z.strictObject({
|
|
14
|
+
action: z.string(),
|
|
15
|
+
composeAs: z.string(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const deferredItemSchema = z.strictObject({
|
|
19
|
+
item: z.string(),
|
|
20
|
+
deferReason: z.string(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// patientId/turnId also on root — repeated per subrecord for FHIR compatibility (each projects a separate resource)
|
|
24
|
+
const assessmentSchema = z.strictObject({
|
|
25
|
+
assessmentId: z.string(),
|
|
26
|
+
patientId: z.string(),
|
|
27
|
+
turnId: z.string(),
|
|
28
|
+
performedAt: dateTime,
|
|
29
|
+
previousId: z.string().nullable().default(null),
|
|
30
|
+
summary: z.string(),
|
|
31
|
+
notes: z.array(z.string()).default([]),
|
|
32
|
+
findings: z.array(mentionFindingSchema).default([]),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const dispositionSchema = z.strictObject({
|
|
36
|
+
dispositionId: z.string(),
|
|
37
|
+
patientId: z.string(),
|
|
38
|
+
kind: z.string(),
|
|
39
|
+
primaryAction: z.string(),
|
|
40
|
+
primaryReason: z.string(),
|
|
41
|
+
primaryCaseId: z.string().nullable().default(null),
|
|
42
|
+
secondaryActions: z.array(secondaryActionSchema).default([]),
|
|
43
|
+
deferredItems: z.array(deferredItemSchema).default([]),
|
|
44
|
+
authoredAt: dateTime,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const provenanceSchema = z.strictObject({
|
|
48
|
+
agentDevice: z.string(),
|
|
49
|
+
onBehalfOf: z.string(),
|
|
50
|
+
recordedAt: dateTime,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const schema = z.strictObject({
|
|
54
|
+
schemaVersion: z.string().default('1'),
|
|
55
|
+
decisionId: z.string(),
|
|
56
|
+
patientId: z.string(),
|
|
57
|
+
turnId: z.string(),
|
|
58
|
+
committedAt: dateTime,
|
|
59
|
+
assessment: assessmentSchema,
|
|
60
|
+
disposition: dispositionSchema,
|
|
61
|
+
provenance: provenanceSchema,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
class RoutingDecision extends BaseDto {}
|
|
65
|
+
|
|
66
|
+
RoutingDecision.schema = schema;
|
|
67
|
+
|
|
68
|
+
module.exports = { RoutingDecision };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateTime = z.iso.datetime({ offset: true });
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
episodeId: z.string(),
|
|
10
|
+
patientId: z.string(),
|
|
11
|
+
canonicalTerm: z.string(),
|
|
12
|
+
openedAt: dateTime,
|
|
13
|
+
lastMentionAt: dateTime,
|
|
14
|
+
closedAt: dateTime.nullable().default(null),
|
|
15
|
+
closeReason: z.string().nullable().default(null),
|
|
16
|
+
evidenceMentionIds: z.array(z.string()).default([]),
|
|
17
|
+
firstIntensity: z.number().nullable().default(null),
|
|
18
|
+
lastIntensity: z.number().nullable().default(null),
|
|
19
|
+
maxIntensity: z.number().nullable().default(null),
|
|
20
|
+
})
|
|
21
|
+
.refine((d) => !d.closeReason || d.closedAt !== null, {
|
|
22
|
+
message: 'closeReason requires closedAt',
|
|
23
|
+
})
|
|
24
|
+
.refine(
|
|
25
|
+
(d) => d.maxIntensity === null
|
|
26
|
+
|| ((d.firstIntensity === null || d.maxIntensity >= d.firstIntensity)
|
|
27
|
+
&& (d.lastIntensity === null || d.maxIntensity >= d.lastIntensity)),
|
|
28
|
+
{ message: 'maxIntensity must be >= firstIntensity and lastIntensity' },
|
|
29
|
+
)
|
|
30
|
+
.refine((d) => new Date(d.lastMentionAt) >= new Date(d.openedAt), {
|
|
31
|
+
message: 'lastMentionAt must be >= openedAt',
|
|
32
|
+
})
|
|
33
|
+
.refine((d) => d.closedAt === null || new Date(d.closedAt) >= new Date(d.openedAt), {
|
|
34
|
+
message: 'closedAt must be >= openedAt',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
class SymptomEpisode extends BaseDto {}
|
|
38
|
+
|
|
39
|
+
SymptomEpisode.schema = schema;
|
|
40
|
+
|
|
41
|
+
module.exports = { SymptomEpisode };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { BaseDto } = require('./BaseDto');
|
|
4
|
+
|
|
5
|
+
const dateOnly = z.iso.date();
|
|
6
|
+
|
|
7
|
+
const schema = z.strictObject({
|
|
8
|
+
schemaVersion: z.string().default('1'),
|
|
9
|
+
episodeId: z.string(),
|
|
10
|
+
patientId: z.string(),
|
|
11
|
+
cancerConditionId: z.string(),
|
|
12
|
+
regimenName: z.string(),
|
|
13
|
+
phase: z.enum(['diagnosis', 'active_curative', 'active_palliative', 'remission', 'advanced']),
|
|
14
|
+
periodStart: dateOnly,
|
|
15
|
+
periodEnd: dateOnly.nullable().default(null),
|
|
16
|
+
currentCycle: z.number().int().nonnegative(),
|
|
17
|
+
currentWeek: z.number().int().nonnegative(),
|
|
18
|
+
plannedCycles: z.number().int().positive(),
|
|
19
|
+
})
|
|
20
|
+
.refine((d) => d.currentCycle <= d.plannedCycles, {
|
|
21
|
+
message: 'currentCycle must be <= plannedCycles',
|
|
22
|
+
})
|
|
23
|
+
.refine((d) => d.periodEnd === null || new Date(d.periodEnd) >= new Date(d.periodStart), {
|
|
24
|
+
message: 'periodEnd must be >= periodStart',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
class TreatmentEpisode extends BaseDto {}
|
|
28
|
+
|
|
29
|
+
TreatmentEpisode.schema = schema;
|
|
30
|
+
|
|
31
|
+
module.exports = { TreatmentEpisode };
|