@persei/perseed-api 5.0.27 → 5.0.29

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 (73) hide show
  1. package/dist/ecosystem-alert.cjs +229 -0
  2. package/dist/ecosystem-alert.cjs.map +1 -0
  3. package/dist/ecosystem-alert.d.ts +1 -0
  4. package/dist/ecosystem-alert.mjs +229 -0
  5. package/dist/ecosystem-alert.mjs.map +1 -0
  6. package/dist/ecosystem-process-log.cjs.map +1 -1
  7. package/dist/ecosystem-process-log.mjs.map +1 -1
  8. package/dist/src/Services/AiService.d.ts.map +1 -1
  9. package/dist/src/Services/FileService.d.ts +1 -1
  10. package/dist/src/Services/FileService.d.ts.map +1 -1
  11. package/dist/src/app.d.ts.map +1 -1
  12. package/dist/src/controllers/IntrospectionController.d.ts +10 -1
  13. package/dist/src/controllers/IntrospectionController.d.ts.map +1 -1
  14. package/dist/src/schemas/container_widget/widgetTypes/PatientListProps.d.ts +20 -0
  15. package/dist/src/schemas/container_widget/widgetTypes/PatientListProps.d.ts.map +1 -1
  16. package/dist/src/test/test-helpers.d.ts.map +1 -1
  17. package/dist/src/v2/modules/ecosystem/action-system/action-system.helper/action-system.helper.d.ts +22 -4
  18. package/dist/src/v2/modules/ecosystem/action-system/action-system.helper/action-system.helper.d.ts.map +1 -1
  19. package/dist/src/v2/modules/ecosystem/action-system/action-system.schema.d.ts +14 -14
  20. package/dist/src/v2/modules/ecosystem/action-system/action-system.service/automatic-trigger.service.d.ts +4 -4
  21. package/dist/src/v2/modules/ecosystem/action-system/action-system.service/manual-trigger-engine.service.d.ts +5 -0
  22. package/dist/src/v2/modules/ecosystem/action-system/action-system.service/manual-trigger-engine.service.d.ts.map +1 -1
  23. package/dist/src/v2/modules/ecosystem/action-system/action-system.service/manual-trigger.service.d.ts +4 -4
  24. package/dist/src/v2/modules/ecosystem/action-system/action-system.service/trigger-engine.service.d.ts +9 -2
  25. package/dist/src/v2/modules/ecosystem/action-system/action-system.service/trigger-engine.service.d.ts.map +1 -1
  26. package/dist/src/v2/modules/ecosystem/alert/alert.helper.d.ts +16 -0
  27. package/dist/src/v2/modules/ecosystem/alert/alert.helper.d.ts.map +1 -1
  28. package/dist/src/v2/modules/ecosystem/alert/alert.model.d.ts +42 -0
  29. package/dist/src/v2/modules/ecosystem/alert/alert.model.d.ts.map +1 -1
  30. package/dist/src/v2/modules/ecosystem/alert/alert.schema.d.ts.map +1 -1
  31. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-listing.service.d.ts +52 -0
  32. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-listing.service.d.ts.map +1 -0
  33. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-listing.service.test.d.ts +2 -0
  34. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-listing.service.test.d.ts.map +1 -0
  35. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-patient-data.service.d.ts +36 -0
  36. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-patient-data.service.d.ts.map +1 -0
  37. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-patient-data.service.test.d.ts +2 -0
  38. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert-patient-data.service.test.d.ts.map +1 -0
  39. package/dist/src/v2/modules/ecosystem/alert/{alert.service.d.ts → alert.service/alert.service.d.ts} +5 -5
  40. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert.service.d.ts.map +1 -0
  41. package/dist/src/v2/modules/ecosystem/alert/alert.service/alert.service.test.d.ts.map +1 -0
  42. package/dist/src/v2/modules/ecosystem/alert/alert.service/index.d.ts +5 -0
  43. package/dist/src/v2/modules/ecosystem/alert/alert.service/index.d.ts.map +1 -0
  44. package/dist/src/v2/modules/ecosystem/alert/index.d.ts +3 -2
  45. package/dist/src/v2/modules/ecosystem/alert/index.d.ts.map +1 -1
  46. package/dist/src/v2/modules/ecosystem/alert/lib.d.ts +2 -0
  47. package/dist/src/v2/modules/ecosystem/alert/lib.d.ts.map +1 -0
  48. package/dist/src/v2/modules/ecosystem/process-log/process-log.schema.d.ts.map +1 -1
  49. package/dist/src/v2/modules/project/forms-data/forms-data.helper.d.ts +52 -1
  50. package/dist/src/v2/modules/project/forms-data/forms-data.helper.d.ts.map +1 -1
  51. package/dist/src/v2/modules/project/forms-data/forms-data.service/associated-sources.service.d.ts +64 -0
  52. package/dist/src/v2/modules/project/forms-data/forms-data.service/associated-sources.service.d.ts.map +1 -0
  53. package/dist/src/v2/modules/project/forms-data/forms-data.service/associated-sources.service.test.d.ts +2 -0
  54. package/dist/src/v2/modules/project/forms-data/forms-data.service/associated-sources.service.test.d.ts.map +1 -0
  55. package/dist/src/v2/modules/project/forms-data/forms-data.service/forms-data.service.d.ts +22 -0
  56. package/dist/src/v2/modules/project/forms-data/forms-data.service/forms-data.service.d.ts.map +1 -0
  57. package/dist/src/v2/modules/project/forms-data/forms-data.service/forms-data.service.test.d.ts.map +1 -0
  58. package/dist/src/v2/modules/project/forms-data/forms-data.service/index.d.ts +4 -0
  59. package/dist/src/v2/modules/project/forms-data/forms-data.service/index.d.ts.map +1 -0
  60. package/dist/src/v2/modules/project/forms-data/index.d.ts +1 -0
  61. package/dist/src/v2/modules/project/forms-data/index.d.ts.map +1 -1
  62. package/dist/v1/perseed-api.cjs +9 -0
  63. package/dist/v1/perseed-api.cjs.map +1 -1
  64. package/dist/v1/perseed-api.mjs +9 -0
  65. package/dist/v1/perseed-api.mjs.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/src/v2/modules/ecosystem/alert/alert.service.d.ts.map +0 -1
  68. package/dist/src/v2/modules/ecosystem/alert/alert.service.test.d.ts.map +0 -1
  69. package/dist/src/v2/modules/project/forms-data/forms-data.service.d.ts +0 -12
  70. package/dist/src/v2/modules/project/forms-data/forms-data.service.d.ts.map +0 -1
  71. package/dist/src/v2/modules/project/forms-data/forms-data.service.test.d.ts.map +0 -1
  72. /package/dist/src/v2/modules/ecosystem/alert/{alert.service.test.d.ts → alert.service/alert.service.test.d.ts} +0 -0
  73. /package/dist/src/v2/modules/project/forms-data/{forms-data.service.test.d.ts → forms-data.service/forms-data.service.test.d.ts} +0 -0
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const typebox = require("typebox");
4
+ const dates_schema = require("./dates.schema-npKpToAJ.cjs");
5
+ const AlertStatus = {
6
+ ACTIVE: "active",
7
+ RESOLVED: "resolved"
8
+ };
9
+ const alertStatusSchema = typebox.Type.Enum(Object.values(AlertStatus), {
10
+ title: "AlertStatus",
11
+ description: "Lifecycle state of an alert. An alert is open until it is resolved"
12
+ });
13
+ const AlertSeverity = {
14
+ INFO: "info",
15
+ LOW: "low",
16
+ MEDIUM: "medium",
17
+ HIGH: "high",
18
+ CRITICAL: "critical"
19
+ };
20
+ const alertSeveritySchema = typebox.Type.Enum(Object.values(AlertSeverity), {
21
+ title: "AlertSeverity",
22
+ description: "Severity of the alert, used to prioritise it"
23
+ });
24
+ const AlertEmitterType = {
25
+ TRIGGER: "trigger",
26
+ API: "api",
27
+ WEBHOOK: "webhook"
28
+ };
29
+ const alertEmitterTypeSchema = typebox.Type.Enum(Object.values(AlertEmitterType), {
30
+ title: "AlertEmitterType",
31
+ description: "Kind of emitter that raised the alert"
32
+ });
33
+ const alertEmitterSchema = typebox.Type.Object(
34
+ {
35
+ type: alertEmitterTypeSchema,
36
+ id: typebox.Type.Optional(
37
+ typebox.Type.Union([typebox.Type.String({ maxLength: 255 }), typebox.Type.Null()], {
38
+ description: "Identifier of the emitter within its type (trigger id, device id, service id…)"
39
+ })
40
+ ),
41
+ name: typebox.Type.Optional(
42
+ typebox.Type.Union([typebox.Type.String({ maxLength: 255 }), typebox.Type.Null()], {
43
+ description: "Human readable name of the emitter"
44
+ })
45
+ ),
46
+ reference: typebox.Type.Optional(
47
+ typebox.Type.Union([typebox.Type.String({ maxLength: 255 }), typebox.Type.Null()], {
48
+ description: "Identifier of the originating event in the emitting system, for traceability"
49
+ })
50
+ )
51
+ },
52
+ { title: "AlertEmitter", description: "Who raised the alert", additionalProperties: false }
53
+ );
54
+ const alertEntityRefSchema = typebox.Type.Object(
55
+ {
56
+ type: typebox.Type.String({ maxLength: 64, description: "Kind of entity (user, phone, email, service…)" }),
57
+ value: typebox.Type.String({ maxLength: 255, description: "Identifier of the entity within its type" })
58
+ },
59
+ {
60
+ title: "AlertEntityRef",
61
+ description: "Reference to an entity by kind and identifier",
62
+ additionalProperties: false
63
+ }
64
+ );
65
+ const alertSchema = typebox.Type.Object(
66
+ {
67
+ id: typebox.Type.Number({ description: "Unique identifier of the alert" }),
68
+ project_id: typebox.Type.Optional(
69
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], {
70
+ description: "Project this alert belongs to. Null for an ecosystem-wide alert, the ones created and read through the `/ecosystem/:ecosystemName/alert` mount, which belong to no project"
71
+ })
72
+ ),
73
+ alert_type_id: typebox.Type.Optional(
74
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], {
75
+ description: "Type of alert, as a row of the `alert_type` catalog of the ecosystem. Null for an alert that is not classified"
76
+ })
77
+ ),
78
+ user_id: typebox.Type.Optional(
79
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], {
80
+ description: "Caaring user the alert is about, and one of the two things that decide who may read it: this user always reads their own alerts. Null when the subject is a patient with no user account, in which case `patient_id` is the one that identifies them"
81
+ })
82
+ ),
83
+ patient_id: typebox.Type.Optional(
84
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], {
85
+ description: "Patient the alert is about, as the `id` of the `patient` CRF row of the project, and the other thing that decides who may read it: the professionals with access to that patient do. A patient may exist without a user account, so this is independent of `user_id`. It lives in the project database, so the column carries no foreign key and `project_id` is what says which database to read it from — an alert carrying it always carries a project"
86
+ })
87
+ ),
88
+ severity: typebox.Type.Optional(
89
+ typebox.Type.Union([alertSeveritySchema, typebox.Type.Null()], { description: "Severity of the alert. Null when not graded" })
90
+ ),
91
+ status: typebox.Type.Optional(alertStatusSchema),
92
+ title: typebox.Type.Optional(
93
+ typebox.Type.Union([typebox.Type.String({ maxLength: 255 }), typebox.Type.Null()], { description: "Short readable title" })
94
+ ),
95
+ summary: typebox.Type.Optional(typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()], { description: "Longer readable description" })),
96
+ emitter: alertEmitterSchema,
97
+ recipients: typebox.Type.Optional(
98
+ typebox.Type.Union([typebox.Type.Array(alertEntityRefSchema), typebox.Type.Null()], {
99
+ description: "Natural addressees of the alert. Null or empty when it has none"
100
+ })
101
+ ),
102
+ related_entities: typebox.Type.Optional(
103
+ typebox.Type.Union([typebox.Type.Array(alertEntityRefSchema), typebox.Type.Null()], {
104
+ description: "Entities related to the alert without being addressees of it"
105
+ })
106
+ ),
107
+ payload: typebox.Type.Optional(
108
+ typebox.Type.Union([typebox.Type.Record(typebox.Type.String(), typebox.Type.Unknown()), typebox.Type.Null()], {
109
+ description: "Structured data that originated the alert (measured value, threshold, form answer…)"
110
+ })
111
+ ),
112
+ metadata: typebox.Type.Optional(
113
+ typebox.Type.Union([typebox.Type.Record(typebox.Type.String(), typebox.Type.Unknown()), typebox.Type.Null()], {
114
+ description: "Free extension point, so a new consumer does not need a schema change"
115
+ })
116
+ ),
117
+ dedup_key: typebox.Type.Optional(
118
+ typebox.Type.String({
119
+ maxLength: 255,
120
+ description: "Fingerprint used to avoid storing the same alert twice. A create carrying a key that is already open in the same scope answers with the stored alert; a resolved alert does not hold its key, so a condition that recurs after being dealt with raises a new one. Optional for the client: the server generates a UUID when it is absent, so the column always carries a usable value"
121
+ })
122
+ ),
123
+ triggered_at: typebox.Type.Optional(
124
+ typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()], {
125
+ description: `When the event that originated the alert happened. ${dates_schema.EMITTED_DATETIME_NOTE}`,
126
+ examples: [dates_schema.API_DATETIME_EXAMPLE]
127
+ })
128
+ ),
129
+ viewed_at: typebox.Type.Optional(
130
+ typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()], {
131
+ description: `When the alert was first viewed. Null while unseen. ${dates_schema.EMITTED_DATETIME_NOTE}`,
132
+ examples: [dates_schema.API_DATETIME_EXAMPLE]
133
+ })
134
+ ),
135
+ viewed_by: typebox.Type.Optional(
136
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], { description: "User who first viewed the alert" })
137
+ ),
138
+ resolved_at: typebox.Type.Optional(
139
+ typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()], {
140
+ description: `When the alert was resolved. Null while active. ${dates_schema.EMITTED_DATETIME_NOTE}`,
141
+ examples: [dates_schema.API_DATETIME_EXAMPLE]
142
+ })
143
+ ),
144
+ resolved_by: typebox.Type.Optional(
145
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], { description: "User who resolved the alert" })
146
+ ),
147
+ resolution_note: typebox.Type.Optional(
148
+ typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()], { description: "Optional note left when resolving the alert" })
149
+ ),
150
+ created_user: typebox.Type.Optional(
151
+ typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()], { description: "User who created the alert, when a user did" })
152
+ ),
153
+ created_at: typebox.Type.Optional(
154
+ typebox.Type.String({
155
+ description: `When the row was created. ${dates_schema.EMITTED_DATETIME_NOTE}`,
156
+ examples: [dates_schema.API_DATETIME_EXAMPLE]
157
+ })
158
+ ),
159
+ mod_time: typebox.Type.Optional(
160
+ typebox.Type.String({
161
+ description: `When the row was last modified. ${dates_schema.EMITTED_DATETIME_NOTE}`,
162
+ examples: [dates_schema.API_DATETIME_EXAMPLE]
163
+ })
164
+ )
165
+ },
166
+ { title: "Alert", description: "An alert and its state (ecosystem level)" }
167
+ );
168
+ const createAlertSchema = typebox.Type.Object(
169
+ {
170
+ alert_type_id: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()])),
171
+ user_id: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()])),
172
+ patient_id: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Number(), typebox.Type.Null()])),
173
+ severity: typebox.Type.Optional(typebox.Type.Union([alertSeveritySchema, typebox.Type.Null()])),
174
+ title: typebox.Type.Optional(typebox.Type.Union([typebox.Type.String({ maxLength: 255 }), typebox.Type.Null()])),
175
+ summary: typebox.Type.Optional(typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()])),
176
+ emitter: alertEmitterSchema,
177
+ recipients: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Array(alertEntityRefSchema), typebox.Type.Null()])),
178
+ related_entities: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Array(alertEntityRefSchema), typebox.Type.Null()])),
179
+ payload: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Record(typebox.Type.String(), typebox.Type.Unknown()), typebox.Type.Null()])),
180
+ metadata: typebox.Type.Optional(typebox.Type.Union([typebox.Type.Record(typebox.Type.String(), typebox.Type.Unknown()), typebox.Type.Null()])),
181
+ dedup_key: typebox.Type.Optional(typebox.Type.String({ maxLength: 255 })),
182
+ // `apiDateTime`, so the value reaches the service already a `Date` and no handler converts a
183
+ // string by hand. The wrapped type is the same `format: 'date-time'` as before, so the 200s and
184
+ // 400s of this field do not move — only what the service receives does.
185
+ triggered_at: typebox.Type.Optional(
186
+ typebox.Type.Union([dates_schema.apiDateTime, typebox.Type.Null()], {
187
+ description: `When the event that originated the alert happened. RFC 3339 with a mandatory offset — \`${dates_schema.API_DATETIME_EXAMPLE}\` is the shape the API emits, so a value read from here can be sent straight back. ${dates_schema.API_DATETIME_ACCEPTED_NOTE}. Stored as UTC`
188
+ })
189
+ )
190
+ },
191
+ { title: "CreateAlert", description: "Payload to create an alert", additionalProperties: false }
192
+ );
193
+ const updateAlertSchema = typebox.Type.Partial(typebox.Type.Omit(createAlertSchema, ["emitter"]), {
194
+ title: "UpdateAlert",
195
+ description: "Payload to update an alert. Every field is optional. `emitter` cannot be changed after creation",
196
+ additionalProperties: false
197
+ });
198
+ const resolveAlertSchema = typebox.Type.Object(
199
+ {
200
+ resolution_note: typebox.Type.Optional(
201
+ typebox.Type.Union([typebox.Type.String(), typebox.Type.Null()], { description: "Optional note explaining the resolution" })
202
+ )
203
+ },
204
+ { title: "ResolveAlert", description: "Payload to resolve an alert", additionalProperties: false }
205
+ );
206
+ const listAlertQuerySchema = typebox.Type.Object(
207
+ {
208
+ status: typebox.Type.Optional(alertStatusSchema),
209
+ severity: typebox.Type.Optional(alertSeveritySchema),
210
+ user_id: typebox.Type.Optional(typebox.Type.Number({ description: "Filter by the user the alert is about" })),
211
+ patient_id: typebox.Type.Optional(typebox.Type.Number({ description: "Filter by the patient the alert is about" })),
212
+ alert_type_id: typebox.Type.Optional(typebox.Type.Number({ description: "Filter by the type of the alert" }))
213
+ },
214
+ { title: "ListAlertQuery", description: "Filters available on the alert list" }
215
+ );
216
+ exports.AlertEmitterType = AlertEmitterType;
217
+ exports.AlertSeverity = AlertSeverity;
218
+ exports.AlertStatus = AlertStatus;
219
+ exports.alertEmitterSchema = alertEmitterSchema;
220
+ exports.alertEmitterTypeSchema = alertEmitterTypeSchema;
221
+ exports.alertEntityRefSchema = alertEntityRefSchema;
222
+ exports.alertSchema = alertSchema;
223
+ exports.alertSeveritySchema = alertSeveritySchema;
224
+ exports.alertStatusSchema = alertStatusSchema;
225
+ exports.createAlertSchema = createAlertSchema;
226
+ exports.listAlertQuerySchema = listAlertQuerySchema;
227
+ exports.resolveAlertSchema = resolveAlertSchema;
228
+ exports.updateAlertSchema = updateAlertSchema;
229
+ //# sourceMappingURL=ecosystem-alert.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecosystem-alert.cjs","sources":["../../src/v2/modules/ecosystem/alert/alert.schema.ts"],"sourcesContent":["import { Type, Static, StaticDecode } from 'typebox'\n// From the schema file, not the module barrel (`@/modules/global/dates`) that every other consumer\n// imports — the one case where `no-restricted-imports` has to give way, as `process-log.schema.ts`\n// already does for the same reason.\n//\n// This module has a `lib.ts`, so this file is an entry point of the published `@persei/perseed-api`\n// bundle, which is browser code. The barrel re-exports `dates.middleware`, a `fastify-plugin` that\n// pulls the server dependencies behind it into that bundle. `validate-v2-modules` enforces this.\n// eslint-disable-next-line no-restricted-imports\nimport {\n API_DATETIME_ACCEPTED_NOTE,\n API_DATETIME_EXAMPLE,\n EMITTED_DATETIME_NOTE,\n apiDateTime,\n} from '@/modules/global/dates/dates.schema'\n\n// An alert stores the alert and its state, nothing else. Everything that happens\n// *because* of an alert (notifications, calls to external services, writing a\n// form) belongs to the action system: an alert is one more trigger source.\n\n// ── Enums / literal unions ────────────────────────────────────────────\n// Same shape as the action-system module: a const object as the source of truth\n// (ergonomic dot access, `AlertStatus.ACTIVE`) plus the schema derived from it.\n\nexport const AlertStatus = {\n ACTIVE: 'active',\n RESOLVED: 'resolved',\n} as const\nexport const alertStatusSchema = Type.Enum(Object.values(AlertStatus), {\n title: 'AlertStatus',\n description: 'Lifecycle state of an alert. An alert is open until it is resolved',\n})\nexport type AlertStatus = Static<typeof alertStatusSchema>\n\nexport const AlertSeverity = {\n INFO: 'info',\n LOW: 'low',\n MEDIUM: 'medium',\n HIGH: 'high',\n CRITICAL: 'critical',\n} as const\nexport const alertSeveritySchema = Type.Enum(Object.values(AlertSeverity), {\n title: 'AlertSeverity',\n description: 'Severity of the alert, used to prioritise it',\n})\nexport type AlertSeverity = Static<typeof alertSeveritySchema>\n\n// Who raised the alert. Closed union: the three entry paths the system has.\n// `webhook` is declared because it is the emitter an ingress endpoint will record\n// — the endpoint itself is a later issue, but an alert created by any other means\n// on behalf of a device must already be able to say so.\nexport const AlertEmitterType = {\n TRIGGER: 'trigger',\n API: 'api',\n WEBHOOK: 'webhook',\n} as const\nexport const alertEmitterTypeSchema = Type.Enum(Object.values(AlertEmitterType), {\n title: 'AlertEmitterType',\n description: 'Kind of emitter that raised the alert',\n})\nexport type AlertEmitterType = Static<typeof alertEmitterTypeSchema>\n\n// ── Embedded structures ──────────────────────────────────────────────\n\nexport const alertEmitterSchema = Type.Object(\n {\n type: alertEmitterTypeSchema,\n id: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {\n description: 'Identifier of the emitter within its type (trigger id, device id, service id…)',\n }),\n ),\n name: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {\n description: 'Human readable name of the emitter',\n }),\n ),\n reference: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {\n description: 'Identifier of the originating event in the emitting system, for traceability',\n }),\n ),\n },\n { title: 'AlertEmitter', description: 'Who raised the alert', additionalProperties: false },\n)\nexport type AlertEmitterData = Static<typeof alertEmitterSchema>\n\n// Recipients and related entities share this shape on purpose: both answer\n// \"which entity, and which one of them\", and the pair differs only in the role it\n// plays — a recipient is a natural addressee of the alert, a related entity is\n// connected to it without being addressed. Same shape means a trigger reading\n// either one reads it the same way.\n//\n// `type` is an open string (a phone, an email, a caaring user, a service…) and\n// `value` is its identifier, so a new kind of recipient needs no migration.\nexport const alertEntityRefSchema = Type.Object(\n {\n type: Type.String({ maxLength: 64, description: 'Kind of entity (user, phone, email, service…)' }),\n value: Type.String({ maxLength: 255, description: 'Identifier of the entity within its type' }),\n },\n {\n title: 'AlertEntityRef',\n description: 'Reference to an entity by kind and identifier',\n additionalProperties: false,\n },\n)\nexport type AlertEntityRefData = Static<typeof alertEntityRefSchema>\n\n// ── alert ─────────────────────────────────────────────────────────────\n\nexport const alertSchema = Type.Object(\n {\n id: Type.Number({ description: 'Unique identifier of the alert' }),\n project_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Project this alert belongs to. Null for an ecosystem-wide alert, the ones created and read through the `/ecosystem/:ecosystemName/alert` mount, which belong to no project',\n }),\n ),\n alert_type_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Type of alert, as a row of the `alert_type` catalog of the ecosystem. Null for an alert that is not classified',\n }),\n ),\n user_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Caaring user the alert is about, and one of the two things that decide who may read it: this user always reads their own alerts. Null when the subject is a patient with no user account, in which case `patient_id` is the one that identifies them',\n }),\n ),\n patient_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Patient the alert is about, as the `id` of the `patient` CRF row of the project, and the other thing that decides who may read it: the professionals with access to that patient do. A patient may exist without a user account, so this is independent of `user_id`. It lives in the project database, so the column carries no foreign key and `project_id` is what says which database to read it from — an alert carrying it always carries a project',\n }),\n ),\n severity: Type.Optional(\n Type.Union([alertSeveritySchema, Type.Null()], { description: 'Severity of the alert. Null when not graded' }),\n ),\n status: Type.Optional(alertStatusSchema),\n title: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], { description: 'Short readable title' }),\n ),\n summary: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: 'Longer readable description' })),\n emitter: alertEmitterSchema,\n recipients: Type.Optional(\n Type.Union([Type.Array(alertEntityRefSchema), Type.Null()], {\n description: 'Natural addressees of the alert. Null or empty when it has none',\n }),\n ),\n related_entities: Type.Optional(\n Type.Union([Type.Array(alertEntityRefSchema), Type.Null()], {\n description: 'Entities related to the alert without being addressees of it',\n }),\n ),\n payload: Type.Optional(\n Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()], {\n description: 'Structured data that originated the alert (measured value, threshold, form answer…)',\n }),\n ),\n metadata: Type.Optional(\n Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()], {\n description: 'Free extension point, so a new consumer does not need a schema change',\n }),\n ),\n dedup_key: Type.Optional(\n Type.String({\n maxLength: 255,\n description:\n 'Fingerprint used to avoid storing the same alert twice. A create carrying a key that is already open in the same scope answers with the stored alert; a resolved alert does not hold its key, so a condition that recurs after being dealt with raises a new one. Optional for the client: the server generates a UUID when it is absent, so the column always carries a usable value',\n }),\n ),\n triggered_at: Type.Optional(\n Type.Union([Type.String(), Type.Null()], {\n description: `When the event that originated the alert happened. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n viewed_at: Type.Optional(\n Type.Union([Type.String(), Type.Null()], {\n description: `When the alert was first viewed. Null while unseen. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n viewed_by: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], { description: 'User who first viewed the alert' }),\n ),\n resolved_at: Type.Optional(\n Type.Union([Type.String(), Type.Null()], {\n description: `When the alert was resolved. Null while active. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n resolved_by: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], { description: 'User who resolved the alert' }),\n ),\n resolution_note: Type.Optional(\n Type.Union([Type.String(), Type.Null()], { description: 'Optional note left when resolving the alert' }),\n ),\n created_user: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], { description: 'User who created the alert, when a user did' }),\n ),\n created_at: Type.Optional(\n Type.String({\n description: `When the row was created. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n mod_time: Type.Optional(\n Type.String({\n description: `When the row was last modified. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n },\n { title: 'Alert', description: 'An alert and its state (ecosystem level)' },\n)\nexport type AlertData = Static<typeof alertSchema>\n\n// Written out rather than derived from `alertSchema`, for the same reason the action-system module\n// spells its request schemas out: an entity schema says what a row holds, a request schema says what\n// a client may send, and only the second can close itself. `Type.Omit` over an entity schema keeps\n// server-owned columns reachable through a body — here `status`, `resolved_*` and `viewed_*` are\n// absent because the server owns them, not because an `Omit` happened to remove them. Resolving\n// goes through its own endpoint; viewing is recorded by the read path.\nexport const createAlertSchema = Type.Object(\n {\n alert_type_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),\n user_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),\n patient_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),\n severity: Type.Optional(Type.Union([alertSeveritySchema, Type.Null()])),\n title: Type.Optional(Type.Union([Type.String({ maxLength: 255 }), Type.Null()])),\n summary: Type.Optional(Type.Union([Type.String(), Type.Null()])),\n emitter: alertEmitterSchema,\n recipients: Type.Optional(Type.Union([Type.Array(alertEntityRefSchema), Type.Null()])),\n related_entities: Type.Optional(Type.Union([Type.Array(alertEntityRefSchema), Type.Null()])),\n payload: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),\n metadata: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),\n dedup_key: Type.Optional(Type.String({ maxLength: 255 })),\n // `apiDateTime`, so the value reaches the service already a `Date` and no handler converts a\n // string by hand. The wrapped type is the same `format: 'date-time'` as before, so the 200s and\n // 400s of this field do not move — only what the service receives does.\n triggered_at: Type.Optional(\n Type.Union([apiDateTime, Type.Null()], {\n description: `When the event that originated the alert happened. RFC 3339 with a mandatory offset — \\`${API_DATETIME_EXAMPLE}\\` is the shape the API emits, so a value read from here can be sent straight back. ${API_DATETIME_ACCEPTED_NOTE}. Stored as UTC`,\n }),\n ),\n },\n { title: 'CreateAlert', description: 'Payload to create an alert', additionalProperties: false },\n)\n// `StaticDecode`, not `Static`: this type describes what a *handler receives*, and the handler\n// receives the decoded body — `triggered_at` is a `Date` there. `Static` resolves in the encode\n// direction, which is the string that travelled on the wire, so it would describe the value one\n// step earlier than the one this type is used for.\nexport type CreateAlertData = StaticDecode<typeof createAlertSchema>\n\n/**\n * The same payload as it travels on the wire, before the boundary decodes it: `triggered_at` is\n * still the RFC 3339 string a client sends.\n *\n * Only clients of the HTTP interface need this — tests that build a request body, and any caller\n * assembling JSON. Everything on the server side of the validator wants `CreateAlertData`, which is\n * the decoded shape.\n */\nexport type CreateAlertRequestBody = Static<typeof createAlertSchema>\n\n// `emitter` is `Type.Omit`-ted, not just made optional by `Type.Partial`: who raised the alert is\n// fixed at creation the same way `status`/`resolved_*`/`viewed_*` already are (see\n// `createAlertSchema` above) — a client rewriting it through a PATCH would falsify that provenance\n// for an entity that carries clinical data. `Type.Partial` alone would only make the key optional,\n// not absent, so it would still accept and apply a client-supplied `emitter`.\n// `Type.Partial` rebuilds the object and does NOT inherit `additionalProperties`, so it is restated.\nexport const updateAlertSchema = Type.Partial(Type.Omit(createAlertSchema, ['emitter']), {\n title: 'UpdateAlert',\n description: 'Payload to update an alert. Every field is optional. `emitter` cannot be changed after creation',\n additionalProperties: false,\n})\nexport type UpdateAlertData = StaticDecode<typeof updateAlertSchema>\n\n// Resolving is its own endpoint rather than a `status` field on the update: it is\n// the single transition of the lifecycle, and it writes three columns at once\n// (`status`, `resolved_at`, `resolved_by`) that must never disagree. A plain\n// `PATCH {status: 'resolved'}` would let a client set the state without the two\n// columns that say who closed it and when.\nexport const resolveAlertSchema = Type.Object(\n {\n resolution_note: Type.Optional(\n Type.Union([Type.String(), Type.Null()], { description: 'Optional note explaining the resolution' }),\n ),\n },\n { title: 'ResolveAlert', description: 'Payload to resolve an alert', additionalProperties: false },\n)\nexport type ResolveAlertData = Static<typeof resolveAlertSchema>\n\n// Filters of the list endpoint, on top of the pagination query. Everything is\n// optional: with none of them the endpoint lists the project's alerts.\nexport const listAlertQuerySchema = Type.Object(\n {\n status: Type.Optional(alertStatusSchema),\n severity: Type.Optional(alertSeveritySchema),\n user_id: Type.Optional(Type.Number({ description: 'Filter by the user the alert is about' })),\n patient_id: Type.Optional(Type.Number({ description: 'Filter by the patient the alert is about' })),\n alert_type_id: Type.Optional(Type.Number({ description: 'Filter by the type of the alert' })),\n },\n { title: 'ListAlertQuery', description: 'Filters available on the alert list' },\n)\nexport type ListAlertQuery = Static<typeof listAlertQuerySchema>\n"],"names":["Type","EMITTED_DATETIME_NOTE","API_DATETIME_EXAMPLE","apiDateTime","API_DATETIME_ACCEPTED_NOTE"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from './src/v2/modules/ecosystem/alert/lib'
@@ -0,0 +1,229 @@
1
+ import { Type } from "typebox";
2
+ import { a as API_DATETIME_EXAMPLE, E as EMITTED_DATETIME_NOTE, b as apiDateTime, A as API_DATETIME_ACCEPTED_NOTE } from "./dates.schema-BqkBcmM6.js";
3
+ const AlertStatus = {
4
+ ACTIVE: "active",
5
+ RESOLVED: "resolved"
6
+ };
7
+ const alertStatusSchema = Type.Enum(Object.values(AlertStatus), {
8
+ title: "AlertStatus",
9
+ description: "Lifecycle state of an alert. An alert is open until it is resolved"
10
+ });
11
+ const AlertSeverity = {
12
+ INFO: "info",
13
+ LOW: "low",
14
+ MEDIUM: "medium",
15
+ HIGH: "high",
16
+ CRITICAL: "critical"
17
+ };
18
+ const alertSeveritySchema = Type.Enum(Object.values(AlertSeverity), {
19
+ title: "AlertSeverity",
20
+ description: "Severity of the alert, used to prioritise it"
21
+ });
22
+ const AlertEmitterType = {
23
+ TRIGGER: "trigger",
24
+ API: "api",
25
+ WEBHOOK: "webhook"
26
+ };
27
+ const alertEmitterTypeSchema = Type.Enum(Object.values(AlertEmitterType), {
28
+ title: "AlertEmitterType",
29
+ description: "Kind of emitter that raised the alert"
30
+ });
31
+ const alertEmitterSchema = Type.Object(
32
+ {
33
+ type: alertEmitterTypeSchema,
34
+ id: Type.Optional(
35
+ Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {
36
+ description: "Identifier of the emitter within its type (trigger id, device id, service id…)"
37
+ })
38
+ ),
39
+ name: Type.Optional(
40
+ Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {
41
+ description: "Human readable name of the emitter"
42
+ })
43
+ ),
44
+ reference: Type.Optional(
45
+ Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {
46
+ description: "Identifier of the originating event in the emitting system, for traceability"
47
+ })
48
+ )
49
+ },
50
+ { title: "AlertEmitter", description: "Who raised the alert", additionalProperties: false }
51
+ );
52
+ const alertEntityRefSchema = Type.Object(
53
+ {
54
+ type: Type.String({ maxLength: 64, description: "Kind of entity (user, phone, email, service…)" }),
55
+ value: Type.String({ maxLength: 255, description: "Identifier of the entity within its type" })
56
+ },
57
+ {
58
+ title: "AlertEntityRef",
59
+ description: "Reference to an entity by kind and identifier",
60
+ additionalProperties: false
61
+ }
62
+ );
63
+ const alertSchema = Type.Object(
64
+ {
65
+ id: Type.Number({ description: "Unique identifier of the alert" }),
66
+ project_id: Type.Optional(
67
+ Type.Union([Type.Number(), Type.Null()], {
68
+ description: "Project this alert belongs to. Null for an ecosystem-wide alert, the ones created and read through the `/ecosystem/:ecosystemName/alert` mount, which belong to no project"
69
+ })
70
+ ),
71
+ alert_type_id: Type.Optional(
72
+ Type.Union([Type.Number(), Type.Null()], {
73
+ description: "Type of alert, as a row of the `alert_type` catalog of the ecosystem. Null for an alert that is not classified"
74
+ })
75
+ ),
76
+ user_id: Type.Optional(
77
+ Type.Union([Type.Number(), Type.Null()], {
78
+ description: "Caaring user the alert is about, and one of the two things that decide who may read it: this user always reads their own alerts. Null when the subject is a patient with no user account, in which case `patient_id` is the one that identifies them"
79
+ })
80
+ ),
81
+ patient_id: Type.Optional(
82
+ Type.Union([Type.Number(), Type.Null()], {
83
+ description: "Patient the alert is about, as the `id` of the `patient` CRF row of the project, and the other thing that decides who may read it: the professionals with access to that patient do. A patient may exist without a user account, so this is independent of `user_id`. It lives in the project database, so the column carries no foreign key and `project_id` is what says which database to read it from — an alert carrying it always carries a project"
84
+ })
85
+ ),
86
+ severity: Type.Optional(
87
+ Type.Union([alertSeveritySchema, Type.Null()], { description: "Severity of the alert. Null when not graded" })
88
+ ),
89
+ status: Type.Optional(alertStatusSchema),
90
+ title: Type.Optional(
91
+ Type.Union([Type.String({ maxLength: 255 }), Type.Null()], { description: "Short readable title" })
92
+ ),
93
+ summary: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Longer readable description" })),
94
+ emitter: alertEmitterSchema,
95
+ recipients: Type.Optional(
96
+ Type.Union([Type.Array(alertEntityRefSchema), Type.Null()], {
97
+ description: "Natural addressees of the alert. Null or empty when it has none"
98
+ })
99
+ ),
100
+ related_entities: Type.Optional(
101
+ Type.Union([Type.Array(alertEntityRefSchema), Type.Null()], {
102
+ description: "Entities related to the alert without being addressees of it"
103
+ })
104
+ ),
105
+ payload: Type.Optional(
106
+ Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()], {
107
+ description: "Structured data that originated the alert (measured value, threshold, form answer…)"
108
+ })
109
+ ),
110
+ metadata: Type.Optional(
111
+ Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()], {
112
+ description: "Free extension point, so a new consumer does not need a schema change"
113
+ })
114
+ ),
115
+ dedup_key: Type.Optional(
116
+ Type.String({
117
+ maxLength: 255,
118
+ description: "Fingerprint used to avoid storing the same alert twice. A create carrying a key that is already open in the same scope answers with the stored alert; a resolved alert does not hold its key, so a condition that recurs after being dealt with raises a new one. Optional for the client: the server generates a UUID when it is absent, so the column always carries a usable value"
119
+ })
120
+ ),
121
+ triggered_at: Type.Optional(
122
+ Type.Union([Type.String(), Type.Null()], {
123
+ description: `When the event that originated the alert happened. ${EMITTED_DATETIME_NOTE}`,
124
+ examples: [API_DATETIME_EXAMPLE]
125
+ })
126
+ ),
127
+ viewed_at: Type.Optional(
128
+ Type.Union([Type.String(), Type.Null()], {
129
+ description: `When the alert was first viewed. Null while unseen. ${EMITTED_DATETIME_NOTE}`,
130
+ examples: [API_DATETIME_EXAMPLE]
131
+ })
132
+ ),
133
+ viewed_by: Type.Optional(
134
+ Type.Union([Type.Number(), Type.Null()], { description: "User who first viewed the alert" })
135
+ ),
136
+ resolved_at: Type.Optional(
137
+ Type.Union([Type.String(), Type.Null()], {
138
+ description: `When the alert was resolved. Null while active. ${EMITTED_DATETIME_NOTE}`,
139
+ examples: [API_DATETIME_EXAMPLE]
140
+ })
141
+ ),
142
+ resolved_by: Type.Optional(
143
+ Type.Union([Type.Number(), Type.Null()], { description: "User who resolved the alert" })
144
+ ),
145
+ resolution_note: Type.Optional(
146
+ Type.Union([Type.String(), Type.Null()], { description: "Optional note left when resolving the alert" })
147
+ ),
148
+ created_user: Type.Optional(
149
+ Type.Union([Type.Number(), Type.Null()], { description: "User who created the alert, when a user did" })
150
+ ),
151
+ created_at: Type.Optional(
152
+ Type.String({
153
+ description: `When the row was created. ${EMITTED_DATETIME_NOTE}`,
154
+ examples: [API_DATETIME_EXAMPLE]
155
+ })
156
+ ),
157
+ mod_time: Type.Optional(
158
+ Type.String({
159
+ description: `When the row was last modified. ${EMITTED_DATETIME_NOTE}`,
160
+ examples: [API_DATETIME_EXAMPLE]
161
+ })
162
+ )
163
+ },
164
+ { title: "Alert", description: "An alert and its state (ecosystem level)" }
165
+ );
166
+ const createAlertSchema = Type.Object(
167
+ {
168
+ alert_type_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
169
+ user_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
170
+ patient_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
171
+ severity: Type.Optional(Type.Union([alertSeveritySchema, Type.Null()])),
172
+ title: Type.Optional(Type.Union([Type.String({ maxLength: 255 }), Type.Null()])),
173
+ summary: Type.Optional(Type.Union([Type.String(), Type.Null()])),
174
+ emitter: alertEmitterSchema,
175
+ recipients: Type.Optional(Type.Union([Type.Array(alertEntityRefSchema), Type.Null()])),
176
+ related_entities: Type.Optional(Type.Union([Type.Array(alertEntityRefSchema), Type.Null()])),
177
+ payload: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),
178
+ metadata: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),
179
+ dedup_key: Type.Optional(Type.String({ maxLength: 255 })),
180
+ // `apiDateTime`, so the value reaches the service already a `Date` and no handler converts a
181
+ // string by hand. The wrapped type is the same `format: 'date-time'` as before, so the 200s and
182
+ // 400s of this field do not move — only what the service receives does.
183
+ triggered_at: Type.Optional(
184
+ Type.Union([apiDateTime, Type.Null()], {
185
+ description: `When the event that originated the alert happened. RFC 3339 with a mandatory offset — \`${API_DATETIME_EXAMPLE}\` is the shape the API emits, so a value read from here can be sent straight back. ${API_DATETIME_ACCEPTED_NOTE}. Stored as UTC`
186
+ })
187
+ )
188
+ },
189
+ { title: "CreateAlert", description: "Payload to create an alert", additionalProperties: false }
190
+ );
191
+ const updateAlertSchema = Type.Partial(Type.Omit(createAlertSchema, ["emitter"]), {
192
+ title: "UpdateAlert",
193
+ description: "Payload to update an alert. Every field is optional. `emitter` cannot be changed after creation",
194
+ additionalProperties: false
195
+ });
196
+ const resolveAlertSchema = Type.Object(
197
+ {
198
+ resolution_note: Type.Optional(
199
+ Type.Union([Type.String(), Type.Null()], { description: "Optional note explaining the resolution" })
200
+ )
201
+ },
202
+ { title: "ResolveAlert", description: "Payload to resolve an alert", additionalProperties: false }
203
+ );
204
+ const listAlertQuerySchema = Type.Object(
205
+ {
206
+ status: Type.Optional(alertStatusSchema),
207
+ severity: Type.Optional(alertSeveritySchema),
208
+ user_id: Type.Optional(Type.Number({ description: "Filter by the user the alert is about" })),
209
+ patient_id: Type.Optional(Type.Number({ description: "Filter by the patient the alert is about" })),
210
+ alert_type_id: Type.Optional(Type.Number({ description: "Filter by the type of the alert" }))
211
+ },
212
+ { title: "ListAlertQuery", description: "Filters available on the alert list" }
213
+ );
214
+ export {
215
+ AlertEmitterType,
216
+ AlertSeverity,
217
+ AlertStatus,
218
+ alertEmitterSchema,
219
+ alertEmitterTypeSchema,
220
+ alertEntityRefSchema,
221
+ alertSchema,
222
+ alertSeveritySchema,
223
+ alertStatusSchema,
224
+ createAlertSchema,
225
+ listAlertQuerySchema,
226
+ resolveAlertSchema,
227
+ updateAlertSchema
228
+ };
229
+ //# sourceMappingURL=ecosystem-alert.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecosystem-alert.mjs","sources":["../../src/v2/modules/ecosystem/alert/alert.schema.ts"],"sourcesContent":["import { Type, Static, StaticDecode } from 'typebox'\n// From the schema file, not the module barrel (`@/modules/global/dates`) that every other consumer\n// imports — the one case where `no-restricted-imports` has to give way, as `process-log.schema.ts`\n// already does for the same reason.\n//\n// This module has a `lib.ts`, so this file is an entry point of the published `@persei/perseed-api`\n// bundle, which is browser code. The barrel re-exports `dates.middleware`, a `fastify-plugin` that\n// pulls the server dependencies behind it into that bundle. `validate-v2-modules` enforces this.\n// eslint-disable-next-line no-restricted-imports\nimport {\n API_DATETIME_ACCEPTED_NOTE,\n API_DATETIME_EXAMPLE,\n EMITTED_DATETIME_NOTE,\n apiDateTime,\n} from '@/modules/global/dates/dates.schema'\n\n// An alert stores the alert and its state, nothing else. Everything that happens\n// *because* of an alert (notifications, calls to external services, writing a\n// form) belongs to the action system: an alert is one more trigger source.\n\n// ── Enums / literal unions ────────────────────────────────────────────\n// Same shape as the action-system module: a const object as the source of truth\n// (ergonomic dot access, `AlertStatus.ACTIVE`) plus the schema derived from it.\n\nexport const AlertStatus = {\n ACTIVE: 'active',\n RESOLVED: 'resolved',\n} as const\nexport const alertStatusSchema = Type.Enum(Object.values(AlertStatus), {\n title: 'AlertStatus',\n description: 'Lifecycle state of an alert. An alert is open until it is resolved',\n})\nexport type AlertStatus = Static<typeof alertStatusSchema>\n\nexport const AlertSeverity = {\n INFO: 'info',\n LOW: 'low',\n MEDIUM: 'medium',\n HIGH: 'high',\n CRITICAL: 'critical',\n} as const\nexport const alertSeveritySchema = Type.Enum(Object.values(AlertSeverity), {\n title: 'AlertSeverity',\n description: 'Severity of the alert, used to prioritise it',\n})\nexport type AlertSeverity = Static<typeof alertSeveritySchema>\n\n// Who raised the alert. Closed union: the three entry paths the system has.\n// `webhook` is declared because it is the emitter an ingress endpoint will record\n// — the endpoint itself is a later issue, but an alert created by any other means\n// on behalf of a device must already be able to say so.\nexport const AlertEmitterType = {\n TRIGGER: 'trigger',\n API: 'api',\n WEBHOOK: 'webhook',\n} as const\nexport const alertEmitterTypeSchema = Type.Enum(Object.values(AlertEmitterType), {\n title: 'AlertEmitterType',\n description: 'Kind of emitter that raised the alert',\n})\nexport type AlertEmitterType = Static<typeof alertEmitterTypeSchema>\n\n// ── Embedded structures ──────────────────────────────────────────────\n\nexport const alertEmitterSchema = Type.Object(\n {\n type: alertEmitterTypeSchema,\n id: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {\n description: 'Identifier of the emitter within its type (trigger id, device id, service id…)',\n }),\n ),\n name: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {\n description: 'Human readable name of the emitter',\n }),\n ),\n reference: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], {\n description: 'Identifier of the originating event in the emitting system, for traceability',\n }),\n ),\n },\n { title: 'AlertEmitter', description: 'Who raised the alert', additionalProperties: false },\n)\nexport type AlertEmitterData = Static<typeof alertEmitterSchema>\n\n// Recipients and related entities share this shape on purpose: both answer\n// \"which entity, and which one of them\", and the pair differs only in the role it\n// plays — a recipient is a natural addressee of the alert, a related entity is\n// connected to it without being addressed. Same shape means a trigger reading\n// either one reads it the same way.\n//\n// `type` is an open string (a phone, an email, a caaring user, a service…) and\n// `value` is its identifier, so a new kind of recipient needs no migration.\nexport const alertEntityRefSchema = Type.Object(\n {\n type: Type.String({ maxLength: 64, description: 'Kind of entity (user, phone, email, service…)' }),\n value: Type.String({ maxLength: 255, description: 'Identifier of the entity within its type' }),\n },\n {\n title: 'AlertEntityRef',\n description: 'Reference to an entity by kind and identifier',\n additionalProperties: false,\n },\n)\nexport type AlertEntityRefData = Static<typeof alertEntityRefSchema>\n\n// ── alert ─────────────────────────────────────────────────────────────\n\nexport const alertSchema = Type.Object(\n {\n id: Type.Number({ description: 'Unique identifier of the alert' }),\n project_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Project this alert belongs to. Null for an ecosystem-wide alert, the ones created and read through the `/ecosystem/:ecosystemName/alert` mount, which belong to no project',\n }),\n ),\n alert_type_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Type of alert, as a row of the `alert_type` catalog of the ecosystem. Null for an alert that is not classified',\n }),\n ),\n user_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Caaring user the alert is about, and one of the two things that decide who may read it: this user always reads their own alerts. Null when the subject is a patient with no user account, in which case `patient_id` is the one that identifies them',\n }),\n ),\n patient_id: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], {\n description:\n 'Patient the alert is about, as the `id` of the `patient` CRF row of the project, and the other thing that decides who may read it: the professionals with access to that patient do. A patient may exist without a user account, so this is independent of `user_id`. It lives in the project database, so the column carries no foreign key and `project_id` is what says which database to read it from — an alert carrying it always carries a project',\n }),\n ),\n severity: Type.Optional(\n Type.Union([alertSeveritySchema, Type.Null()], { description: 'Severity of the alert. Null when not graded' }),\n ),\n status: Type.Optional(alertStatusSchema),\n title: Type.Optional(\n Type.Union([Type.String({ maxLength: 255 }), Type.Null()], { description: 'Short readable title' }),\n ),\n summary: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: 'Longer readable description' })),\n emitter: alertEmitterSchema,\n recipients: Type.Optional(\n Type.Union([Type.Array(alertEntityRefSchema), Type.Null()], {\n description: 'Natural addressees of the alert. Null or empty when it has none',\n }),\n ),\n related_entities: Type.Optional(\n Type.Union([Type.Array(alertEntityRefSchema), Type.Null()], {\n description: 'Entities related to the alert without being addressees of it',\n }),\n ),\n payload: Type.Optional(\n Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()], {\n description: 'Structured data that originated the alert (measured value, threshold, form answer…)',\n }),\n ),\n metadata: Type.Optional(\n Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()], {\n description: 'Free extension point, so a new consumer does not need a schema change',\n }),\n ),\n dedup_key: Type.Optional(\n Type.String({\n maxLength: 255,\n description:\n 'Fingerprint used to avoid storing the same alert twice. A create carrying a key that is already open in the same scope answers with the stored alert; a resolved alert does not hold its key, so a condition that recurs after being dealt with raises a new one. Optional for the client: the server generates a UUID when it is absent, so the column always carries a usable value',\n }),\n ),\n triggered_at: Type.Optional(\n Type.Union([Type.String(), Type.Null()], {\n description: `When the event that originated the alert happened. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n viewed_at: Type.Optional(\n Type.Union([Type.String(), Type.Null()], {\n description: `When the alert was first viewed. Null while unseen. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n viewed_by: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], { description: 'User who first viewed the alert' }),\n ),\n resolved_at: Type.Optional(\n Type.Union([Type.String(), Type.Null()], {\n description: `When the alert was resolved. Null while active. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n resolved_by: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], { description: 'User who resolved the alert' }),\n ),\n resolution_note: Type.Optional(\n Type.Union([Type.String(), Type.Null()], { description: 'Optional note left when resolving the alert' }),\n ),\n created_user: Type.Optional(\n Type.Union([Type.Number(), Type.Null()], { description: 'User who created the alert, when a user did' }),\n ),\n created_at: Type.Optional(\n Type.String({\n description: `When the row was created. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n mod_time: Type.Optional(\n Type.String({\n description: `When the row was last modified. ${EMITTED_DATETIME_NOTE}`,\n examples: [API_DATETIME_EXAMPLE],\n }),\n ),\n },\n { title: 'Alert', description: 'An alert and its state (ecosystem level)' },\n)\nexport type AlertData = Static<typeof alertSchema>\n\n// Written out rather than derived from `alertSchema`, for the same reason the action-system module\n// spells its request schemas out: an entity schema says what a row holds, a request schema says what\n// a client may send, and only the second can close itself. `Type.Omit` over an entity schema keeps\n// server-owned columns reachable through a body — here `status`, `resolved_*` and `viewed_*` are\n// absent because the server owns them, not because an `Omit` happened to remove them. Resolving\n// goes through its own endpoint; viewing is recorded by the read path.\nexport const createAlertSchema = Type.Object(\n {\n alert_type_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),\n user_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),\n patient_id: Type.Optional(Type.Union([Type.Number(), Type.Null()])),\n severity: Type.Optional(Type.Union([alertSeveritySchema, Type.Null()])),\n title: Type.Optional(Type.Union([Type.String({ maxLength: 255 }), Type.Null()])),\n summary: Type.Optional(Type.Union([Type.String(), Type.Null()])),\n emitter: alertEmitterSchema,\n recipients: Type.Optional(Type.Union([Type.Array(alertEntityRefSchema), Type.Null()])),\n related_entities: Type.Optional(Type.Union([Type.Array(alertEntityRefSchema), Type.Null()])),\n payload: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),\n metadata: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),\n dedup_key: Type.Optional(Type.String({ maxLength: 255 })),\n // `apiDateTime`, so the value reaches the service already a `Date` and no handler converts a\n // string by hand. The wrapped type is the same `format: 'date-time'` as before, so the 200s and\n // 400s of this field do not move — only what the service receives does.\n triggered_at: Type.Optional(\n Type.Union([apiDateTime, Type.Null()], {\n description: `When the event that originated the alert happened. RFC 3339 with a mandatory offset — \\`${API_DATETIME_EXAMPLE}\\` is the shape the API emits, so a value read from here can be sent straight back. ${API_DATETIME_ACCEPTED_NOTE}. Stored as UTC`,\n }),\n ),\n },\n { title: 'CreateAlert', description: 'Payload to create an alert', additionalProperties: false },\n)\n// `StaticDecode`, not `Static`: this type describes what a *handler receives*, and the handler\n// receives the decoded body — `triggered_at` is a `Date` there. `Static` resolves in the encode\n// direction, which is the string that travelled on the wire, so it would describe the value one\n// step earlier than the one this type is used for.\nexport type CreateAlertData = StaticDecode<typeof createAlertSchema>\n\n/**\n * The same payload as it travels on the wire, before the boundary decodes it: `triggered_at` is\n * still the RFC 3339 string a client sends.\n *\n * Only clients of the HTTP interface need this — tests that build a request body, and any caller\n * assembling JSON. Everything on the server side of the validator wants `CreateAlertData`, which is\n * the decoded shape.\n */\nexport type CreateAlertRequestBody = Static<typeof createAlertSchema>\n\n// `emitter` is `Type.Omit`-ted, not just made optional by `Type.Partial`: who raised the alert is\n// fixed at creation the same way `status`/`resolved_*`/`viewed_*` already are (see\n// `createAlertSchema` above) — a client rewriting it through a PATCH would falsify that provenance\n// for an entity that carries clinical data. `Type.Partial` alone would only make the key optional,\n// not absent, so it would still accept and apply a client-supplied `emitter`.\n// `Type.Partial` rebuilds the object and does NOT inherit `additionalProperties`, so it is restated.\nexport const updateAlertSchema = Type.Partial(Type.Omit(createAlertSchema, ['emitter']), {\n title: 'UpdateAlert',\n description: 'Payload to update an alert. Every field is optional. `emitter` cannot be changed after creation',\n additionalProperties: false,\n})\nexport type UpdateAlertData = StaticDecode<typeof updateAlertSchema>\n\n// Resolving is its own endpoint rather than a `status` field on the update: it is\n// the single transition of the lifecycle, and it writes three columns at once\n// (`status`, `resolved_at`, `resolved_by`) that must never disagree. A plain\n// `PATCH {status: 'resolved'}` would let a client set the state without the two\n// columns that say who closed it and when.\nexport const resolveAlertSchema = Type.Object(\n {\n resolution_note: Type.Optional(\n Type.Union([Type.String(), Type.Null()], { description: 'Optional note explaining the resolution' }),\n ),\n },\n { title: 'ResolveAlert', description: 'Payload to resolve an alert', additionalProperties: false },\n)\nexport type ResolveAlertData = Static<typeof resolveAlertSchema>\n\n// Filters of the list endpoint, on top of the pagination query. Everything is\n// optional: with none of them the endpoint lists the project's alerts.\nexport const listAlertQuerySchema = Type.Object(\n {\n status: Type.Optional(alertStatusSchema),\n severity: Type.Optional(alertSeveritySchema),\n user_id: Type.Optional(Type.Number({ description: 'Filter by the user the alert is about' })),\n patient_id: Type.Optional(Type.Number({ description: 'Filter by the patient the alert is about' })),\n alert_type_id: Type.Optional(Type.Number({ description: 'Filter by the type of the alert' })),\n },\n { title: 'ListAlertQuery', description: 'Filters available on the alert list' },\n)\nexport type ListAlertQuery = Static<typeof listAlertQuerySchema>\n"],"names":[],"mappings":";;AAwBO,MAAM,cAAc;AAAA,EACzB,QAAQ;AAAA,EACR,UAAU;AACZ;AACO,MAAM,oBAAoB,KAAK,KAAK,OAAO,OAAO,WAAW,GAAG;AAAA,EACrE,OAAO;AAAA,EACP,aAAa;AACf,CAAC;AAGM,MAAM,gBAAgB;AAAA,EAC3B,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AACZ;AACO,MAAM,sBAAsB,KAAK,KAAK,OAAO,OAAO,aAAa,GAAG;AAAA,EACzE,OAAO;AAAA,EACP,aAAa;AACf,CAAC;AAOM,MAAM,mBAAmB;AAAA,EAC9B,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AACX;AACO,MAAM,yBAAyB,KAAK,KAAK,OAAO,OAAO,gBAAgB,GAAG;AAAA,EAC/E,OAAO;AAAA,EACP,aAAa;AACf,CAAC;AAKM,MAAM,qBAAqB,KAAK;AAAA,EACrC;AAAA,IACE,MAAM;AAAA,IACN,IAAI,KAAK;AAAA,MACP,KAAK,MAAM,CAAC,KAAK,OAAO,EAAE,WAAW,KAAK,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QACzD,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,IAEH,MAAM,KAAK;AAAA,MACT,KAAK,MAAM,CAAC,KAAK,OAAO,EAAE,WAAW,KAAK,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QACzD,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,IAEH,WAAW,KAAK;AAAA,MACd,KAAK,MAAM,CAAC,KAAK,OAAO,EAAE,WAAW,KAAK,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QACzD,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,EACH;AAAA,EAEF,EAAE,OAAO,gBAAgB,aAAa,wBAAwB,sBAAsB,MAAA;AACtF;AAWO,MAAM,uBAAuB,KAAK;AAAA,EACvC;AAAA,IACE,MAAM,KAAK,OAAO,EAAE,WAAW,IAAI,aAAa,iDAAiD;AAAA,IACjG,OAAO,KAAK,OAAO,EAAE,WAAW,KAAK,aAAa,4CAA4C;AAAA,EAAA;AAAA,EAEhG;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,sBAAsB;AAAA,EAAA;AAE1B;AAKO,MAAM,cAAc,KAAK;AAAA,EAC9B;AAAA,IACE,IAAI,KAAK,OAAO,EAAE,aAAa,kCAAkC;AAAA,IACjE,YAAY,KAAK;AAAA,MACf,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aACE;AAAA,MAAA,CACH;AAAA,IAAA;AAAA,IAEH,eAAe,KAAK;AAAA,MAClB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aACE;AAAA,MAAA,CACH;AAAA,IAAA;AAAA,IAEH,SAAS,KAAK;AAAA,MACZ,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aACE;AAAA,MAAA,CACH;AAAA,IAAA;AAAA,IAEH,YAAY,KAAK;AAAA,MACf,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aACE;AAAA,MAAA,CACH;AAAA,IAAA;AAAA,IAEH,UAAU,KAAK;AAAA,MACb,KAAK,MAAM,CAAC,qBAAqB,KAAK,MAAM,GAAG,EAAE,aAAa,8CAAA,CAA+C;AAAA,IAAA;AAAA,IAE/G,QAAQ,KAAK,SAAS,iBAAiB;AAAA,IACvC,OAAO,KAAK;AAAA,MACV,KAAK,MAAM,CAAC,KAAK,OAAO,EAAE,WAAW,KAAK,GAAG,KAAK,KAAA,CAAM,GAAG,EAAE,aAAa,wBAAwB;AAAA,IAAA;AAAA,IAEpG,SAAS,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,OAAA,GAAU,KAAK,MAAM,GAAG,EAAE,aAAa,8BAAA,CAA+B,CAAC;AAAA,IAC/G,SAAS;AAAA,IACT,YAAY,KAAK;AAAA,MACf,KAAK,MAAM,CAAC,KAAK,MAAM,oBAAoB,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QAC1D,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,IAEH,kBAAkB,KAAK;AAAA,MACrB,KAAK,MAAM,CAAC,KAAK,MAAM,oBAAoB,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QAC1D,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,IAEH,SAAS,KAAK;AAAA,MACZ,KAAK,MAAM,CAAC,KAAK,OAAO,KAAK,UAAU,KAAK,SAAS,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QACpE,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,IAEH,UAAU,KAAK;AAAA,MACb,KAAK,MAAM,CAAC,KAAK,OAAO,KAAK,UAAU,KAAK,SAAS,GAAG,KAAK,KAAA,CAAM,GAAG;AAAA,QACpE,aAAa;AAAA,MAAA,CACd;AAAA,IAAA;AAAA,IAEH,WAAW,KAAK;AAAA,MACd,KAAK,OAAO;AAAA,QACV,WAAW;AAAA,QACX,aACE;AAAA,MAAA,CACH;AAAA,IAAA;AAAA,IAEH,cAAc,KAAK;AAAA,MACjB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aAAa,sDAAsD,qBAAqB;AAAA,QACxF,UAAU,CAAC,oBAAoB;AAAA,MAAA,CAChC;AAAA,IAAA;AAAA,IAEH,WAAW,KAAK;AAAA,MACd,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aAAa,uDAAuD,qBAAqB;AAAA,QACzF,UAAU,CAAC,oBAAoB;AAAA,MAAA,CAChC;AAAA,IAAA;AAAA,IAEH,WAAW,KAAK;AAAA,MACd,KAAK,MAAM,CAAC,KAAK,OAAA,GAAU,KAAK,MAAM,GAAG,EAAE,aAAa,mCAAmC;AAAA,IAAA;AAAA,IAE7F,aAAa,KAAK;AAAA,MAChB,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,GAAG;AAAA,QACvC,aAAa,mDAAmD,qBAAqB;AAAA,QACrF,UAAU,CAAC,oBAAoB;AAAA,MAAA,CAChC;AAAA,IAAA;AAAA,IAEH,aAAa,KAAK;AAAA,MAChB,KAAK,MAAM,CAAC,KAAK,OAAA,GAAU,KAAK,MAAM,GAAG,EAAE,aAAa,+BAA+B;AAAA,IAAA;AAAA,IAEzF,iBAAiB,KAAK;AAAA,MACpB,KAAK,MAAM,CAAC,KAAK,OAAA,GAAU,KAAK,MAAM,GAAG,EAAE,aAAa,+CAA+C;AAAA,IAAA;AAAA,IAEzG,cAAc,KAAK;AAAA,MACjB,KAAK,MAAM,CAAC,KAAK,OAAA,GAAU,KAAK,MAAM,GAAG,EAAE,aAAa,+CAA+C;AAAA,IAAA;AAAA,IAEzG,YAAY,KAAK;AAAA,MACf,KAAK,OAAO;AAAA,QACV,aAAa,6BAA6B,qBAAqB;AAAA,QAC/D,UAAU,CAAC,oBAAoB;AAAA,MAAA,CAChC;AAAA,IAAA;AAAA,IAEH,UAAU,KAAK;AAAA,MACb,KAAK,OAAO;AAAA,QACV,aAAa,mCAAmC,qBAAqB;AAAA,QACrE,UAAU,CAAC,oBAAoB;AAAA,MAAA,CAChC;AAAA,IAAA;AAAA,EACH;AAAA,EAEF,EAAE,OAAO,SAAS,aAAa,2CAAA;AACjC;AASO,MAAM,oBAAoB,KAAK;AAAA,EACpC;AAAA,IACE,eAAe,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IACrE,SAAS,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAC/D,YAAY,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAClE,UAAU,KAAK,SAAS,KAAK,MAAM,CAAC,qBAAqB,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IACtE,OAAO,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,OAAO,EAAE,WAAW,KAAK,GAAG,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAC/E,SAAS,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAC/D,SAAS;AAAA,IACT,YAAY,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,MAAM,oBAAoB,GAAG,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IACrF,kBAAkB,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,MAAM,oBAAoB,GAAG,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAC3F,SAAS,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,OAAO,KAAK,UAAU,KAAK,SAAS,GAAG,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAC5F,UAAU,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,OAAO,KAAK,UAAU,KAAK,SAAS,GAAG,KAAK,KAAA,CAAM,CAAC,CAAC;AAAA,IAC7F,WAAW,KAAK,SAAS,KAAK,OAAO,EAAE,WAAW,IAAA,CAAK,CAAC;AAAA;AAAA;AAAA;AAAA,IAIxD,cAAc,KAAK;AAAA,MACjB,KAAK,MAAM,CAAC,aAAa,KAAK,KAAA,CAAM,GAAG;AAAA,QACrC,aAAa,2FAA2F,oBAAoB,uFAAuF,0BAA0B;AAAA,MAAA,CAC9O;AAAA,IAAA;AAAA,EACH;AAAA,EAEF,EAAE,OAAO,eAAe,aAAa,8BAA8B,sBAAsB,MAAA;AAC3F;AAuBO,MAAM,oBAAoB,KAAK,QAAQ,KAAK,KAAK,mBAAmB,CAAC,SAAS,CAAC,GAAG;AAAA,EACvF,OAAO;AAAA,EACP,aAAa;AAAA,EACb,sBAAsB;AACxB,CAAC;AAQM,MAAM,qBAAqB,KAAK;AAAA,EACrC;AAAA,IACE,iBAAiB,KAAK;AAAA,MACpB,KAAK,MAAM,CAAC,KAAK,OAAA,GAAU,KAAK,MAAM,GAAG,EAAE,aAAa,2CAA2C;AAAA,IAAA;AAAA,EACrG;AAAA,EAEF,EAAE,OAAO,gBAAgB,aAAa,+BAA+B,sBAAsB,MAAA;AAC7F;AAKO,MAAM,uBAAuB,KAAK;AAAA,EACvC;AAAA,IACE,QAAQ,KAAK,SAAS,iBAAiB;AAAA,IACvC,UAAU,KAAK,SAAS,mBAAmB;AAAA,IAC3C,SAAS,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,wCAAA,CAAyC,CAAC;AAAA,IAC5F,YAAY,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,2CAAA,CAA4C,CAAC;AAAA,IAClG,eAAe,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mCAAmC,CAAC;AAAA,EAAA;AAAA,EAE9F,EAAE,OAAO,kBAAkB,aAAa,sCAAA;AAC1C;"}