@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.
Files changed (67) hide show
  1. package/lib/adapters/messageAdapters/TwilioMessageAdapter.js +17 -3
  2. package/lib/clinical/AssistantProcessor.js +3 -0
  3. package/lib/clinical/config/divergenceConfig.js +63 -0
  4. package/lib/clinical/helpers/divergenceHelper.js +100 -0
  5. package/lib/clinical/helpers/seedHelper.js +62 -0
  6. package/lib/clinical/index.js +7 -0
  7. package/lib/clinical/memory/DefaultMemoryManager.js +13 -1
  8. package/lib/clinical/models/divergenceModel.js +41 -0
  9. package/lib/clinical/models/turnTraceModel.js +11 -0
  10. package/lib/clinical/providers/BaseLLMProvider.js +16 -0
  11. package/lib/clinical/services/clinicalAgentService.js +18 -0
  12. package/lib/clinical/services/divergenceService.js +74 -0
  13. package/lib/clinical/services/promptComposerService.js +2 -1
  14. package/lib/clinical/services/seedService.js +21 -0
  15. package/lib/clinical/services/shadowMessageService.js +58 -0
  16. package/lib/clinical/services/shadowService.js +11 -2
  17. package/lib/clinical/stores/CtcaeCatalog.js +67 -0
  18. package/lib/controllers/conversationController.js +3 -1
  19. package/lib/controllers/templateController.js +27 -12
  20. package/lib/core/BatchingManager.js +2 -2
  21. package/lib/eval/EvalProvider.js +4 -3
  22. package/lib/fhir/config/catalogConfig.js +46 -0
  23. package/lib/fhir/config/connection.js +11 -0
  24. package/lib/fhir/config/fhirConfig.js +41 -0
  25. package/lib/fhir/constants/projectionSlugs.js +3 -0
  26. package/lib/fhir/helpers/elementHelper.js +33 -0
  27. package/lib/fhir/index.js +21 -0
  28. package/lib/fhir/models/envelopeModel.js +129 -0
  29. package/lib/fhir/models/historyModel.js +19 -0
  30. package/lib/fhir/projections/clinicalMentionProjection.js +65 -0
  31. package/lib/fhir/projections/registerProjectors.js +14 -0
  32. package/lib/fhir/resources/Appointment.js +28 -0
  33. package/lib/fhir/resources/Condition.js +34 -0
  34. package/lib/fhir/resources/MedicationStatement.js +42 -0
  35. package/lib/fhir/resources/Observation.js +79 -0
  36. package/lib/fhir/resources/Procedure.js +30 -0
  37. package/lib/fhir/resources/Provenance.js +28 -0
  38. package/lib/fhir/services/clinicalMentionService.js +18 -0
  39. package/lib/fhir/services/fhirService.js +31 -0
  40. package/lib/fhir/stores/fhirStore.js +187 -0
  41. package/lib/helpers/deliveryAttemptHelper.js +5 -13
  42. package/lib/helpers/projectorHelper.js +18 -0
  43. package/lib/helpers/twilioMediaHelper.js +25 -1
  44. package/lib/index.js +14 -0
  45. package/lib/services/airtableService.js +40 -14
  46. package/lib/shared/dtos/BaseDto.js +13 -0
  47. package/lib/shared/dtos/ClinicalFact.js +85 -0
  48. package/lib/shared/dtos/ClinicalMention.js +53 -0
  49. package/lib/shared/dtos/ClusterHistoryRecord.js +21 -0
  50. package/lib/shared/dtos/ClusterImpression.js +40 -0
  51. package/lib/shared/dtos/ContingencySafetyNet.js +47 -0
  52. package/lib/shared/dtos/DispatchedEscalation.js +44 -0
  53. package/lib/shared/dtos/Intervention.js +29 -0
  54. package/lib/shared/dtos/ManagedSymptom.js +75 -0
  55. package/lib/shared/dtos/MonitoringEpisode.js +25 -0
  56. package/lib/shared/dtos/PatientContext.js +22 -0
  57. package/lib/shared/dtos/PatientRiskAssessment.js +46 -0
  58. package/lib/shared/dtos/Reminder.js +32 -0
  59. package/lib/shared/dtos/RoutingDecision.js +68 -0
  60. package/lib/shared/dtos/SymptomEpisode.js +41 -0
  61. package/lib/shared/dtos/TreatmentEpisode.js +31 -0
  62. package/lib/utils/jsonUtils.js +34 -1
  63. package/lib/utils/vcardParser.js +41 -0
  64. package/package.json +7 -2
  65. package/lib/clinical/config/fhirConfig.js +0 -49
  66. package/lib/clinical/services/fhirService.js +0 -24
  67. /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 };