evo360-types 1.3.409 → 1.3.413

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.
@@ -0,0 +1,389 @@
1
+ import { z } from "zod";
2
+ import { zFireDocSchema } from "../shared/zod-schemas";
3
+
4
+ // ======================================================
5
+ // evo-campaigns (feat-081 — Hub Omni Campaigns) — zod schemas
6
+ //
7
+ // Doc schemas mirror the hand-written interfaces in
8
+ // types/evo-campaigns/index.ts. Request schemas validate the admin HTTP surface
9
+ // (evo_campaigns_admin). passthrough() everywhere additive fields may appear.
10
+ // ======================================================
11
+
12
+ // ----- Enums ----------------------------------------------------------------
13
+ export const zCampaignStatusSchema = z.enum([
14
+ "draft",
15
+ "scheduled",
16
+ "running",
17
+ "paused",
18
+ "finished",
19
+ "canceled",
20
+ "finished_error",
21
+ ]);
22
+ export type ICampaignStatus = z.infer<typeof zCampaignStatusSchema>;
23
+
24
+ export const zCampaignTypeSchema = z.enum([
25
+ "conversion",
26
+ "informative",
27
+ "relationship",
28
+ "operational",
29
+ "promotional",
30
+ ]);
31
+
32
+ export const zCampaignChannelSchema = z.enum(["whatsapp", "email"]);
33
+
34
+ export const zCampaignVariableSourceSchema = z.enum([
35
+ "placeholder",
36
+ "fixed",
37
+ "csv_column",
38
+ "qr_link",
39
+ ]);
40
+
41
+ export const zRecipientDispatchStateSchema = z.enum([
42
+ "pending",
43
+ "dispatched",
44
+ "done",
45
+ "excluded",
46
+ ]);
47
+
48
+ export const zRecipientChannelStatusSchema = z.enum([
49
+ "pending",
50
+ "scheduled",
51
+ "sent",
52
+ "delivered",
53
+ "read",
54
+ "failed",
55
+ "clicked",
56
+ "replied",
57
+ ]);
58
+
59
+ export const zRecipientInteractionStatusSchema = z.enum([
60
+ "pendente",
61
+ "inelegivel",
62
+ "agendado",
63
+ "enviado",
64
+ "entregue",
65
+ "lido",
66
+ "clicou",
67
+ "respondeu",
68
+ "convertido",
69
+ "falhou",
70
+ "optout",
71
+ "cancelado",
72
+ ]);
73
+
74
+ // ----- Shared value objects -------------------------------------------------
75
+ export const zCampaignCodeNameSchema = z
76
+ .object({
77
+ code: z.string(),
78
+ name: z.string(),
79
+ })
80
+ .passthrough();
81
+
82
+ export const zCampaignVariableMappingSchema = z
83
+ .object({
84
+ variable: z.string(),
85
+ source: zCampaignVariableSourceSchema,
86
+ token: z.string().optional(),
87
+ value: z.string().optional(),
88
+ column: z.string().optional(),
89
+ cta_id: z.string().optional(),
90
+ })
91
+ .passthrough();
92
+
93
+ export const zCampaignCtaSchema = z
94
+ .object({
95
+ cta_id: z.string(),
96
+ label: z.string(),
97
+ button_index: z.number().optional(),
98
+ target_url: z.string().optional(),
99
+ })
100
+ .passthrough();
101
+
102
+ export const zCampaignPacingSchema = z
103
+ .object({
104
+ batch_size: z.number(),
105
+ batch_interval_minutes: z.number(),
106
+ daily_limit: z.number(),
107
+ allowed_window: z
108
+ .object({
109
+ start: z.string(),
110
+ end: z.string(),
111
+ })
112
+ .passthrough(),
113
+ })
114
+ .passthrough();
115
+
116
+ export const zCampaignTicketPolicySchema = z
117
+ .object({
118
+ mode: z.enum(["close", "keep_open"]),
119
+ close_status: zCampaignCodeNameSchema.optional(),
120
+ note: z.string().optional(),
121
+ })
122
+ .passthrough();
123
+
124
+ export const zCampaignMediaSchema = z
125
+ .object({
126
+ type: z.string(),
127
+ gcs_path: z.string().optional(),
128
+ url: z.string().optional(),
129
+ })
130
+ .passthrough();
131
+
132
+ export const zCampaignChannelConfigSchema = z
133
+ .object({
134
+ channel_id: z.string(),
135
+ template_id: z.string(),
136
+ template_language: z.string().optional(),
137
+ template_category: z.string().optional(),
138
+ variable_mappings: z.array(zCampaignVariableMappingSchema),
139
+ media: zCampaignMediaSchema.optional(),
140
+ ctas: z.array(zCampaignCtaSchema),
141
+ department_id: z.string().optional(),
142
+ ticket_policy: zCampaignTicketPolicySchema,
143
+ pacing: zCampaignPacingSchema,
144
+ subject_override: z.string().optional(),
145
+ preheader: z.string().optional(),
146
+ })
147
+ .passthrough();
148
+
149
+ export const zCampaignAttributionSchema = z
150
+ .object({
151
+ window_days: z.number(),
152
+ conversion_event: z.string(),
153
+ appointment_status_base_in: z.array(z.string()),
154
+ requires: z.enum(["delivery", "click", "reply"]),
155
+ })
156
+ .passthrough();
157
+
158
+ export const zCampaignNewLeadConfigSchema = z
159
+ .object({
160
+ dist_channel: zCampaignCodeNameSchema,
161
+ qualification: z
162
+ .object({
163
+ code: z.string(),
164
+ name: z.string(),
165
+ funnelLevel: z.union([z.string(), z.number()]),
166
+ })
167
+ .passthrough()
168
+ .nullable()
169
+ .optional(),
170
+ tags: z.array(z.string()).optional(),
171
+ })
172
+ .passthrough();
173
+
174
+ export const zCampaignFrequencySchema = z
175
+ .object({
176
+ min_days: z.number(),
177
+ override: z.boolean(),
178
+ })
179
+ .passthrough();
180
+
181
+ export const zCampaignScheduleSchema = z
182
+ .object({
183
+ mode: z.enum(["immediate", "scheduled"]),
184
+ scheduled_at: z.coerce.date().nullable().optional(),
185
+ })
186
+ .passthrough();
187
+
188
+ // ----- Campaign doc ---------------------------------------------------------
189
+ export const zCampaignSchema = zFireDocSchema
190
+ .extend({
191
+ name: z.string(),
192
+ description: z.string().nullable().optional(),
193
+ purpose: zCampaignCodeNameSchema,
194
+ objective: zCampaignCodeNameSchema,
195
+ campaign_type: zCampaignTypeSchema,
196
+ tags: z.array(z.string()).optional(),
197
+ status: zCampaignStatusSchema,
198
+ attribution_open: z.boolean(),
199
+ attribution_deadline: z.coerce.date().nullable().optional(),
200
+ attribution: zCampaignAttributionSchema,
201
+ channels: z
202
+ .object({
203
+ whatsapp: zCampaignChannelConfigSchema.optional(),
204
+ email: zCampaignChannelConfigSchema.optional(),
205
+ })
206
+ .passthrough(),
207
+ new_lead_config: zCampaignNewLeadConfigSchema.optional(),
208
+ frequency: zCampaignFrequencySchema,
209
+ schedule: zCampaignScheduleSchema,
210
+ dispatch_state: z.record(z.unknown()).optional(),
211
+ prevalidation: z.record(z.unknown()).optional(),
212
+ cost_estimate: z.record(z.unknown()).optional(),
213
+ counters: z.record(z.unknown()).optional(),
214
+ })
215
+ .passthrough();
216
+
217
+ // ----- Bucket doc -----------------------------------------------------------
218
+ export const zCampaignBucketSourceTypeSchema = z.enum([
219
+ "leads",
220
+ "patients",
221
+ "appointments",
222
+ "csv",
223
+ ]);
224
+
225
+ export const zCampaignBucketSchema = zFireDocSchema
226
+ .extend({
227
+ source_type: zCampaignBucketSourceTypeSchema,
228
+ label: z.string(),
229
+ filters: z.record(z.unknown()).optional(),
230
+ csv_file: z.record(z.unknown()).optional(),
231
+ counts: z.record(z.unknown()).optional(),
232
+ })
233
+ .passthrough();
234
+
235
+ // ----- Recipient doc --------------------------------------------------------
236
+ export const zCampaignRecipientSchema = zFireDocSchema
237
+ .extend({
238
+ lead_id: z.string().optional(),
239
+ patient_id: z.string().optional(),
240
+ is_new_lead: z.boolean(),
241
+ contact: z
242
+ .object({
243
+ display_name: z.string().optional(),
244
+ mobile: z.string().optional(),
245
+ email: z.string().optional(),
246
+ external_id: z.string().optional(),
247
+ })
248
+ .passthrough(),
249
+ bucket_ids: z.array(z.string()),
250
+ primary_bucket_id: z.string(),
251
+ csv_variables: z.record(z.string()).optional(),
252
+ dispatch_state: zRecipientDispatchStateSchema,
253
+ eligibility: z.record(z.unknown()),
254
+ channels: z.record(z.unknown()),
255
+ interaction_status: zRecipientInteractionStatusSchema,
256
+ converted_at: z.coerce.date().nullable().optional(),
257
+ conversion_id: z.string().nullable().optional(),
258
+ })
259
+ .passthrough();
260
+
261
+ // ----- Conversion doc -------------------------------------------------------
262
+ export const zCampaignConversionSchema = zFireDocSchema
263
+ .extend({
264
+ lead_id: z.string(),
265
+ recipient_id: z.string(),
266
+ conversion_event: z.string(),
267
+ source_entity_type: z.enum(["appointment", "qr_action", "ticket", "message"]),
268
+ source_entity_id: z.string(),
269
+ channel: zCampaignChannelSchema.optional(),
270
+ cta_id: z.string().optional(),
271
+ attributed_at: z.coerce.date(),
272
+ attribution_model: z.literal("last_campaign_in_window"),
273
+ window_days: z.number(),
274
+ metadata: z.record(z.unknown()).optional(),
275
+ })
276
+ .passthrough();
277
+
278
+ // ----- Lead campaign-history entry ------------------------------------------
279
+ export const zLeadCampaignHistoryEntrySchema = zFireDocSchema
280
+ .extend({
281
+ campaign_id: z.string(),
282
+ campaign_name: z.string(),
283
+ classification_level: z.string(),
284
+ channels_sent: z.array(zCampaignChannelSchema),
285
+ template_ids: z.record(z.string()).optional(),
286
+ bucket_ids: z.array(z.string()).optional(),
287
+ first_sent_at: z.coerce.date().nullable().optional(),
288
+ last_sent_at: z.coerce.date().nullable().optional(),
289
+ delivered_at: z.coerce.date().nullable().optional(),
290
+ read_at: z.coerce.date().nullable().optional(),
291
+ clicked_at: z.coerce.date().nullable().optional(),
292
+ replied_at: z.coerce.date().nullable().optional(),
293
+ converted_at: z.coerce.date().nullable().optional(),
294
+ interaction_status: zRecipientInteractionStatusSchema,
295
+ })
296
+ .passthrough();
297
+
298
+ // ----- Tariff dic item ------------------------------------------------------
299
+ export const zTariffItemSchema = z
300
+ .object({
301
+ code: z.string(),
302
+ name: z.string(),
303
+ description: z.string().optional(),
304
+ context: z.string(),
305
+ channel: z.string().optional(),
306
+ provider: z.string().optional(),
307
+ country: z.string().optional(),
308
+ category: z.string().optional(),
309
+ currency: z.string(),
310
+ calc_type: z.enum(["fixed", "percentage"]),
311
+ value: z.number(),
312
+ percent_base: z.string().optional(),
313
+ valid_from: z.coerce.date(),
314
+ valid_to: z.coerce.date().nullable().optional(),
315
+ active: z.boolean(),
316
+ metadata: z.record(z.unknown()).optional(),
317
+ })
318
+ .passthrough();
319
+
320
+ // ======================================================
321
+ // Request schemas (admin HTTP surface)
322
+ // ======================================================
323
+
324
+ /** POST /campaigns, PATCH /campaigns/:id — draft create/edit. */
325
+ export const zCampaignUpsert = z
326
+ .object({
327
+ name: z.string().min(1),
328
+ description: z.string().nullable().optional(),
329
+ purpose: zCampaignCodeNameSchema,
330
+ objective: zCampaignCodeNameSchema,
331
+ campaign_type: zCampaignTypeSchema,
332
+ tags: z.array(z.string()).optional(),
333
+ attribution: zCampaignAttributionSchema.partial().optional(),
334
+ channels: z
335
+ .object({
336
+ whatsapp: zCampaignChannelConfigSchema.optional(),
337
+ email: zCampaignChannelConfigSchema.optional(),
338
+ })
339
+ .passthrough()
340
+ .optional(),
341
+ new_lead_config: zCampaignNewLeadConfigSchema.optional(),
342
+ frequency: zCampaignFrequencySchema.partial().optional(),
343
+ schedule: zCampaignScheduleSchema.optional(),
344
+ })
345
+ .passthrough();
346
+ export type ICampaignUpsert = z.infer<typeof zCampaignUpsert>;
347
+
348
+ /** POST /campaigns/:id/buckets — create a bucket with filters. */
349
+ export const zBucketCreate = z
350
+ .object({
351
+ source_type: zCampaignBucketSourceTypeSchema,
352
+ label: z.string().min(1),
353
+ filters: z.record(z.unknown()).optional(),
354
+ })
355
+ .passthrough();
356
+ export type IBucketCreate = z.infer<typeof zBucketCreate>;
357
+
358
+ /** POST /campaigns/:id/buckets/csv — upload metadata (file itself is base64 in body). */
359
+ export const zCsvUploadMeta = z
360
+ .object({
361
+ label: z.string().min(1),
362
+ filename: z.string().min(1),
363
+ file_base64: z.string(),
364
+ /** CSV column -> semantic field mapping (name/mobile/email/variables). */
365
+ mapping: z.record(z.string()),
366
+ })
367
+ .passthrough();
368
+ export type ICsvUploadMeta = z.infer<typeof zCsvUploadMeta>;
369
+
370
+ /** Prevalidation snapshot returned by GET /campaigns/:id/prevalidation. */
371
+ export const zPrevalidationResult = z
372
+ .object({
373
+ status: z.enum(["approved", "warnings", "blocked"]),
374
+ errors: z.array(z.string()),
375
+ warnings: z.array(z.string()),
376
+ ran_at: z.coerce.date().optional(),
377
+ })
378
+ .passthrough();
379
+ export type IPrevalidationResult = z.infer<typeof zPrevalidationResult>;
380
+
381
+ /** POST /campaigns/:id/{schedule|start|pause|resume|cancel}. */
382
+ export const zCampaignAction = z
383
+ .object({
384
+ action: z.enum(["schedule", "start", "pause", "resume", "cancel"]),
385
+ scheduled_at: z.coerce.date().nullable().optional(),
386
+ reason: z.string().optional(),
387
+ })
388
+ .passthrough();
389
+ export type ICampaignAction = z.infer<typeof zCampaignAction>;