tychat-contracts 1.0.74 → 1.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connections/index.d.ts +1 -0
- package/dist/connections/index.d.ts.map +1 -1
- package/dist/connections/index.js +1 -0
- package/dist/connections/whatsapp-official.dto.d.ts +31 -0
- package/dist/connections/whatsapp-official.dto.d.ts.map +1 -0
- package/dist/connections/whatsapp-official.dto.js +149 -0
- package/dist/conversations/followup-config-response.dto.d.ts +7 -1
- package/dist/conversations/followup-config-response.dto.d.ts.map +1 -1
- package/dist/conversations/followup-config-response.dto.js +73 -7
- package/dist/conversations/followup-events.dto.d.ts +30 -0
- package/dist/conversations/followup-events.dto.d.ts.map +1 -0
- package/dist/conversations/followup-events.dto.js +162 -0
- package/dist/conversations/followup-log-response.dto.d.ts +0 -1
- package/dist/conversations/followup-log-response.dto.d.ts.map +1 -1
- package/dist/conversations/followup-log-response.dto.js +0 -7
- package/dist/conversations/index.d.ts +2 -0
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/index.js +2 -0
- package/dist/conversations/satisfaction-response.dto.d.ts +36 -0
- package/dist/conversations/satisfaction-response.dto.d.ts.map +1 -0
- package/dist/conversations/satisfaction-response.dto.js +149 -0
- package/dist/tenants/index.d.ts +2 -0
- package/dist/tenants/index.d.ts.map +1 -1
- package/dist/tenants/index.js +2 -0
- package/dist/tenants/tenant-slug.util.d.ts +46 -0
- package/dist/tenants/tenant-slug.util.d.ts.map +1 -0
- package/dist/tenants/tenant-slug.util.js +59 -0
- package/dist/tenants/tenant-slug.util.spec.d.ts +2 -0
- package/dist/tenants/tenant-slug.util.spec.d.ts.map +1 -0
- package/dist/tenants/tenant-slug.util.spec.js +102 -0
- package/dist/tenants/whatsapp-provider-kind.dto.d.ts +9 -0
- package/dist/tenants/whatsapp-provider-kind.dto.d.ts.map +1 -0
- package/dist/tenants/whatsapp-provider-kind.dto.js +13 -0
- package/package.json +2 -1
- package/src/connections/index.ts +1 -0
- package/src/connections/whatsapp-official.dto.ts +113 -0
- package/src/conversations/followup-config-response.dto.ts +60 -5
- package/src/conversations/followup-events.dto.ts +111 -0
- package/src/conversations/followup-log-response.dto.ts +0 -5
- package/src/conversations/index.ts +2 -0
- package/src/conversations/satisfaction-response.dto.ts +116 -0
- package/src/tenants/index.ts +2 -0
- package/src/tenants/tenant-slug.util.ts +56 -0
- package/src/tenants/whatsapp-provider-kind.dto.ts +12 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { IsBoolean, IsISO8601, IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
export const TOPIC_MESSAGE_SENT = 'MESSAGE_SENT';
|
|
5
|
+
export const TOPIC_MESSAGE_RECEIVED = 'MESSAGE_RECEIVED';
|
|
6
|
+
export const TOPIC_FOLLOWUP_CONFIG_UPDATED = 'FOLLOWUP_CONFIG_UPDATED';
|
|
7
|
+
export const TOPIC_APPOINTMENT_CHANGED = 'APPOINTMENT_CHANGED';
|
|
8
|
+
|
|
9
|
+
export class FollowupConversationEventPayload {
|
|
10
|
+
@ApiProperty({ example: 'homolog' })
|
|
11
|
+
@IsString()
|
|
12
|
+
@MinLength(1)
|
|
13
|
+
@MaxLength(255)
|
|
14
|
+
tenant: string;
|
|
15
|
+
|
|
16
|
+
@ApiProperty({ example: '3c593727-4505-495a-a054-c052acb4cf19' })
|
|
17
|
+
@IsString()
|
|
18
|
+
@MinLength(1)
|
|
19
|
+
@MaxLength(255)
|
|
20
|
+
patientId: string;
|
|
21
|
+
|
|
22
|
+
@ApiProperty({ example: 'f3f50ecb-fb71-4d4b-a0dd-910ef6e3fd0a' })
|
|
23
|
+
@IsUUID()
|
|
24
|
+
sessionUuid: string;
|
|
25
|
+
|
|
26
|
+
@ApiProperty({ required: false, example: 1 })
|
|
27
|
+
@IsOptional()
|
|
28
|
+
unitId?: number;
|
|
29
|
+
|
|
30
|
+
@ApiProperty({ example: 'ia' })
|
|
31
|
+
@IsString()
|
|
32
|
+
@MinLength(1)
|
|
33
|
+
@MaxLength(50)
|
|
34
|
+
messageType: 'ia' | 'human' | 'patient';
|
|
35
|
+
|
|
36
|
+
@ApiProperty({ required: false, example: false })
|
|
37
|
+
@IsOptional()
|
|
38
|
+
@IsBoolean()
|
|
39
|
+
isFollowup?: boolean;
|
|
40
|
+
|
|
41
|
+
@ApiProperty({ example: '2026-03-30T12:03:14.000Z' })
|
|
42
|
+
@IsISO8601()
|
|
43
|
+
occurredAt: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class FollowupConfigUpdatedEventPayload {
|
|
47
|
+
@ApiProperty({ example: 'homolog' })
|
|
48
|
+
@IsString()
|
|
49
|
+
@MinLength(1)
|
|
50
|
+
@MaxLength(255)
|
|
51
|
+
tenant: string;
|
|
52
|
+
|
|
53
|
+
@ApiProperty({ required: false, example: 1 })
|
|
54
|
+
@IsOptional()
|
|
55
|
+
unitId?: number;
|
|
56
|
+
|
|
57
|
+
@ApiProperty({
|
|
58
|
+
description: 'Serialized follow-up configuration snapshot stored in cache.',
|
|
59
|
+
type: Object,
|
|
60
|
+
})
|
|
61
|
+
config: Record<string, unknown>;
|
|
62
|
+
|
|
63
|
+
@ApiProperty({ example: '2026-03-30T12:03:14.000Z' })
|
|
64
|
+
@IsISO8601()
|
|
65
|
+
occurredAt: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class AppointmentChangedEventPayload {
|
|
69
|
+
@ApiProperty({ example: 'homolog' })
|
|
70
|
+
@IsString()
|
|
71
|
+
@MinLength(1)
|
|
72
|
+
@MaxLength(255)
|
|
73
|
+
tenant: string;
|
|
74
|
+
|
|
75
|
+
@ApiProperty({ example: 'f3f50ecb-fb71-4d4b-a0dd-910ef6e3fd0a' })
|
|
76
|
+
@IsUUID()
|
|
77
|
+
appointmentId: string;
|
|
78
|
+
|
|
79
|
+
@ApiProperty({ example: '3c593727-4505-495a-a054-c052acb4cf19' })
|
|
80
|
+
@IsString()
|
|
81
|
+
@MinLength(1)
|
|
82
|
+
@MaxLength(255)
|
|
83
|
+
patientId: string;
|
|
84
|
+
|
|
85
|
+
@ApiProperty({ example: 'sended' })
|
|
86
|
+
@IsString()
|
|
87
|
+
@MinLength(1)
|
|
88
|
+
@MaxLength(50)
|
|
89
|
+
status: string;
|
|
90
|
+
|
|
91
|
+
@ApiProperty({ required: false, example: 'pending' })
|
|
92
|
+
@IsOptional()
|
|
93
|
+
@IsString()
|
|
94
|
+
@MaxLength(50)
|
|
95
|
+
previousStatus?: string;
|
|
96
|
+
|
|
97
|
+
@ApiProperty({ required: false, example: '2026-03-30T15:00:00.000Z' })
|
|
98
|
+
@IsOptional()
|
|
99
|
+
@IsISO8601()
|
|
100
|
+
date?: string;
|
|
101
|
+
|
|
102
|
+
@ApiProperty({ required: false, example: '2026-03-30T16:00:00.000Z' })
|
|
103
|
+
@IsOptional()
|
|
104
|
+
@IsISO8601()
|
|
105
|
+
exitTime?: string;
|
|
106
|
+
|
|
107
|
+
@ApiProperty({ example: '2026-03-30T12:03:14.000Z' })
|
|
108
|
+
@IsISO8601()
|
|
109
|
+
occurredAt: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
@@ -12,11 +12,6 @@ export class FollowupLogResponseDto {
|
|
|
12
12
|
@IsNotEmpty()
|
|
13
13
|
id: string;
|
|
14
14
|
|
|
15
|
-
@ApiProperty({ description: 'Tenant slug', example: 'clinic-alpha' })
|
|
16
|
-
@IsString()
|
|
17
|
-
@IsNotEmpty()
|
|
18
|
-
tenant: string;
|
|
19
|
-
|
|
20
15
|
@ApiPropertyOptional({ description: 'Session UUID from conversation_sessions (null for appointment-based)' })
|
|
21
16
|
@IsOptional()
|
|
22
17
|
@IsString()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import {
|
|
3
|
+
IsIn,
|
|
4
|
+
IsInt,
|
|
5
|
+
IsNotEmpty,
|
|
6
|
+
IsOptional,
|
|
7
|
+
IsString,
|
|
8
|
+
Max,
|
|
9
|
+
MaxLength,
|
|
10
|
+
Min,
|
|
11
|
+
} from 'class-validator';
|
|
12
|
+
|
|
13
|
+
/** Valid satisfaction survey types. */
|
|
14
|
+
export const SATISFACTION_TYPES = [
|
|
15
|
+
'satisfaction_booking',
|
|
16
|
+
'satisfaction_finished',
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type SatisfactionTypeDto = (typeof SATISFACTION_TYPES)[number];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Payload to save a patient's satisfaction rating.
|
|
23
|
+
* Sent from the conversation-service to the followup-service
|
|
24
|
+
* when the AI detects a numeric satisfaction response.
|
|
25
|
+
*/
|
|
26
|
+
export class SaveSatisfactionRequestDto {
|
|
27
|
+
@ApiProperty({ description: 'Tenant slug', example: 'clinic-alpha' })
|
|
28
|
+
@IsString()
|
|
29
|
+
@IsNotEmpty()
|
|
30
|
+
tenant: string;
|
|
31
|
+
|
|
32
|
+
@ApiProperty({ description: 'Patient UUID' })
|
|
33
|
+
@IsString()
|
|
34
|
+
@IsNotEmpty()
|
|
35
|
+
patientId: string;
|
|
36
|
+
|
|
37
|
+
@ApiProperty({ description: 'Appointment UUID linked to the satisfaction survey' })
|
|
38
|
+
@IsString()
|
|
39
|
+
@IsNotEmpty()
|
|
40
|
+
appointmentId: string;
|
|
41
|
+
|
|
42
|
+
@ApiProperty({
|
|
43
|
+
description: 'Type of satisfaction survey',
|
|
44
|
+
enum: SATISFACTION_TYPES,
|
|
45
|
+
example: 'satisfaction_booking',
|
|
46
|
+
})
|
|
47
|
+
@IsIn(SATISFACTION_TYPES)
|
|
48
|
+
satisfactionType: SatisfactionTypeDto;
|
|
49
|
+
|
|
50
|
+
@ApiProperty({ description: 'Patient satisfaction rating (1-5)', example: 4 })
|
|
51
|
+
@IsInt()
|
|
52
|
+
@Min(1)
|
|
53
|
+
@Max(5)
|
|
54
|
+
rating: number;
|
|
55
|
+
|
|
56
|
+
@ApiPropertyOptional({ description: 'Optional text comment from the patient', example: 'Otimo atendimento!' })
|
|
57
|
+
@IsOptional()
|
|
58
|
+
@IsString()
|
|
59
|
+
@MaxLength(2000)
|
|
60
|
+
comment?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Response DTO for a saved satisfaction rating.
|
|
65
|
+
*/
|
|
66
|
+
export class SatisfactionResponseDto {
|
|
67
|
+
@ApiProperty({ description: 'Primary key (UUID)' })
|
|
68
|
+
@IsString()
|
|
69
|
+
@IsNotEmpty()
|
|
70
|
+
id: string;
|
|
71
|
+
|
|
72
|
+
@ApiProperty({ description: 'Patient UUID' })
|
|
73
|
+
@IsString()
|
|
74
|
+
@IsNotEmpty()
|
|
75
|
+
patientId: string;
|
|
76
|
+
|
|
77
|
+
@ApiProperty({ description: 'Appointment UUID' })
|
|
78
|
+
@IsString()
|
|
79
|
+
@IsNotEmpty()
|
|
80
|
+
appointmentId: string;
|
|
81
|
+
|
|
82
|
+
@ApiProperty({
|
|
83
|
+
description: 'Type of satisfaction survey',
|
|
84
|
+
enum: SATISFACTION_TYPES,
|
|
85
|
+
example: 'satisfaction_finished',
|
|
86
|
+
})
|
|
87
|
+
@IsIn(SATISFACTION_TYPES)
|
|
88
|
+
satisfactionType: SatisfactionTypeDto;
|
|
89
|
+
|
|
90
|
+
@ApiProperty({ description: 'Patient satisfaction rating (1-5)', example: 5 })
|
|
91
|
+
@IsInt()
|
|
92
|
+
@Min(1)
|
|
93
|
+
@Max(5)
|
|
94
|
+
rating: number;
|
|
95
|
+
|
|
96
|
+
@ApiPropertyOptional({ description: 'Optional text comment from the patient' })
|
|
97
|
+
@IsOptional()
|
|
98
|
+
@IsString()
|
|
99
|
+
comment?: string | null;
|
|
100
|
+
|
|
101
|
+
@ApiProperty({ description: 'Timestamp when the response was recorded (ISO 8601)' })
|
|
102
|
+
@IsString()
|
|
103
|
+
@IsNotEmpty()
|
|
104
|
+
createdAt: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Response DTO for average satisfaction stats.
|
|
109
|
+
*/
|
|
110
|
+
export class SatisfactionAverageDto {
|
|
111
|
+
@ApiProperty({ description: 'Average rating', example: 4.2 })
|
|
112
|
+
average: number;
|
|
113
|
+
|
|
114
|
+
@ApiProperty({ description: 'Total number of responses', example: 42 })
|
|
115
|
+
count: number;
|
|
116
|
+
}
|
package/src/tenants/index.ts
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for tenant slug <-> domain transformations.
|
|
3
|
+
*
|
|
4
|
+
* Convention:
|
|
5
|
+
* - Slugs stored in DB use underscores for word separation (e.g. "clinica_sampaio")
|
|
6
|
+
* or are single words (e.g. "homolog"). Hyphens are NOT allowed in slugs.
|
|
7
|
+
* - DNS hostnames use hyphens (underscores are invalid in DNS labels).
|
|
8
|
+
* - Conversion is bijective: slug "clinica_sampaio" <-> domain "clinica-sampaio"
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* - Slug "homolog" -> domain segment "homolog" -> slug "homolog"
|
|
12
|
+
* - Slug "clinica_sampaio" -> domain segment "clinica-sampaio" -> slug "clinica_sampaio"
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Converts a tenant slug to a DNS-safe domain segment.
|
|
17
|
+
* Replaces underscores with hyphens and lowercases the result.
|
|
18
|
+
*
|
|
19
|
+
* @example slugToDomainSegment("clinica_sampaio") // "clinica-sampaio"
|
|
20
|
+
* @example slugToDomainSegment("homolog") // "homolog"
|
|
21
|
+
*/
|
|
22
|
+
export function slugToDomainSegment(slug: string): string {
|
|
23
|
+
return (slug || 'default').toLowerCase().replace(/_/g, '-');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Converts a domain segment back to the tenant slug.
|
|
28
|
+
* Replaces hyphens with underscores (the stored DB format).
|
|
29
|
+
*
|
|
30
|
+
* @example domainSegmentToSlug("clinica-sampaio") // "clinica_sampaio"
|
|
31
|
+
* @example domainSegmentToSlug("homolog") // "homolog"
|
|
32
|
+
*/
|
|
33
|
+
export function domainSegmentToSlug(domainSegment: string): string {
|
|
34
|
+
return (domainSegment || 'default').toLowerCase().replace(/-/g, '_');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Regex pattern that validates an acceptable tenant slug.
|
|
39
|
+
* Allows lowercase alphanumeric characters and underscores only (no hyphens).
|
|
40
|
+
* Must start and end with an alphanumeric character.
|
|
41
|
+
* Single-character slugs (e.g. "a") are allowed.
|
|
42
|
+
* Max 63 chars (DNS label limit after _ -> - conversion).
|
|
43
|
+
*/
|
|
44
|
+
export const TENANT_SLUG_REGEX = /^[a-z0-9]([a-z0-9_]*[a-z0-9])?$/;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validates whether a slug is acceptable.
|
|
48
|
+
* Rules:
|
|
49
|
+
* - Only lowercase letters, digits, and underscores (no hyphens)
|
|
50
|
+
* - Must start and end with a letter or digit
|
|
51
|
+
* - Length between 1 and 63 characters
|
|
52
|
+
*/
|
|
53
|
+
export function isValidTenantSlug(slug: string): boolean {
|
|
54
|
+
if (!slug || slug.length > 63) return false;
|
|
55
|
+
return TENANT_SLUG_REGEX.test(slug.toLowerCase());
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported WhatsApp provider types for tenant instance provisioning.
|
|
3
|
+
*
|
|
4
|
+
* - `evolution_api`: Evolution API v2 (Baileys-based unofficial).
|
|
5
|
+
* - `whatsapp_official`: Meta Cloud API (official WhatsApp Business).
|
|
6
|
+
*/
|
|
7
|
+
export const WHATSAPP_PROVIDER_KINDS = [
|
|
8
|
+
'evolution_api',
|
|
9
|
+
'whatsapp_official',
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export type WhatsAppProviderKindDto = (typeof WHATSAPP_PROVIDER_KINDS)[number];
|