spine-framework-portal 0.1.1 → 0.1.4

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/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # spine-framework-portal
2
+
3
+ Customer self-service portal app for Spine Framework.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install spine-framework-portal
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ npx spine-framework install-app customer-portal
15
+ ```
16
+
17
+ ## Features
18
+
19
+ - Community discussion and Q&A
20
+ - Course and lesson content delivery
21
+ - Support ticket submission and tracking
22
+ - Knowledge base article access
23
+
24
+ ## License
25
+
26
+ MIT
@@ -0,0 +1,202 @@
1
+ // Portal Community Escalation Handler
2
+ // Converts unanswered community posts to support tickets.
3
+ // Standalone: no dependency on cortex functions.
4
+ // Triggered by cron — see portal seed/triggers.json.
5
+
6
+ import { createHandler } from './_shared/middleware'
7
+ import { adminDb } from './_shared/db'
8
+
9
+ interface CommunityPost {
10
+ id: string
11
+ title: string
12
+ description?: string
13
+ account_id: string
14
+ person_id: string
15
+ created_at: string
16
+ data?: {
17
+ category?: string
18
+ tags?: string[]
19
+ status?: string
20
+ escalation?: {
21
+ escalated_to_ticket_id?: string
22
+ }
23
+ }
24
+ }
25
+
26
+ async function escalatePostToTicket(post: CommunityPost): Promise<string | null> {
27
+ try {
28
+ const { data: existingTicket } = await adminDb
29
+ .from('items')
30
+ .select('id')
31
+ .eq('type_slug', 'support_ticket')
32
+ .eq('data->>source_post_id', post.id)
33
+ .limit(1)
34
+ .maybeSingle()
35
+
36
+ if (existingTicket) {
37
+ console.log(`Post ${post.id} already escalated to ticket ${existingTicket.id}`)
38
+ return null
39
+ }
40
+
41
+ const { data: newTicket, error: insertError } = await adminDb
42
+ .from('items')
43
+ .insert({
44
+ type_slug: 'support_ticket',
45
+ title: `Escalated: ${post.title}`,
46
+ description: post.description || 'No description provided',
47
+ account_id: post.account_id,
48
+ person_id: post.person_id,
49
+ status: 'open',
50
+ data: {
51
+ source_post_id: post.id,
52
+ source: 'community_escalation',
53
+ escalated_at: new Date().toISOString(),
54
+ original_category: post.data?.category || 'general',
55
+ original_tags: post.data?.tags || [],
56
+ community_status: 'unanswered_24h',
57
+ ai_metadata: {
58
+ confidence_threshold: 0.75,
59
+ escalation_reason: 'community_unanswered',
60
+ problem_statement: post.title,
61
+ source_content: post.description?.slice(0, 1000),
62
+ },
63
+ },
64
+ })
65
+ .select('id')
66
+ .single()
67
+
68
+ if (insertError || !newTicket) {
69
+ throw new Error(`Failed to create ticket: ${insertError?.message}`)
70
+ }
71
+
72
+ const ticketId = newTicket.id
73
+
74
+ await adminDb.from('threads').insert({
75
+ target_type: 'items',
76
+ target_id: ticketId,
77
+ visibility: 'external',
78
+ status: 'active',
79
+ })
80
+
81
+ await adminDb
82
+ .from('items')
83
+ .update({
84
+ data: {
85
+ ...post.data,
86
+ status: 'escalated',
87
+ escalation: {
88
+ escalated_to_ticket_id: ticketId,
89
+ escalated_at: new Date().toISOString(),
90
+ reason: 'unanswered_24h',
91
+ },
92
+ },
93
+ updated_at: new Date().toISOString(),
94
+ })
95
+ .eq('id', post.id)
96
+
97
+ // Attempt to trigger support triage pipeline if present
98
+ try {
99
+ const { data: pipeline } = await adminDb
100
+ .from('pipelines')
101
+ .select('id')
102
+ .ilike('name', '%support%triage%')
103
+ .limit(1)
104
+ .maybeSingle()
105
+
106
+ if (pipeline) {
107
+ await adminDb.from('pipeline_executions').insert({
108
+ pipeline_id: pipeline.id,
109
+ target_type: 'items',
110
+ target_id: ticketId,
111
+ status: 'pending',
112
+ input_context: {
113
+ ticket_id: ticketId,
114
+ account_id: post.account_id,
115
+ title: post.title,
116
+ description: post.description || '',
117
+ source: 'community_escalation',
118
+ },
119
+ })
120
+ }
121
+ } catch (err) {
122
+ console.error('Failed to trigger triage pipeline:', err)
123
+ }
124
+
125
+ console.log(`Escalated post ${post.id} to ticket ${ticketId}`)
126
+ return ticketId
127
+
128
+ } catch (err) {
129
+ console.error(`Failed to escalate post ${post.id}:`, err)
130
+ throw err
131
+ }
132
+ }
133
+
134
+ export const checkUnanswered = createHandler(async (_ctx, _body) => {
135
+ console.log('[PortalEscalation] Starting community escalation check...')
136
+
137
+ try {
138
+ const cutoffTime = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
139
+
140
+ const { data: unansweredPosts, error: postsError } = await adminDb
141
+ .from('items')
142
+ .select('id, title, description, account_id, person_id, created_at, data')
143
+ .eq('type_slug', 'community_post')
144
+ .not('data->>status', 'eq', 'escalated')
145
+ .lt('created_at', cutoffTime)
146
+ .order('created_at', { ascending: true })
147
+ .limit(50)
148
+
149
+ if (postsError) {
150
+ throw new Error(`Failed to fetch posts: ${postsError.message}`)
151
+ }
152
+
153
+ if (!unansweredPosts || unansweredPosts.length === 0) {
154
+ return { status: 'ok', processed: 0, escalated: 0, failed: 0, skipped: 0 }
155
+ }
156
+
157
+ const postsToEscalate: CommunityPost[] = []
158
+ for (const post of unansweredPosts) {
159
+ const { data: replies } = await adminDb
160
+ .from('items')
161
+ .select('id')
162
+ .eq('type_slug', 'community_reply')
163
+ .eq('data->>post_id', post.id)
164
+ .gt('created_at', post.created_at)
165
+ .limit(1)
166
+
167
+ if (!replies || replies.length === 0) {
168
+ postsToEscalate.push(post as CommunityPost)
169
+ }
170
+ }
171
+
172
+ console.log(`[PortalEscalation] Found ${postsToEscalate.length} unanswered posts`)
173
+
174
+ const results = { escalated: [] as string[], failed: [] as string[], skipped: [] as string[] }
175
+
176
+ for (const post of postsToEscalate) {
177
+ try {
178
+ const ticketId = await escalatePostToTicket(post)
179
+ if (ticketId) results.escalated.push(post.id)
180
+ else results.skipped.push(post.id)
181
+ } catch (err) {
182
+ console.error(`Failed to escalate post ${post.id}:`, err)
183
+ results.failed.push(post.id)
184
+ }
185
+ }
186
+
187
+ return {
188
+ status: 'ok',
189
+ processed: postsToEscalate.length,
190
+ escalated: results.escalated.length,
191
+ failed: results.failed.length,
192
+ skipped: results.skipped.length,
193
+ details: results,
194
+ }
195
+
196
+ } catch (err) {
197
+ console.error('[PortalEscalation] Failed:', err)
198
+ const error: any = new Error('Failed to process community escalation')
199
+ error.statusCode = 500
200
+ throw error
201
+ }
202
+ })
@@ -1,11 +1,45 @@
1
+ // Portal Signal Handler
2
+ // Records portal user actions as funnel signals in the items table.
3
+ // Portal users are always identified — no anonymous session handling.
4
+ // Standalone: no dependency on cortex functions.
5
+
1
6
  import { createHandler } from './_shared/middleware'
2
- import { processSignal } from './custom_funnel-signal'
7
+ import { adminDb } from './_shared/db'
8
+ import { resolveTypeIds, resolveAccountId } from './_shared/resolve-ids'
9
+
10
+ async function resolveIds() {
11
+ const [types, unidentifiedVisitorsAccountId] = await Promise.all([
12
+ resolveTypeIds([{ kind: 'item', slug: 'funnel_signal' }]),
13
+ resolveAccountId('unidentified-visitors'),
14
+ ])
15
+ return {
16
+ FUNNEL_SIGNAL_TYPE_ID: types['item/funnel_signal'],
17
+ UNIDENTIFIED_VISITORS_ACCOUNT_ID: unidentifiedVisitorsAccountId,
18
+ }
19
+ }
20
+
21
+ function ratingToTemperature(rating: number): 'cold' | 'warm' | 'hot' {
22
+ if (rating <= 2) return 'cold'
23
+ if (rating <= 3) return 'warm'
24
+ return 'hot'
25
+ }
26
+
27
+ function calculateSimpleScore(actionValue: number): { calculated: number; rating: 1 | 2 | 3 | 4 | 5 } {
28
+ const calculated = actionValue
29
+ let rating: 1 | 2 | 3 | 4 | 5
30
+ if (calculated <= 1) rating = 1
31
+ else if (calculated <= 4) rating = 2
32
+ else if (calculated <= 8) rating = 3
33
+ else if (calculated <= 15) rating = 4
34
+ else rating = 5
35
+ return { calculated, rating }
36
+ }
3
37
 
4
38
  export const handler = createHandler(async (ctx, body) => {
5
39
  const { action_type, action_value, action_description, session_id } = body || {}
6
40
 
7
- if (!action_type || typeof action_value !== 'number') {
8
- const err: any = new Error('action_type and action_value are required')
41
+ if (!action_type || ![1, 2, 5].includes(action_value)) {
42
+ const err: any = new Error('action_type and action_value (1, 2, or 5) are required')
9
43
  err.statusCode = 400
10
44
  throw err
11
45
  }
@@ -16,18 +50,92 @@ export const handler = createHandler(async (ctx, body) => {
16
50
  throw err
17
51
  }
18
52
 
19
- const payload = {
20
- account_id: ctx.accountId,
21
- person_id: ctx.principal.id,
22
- session_id: session_id || `portal_${ctx.principal.id}_${Date.now()}`,
23
- stage: 'identified',
24
- source: 'int',
25
- action_type,
26
- action_value,
27
- ...(action_description && { action_description }),
53
+ const ids = await resolveIds()
54
+ const now = new Date().toISOString()
55
+ const scoring = calculateSimpleScore(action_value)
56
+ const resolvedSessionId = session_id || `portal_${ctx.principal.id}_${Date.now()}`
57
+
58
+ const signalData = {
59
+ identity: {
60
+ anonymous_id: null,
61
+ person_id: ctx.principal.id,
62
+ account_id: ctx.accountId,
63
+ session_id: resolvedSessionId,
64
+ },
65
+ classification: {
66
+ stage: 'identified',
67
+ source: 'int',
68
+ },
69
+ action: {
70
+ action_type,
71
+ action_value,
72
+ action_description: action_description || null,
73
+ },
74
+ scoring_components: {
75
+ raw_score: {
76
+ calculated: scoring.calculated,
77
+ max_possible: 25,
78
+ rating: scoring.rating,
79
+ },
80
+ },
81
+ processing: {
82
+ received_at: now,
83
+ enriched_at: now,
84
+ scored_at: now,
85
+ stitched_at: null,
86
+ stitched_to_account_id: null,
87
+ },
88
+ }
89
+
90
+ const { data, error } = await adminDb
91
+ .from('items')
92
+ .insert({
93
+ type_id: ids.FUNNEL_SIGNAL_TYPE_ID,
94
+ title: `${action_type} - ${action_value}`,
95
+ account_id: ctx.accountId,
96
+ data: signalData,
97
+ })
98
+ .select('id')
99
+ .single()
100
+
101
+ if (error) {
102
+ throw new Error(`Failed to record portal signal: ${error.message}`)
28
103
  }
29
104
 
30
- await processSignal(payload, { accountId: ctx.accountId, requestId: ctx.requestId }, {})
105
+ // Update account funnel data
106
+ const { data: account } = await adminDb
107
+ .from('accounts')
108
+ .select('data')
109
+ .eq('id', ctx.accountId)
110
+ .single()
111
+
112
+ if (account) {
113
+ const currentRating = account.data?.ratings?.identified?.rating || 0
114
+ const shouldUpdate = scoring.rating > currentRating
115
+
116
+ await adminDb
117
+ .from('accounts')
118
+ .update({
119
+ data: {
120
+ ...account.data,
121
+ ...(shouldUpdate && {
122
+ lead_score: scoring.calculated,
123
+ temperature: ratingToTemperature(scoring.rating),
124
+ lifecycle_stage: 'identified',
125
+ ratings: {
126
+ ...(account.data?.ratings || {}),
127
+ identified: {
128
+ rating: scoring.rating,
129
+ raw_score: scoring.calculated,
130
+ calculated_at: now,
131
+ },
132
+ },
133
+ }),
134
+ last_signal_at: now,
135
+ },
136
+ })
137
+ .eq('id', ctx.accountId)
138
+ }
31
139
 
32
- return { status: 'ok' }
140
+ return { status: 'ok', signal_id: data.id, rating: scoring.rating }
33
141
  })
package/manifest.json CHANGED
@@ -54,6 +54,7 @@
54
54
  "features": ["tickets", "kb", "courses", "community", "marketplace"],
55
55
  "dependencies": ["items", "threads", "messages"],
56
56
  "entry_point": "./index.tsx",
57
+ "directories": ["pages", "components", "hooks", "config", "seed", "functions", "migrations", "tests"],
57
58
  "is_public": true,
58
59
  "auth_required": true
59
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework-portal",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Customer Portal — self-service portal app for Spine Framework",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,8 +10,7 @@
10
10
  "directory": "custom/apps/customer-portal"
11
11
  },
12
12
  "peerDependencies": {
13
- "spine-framework": ">=0.1.0",
14
- "spine-framework-cortex": ">=0.1.0"
13
+ "spine-framework": ">=0.1.0"
15
14
  },
16
15
  "files": [
17
16
  "index.tsx",
@@ -0,0 +1,12 @@
1
+ [
2
+ {
3
+ "slug": "member",
4
+ "app_slug": "customer-portal",
5
+ "name": "Member",
6
+ "description": "Portal member with standard access",
7
+ "permissions": ["*"],
8
+ "is_system": true,
9
+ "is_active": true,
10
+ "is_protected": false
11
+ }
12
+ ]
@@ -4,8 +4,9 @@
4
4
  "description": "Check for community posts >24h without answers and escalate to support tickets",
5
5
  "trigger_type": "cron",
6
6
  "event_type": null,
7
+ "scope": "account",
7
8
  "config": {
8
- "function": "custom_community-escalation.checkUnanswered",
9
+ "function": "custom_portal-community-escalation.checkUnanswered",
9
10
  "schedule": "0 */4 * * *",
10
11
  "timezone": "UTC",
11
12
  "description": "Check for community posts >24h without answers, create tickets"
package/seed/types.json CHANGED
@@ -10,20 +10,38 @@
10
10
  "is_active": true,
11
11
  "design_schema": {
12
12
  "scope": "platform",
13
+ "views": {
14
+ "default_list": {
15
+ "type": "list",
16
+ "label": "Community Posts",
17
+ "fields": {
18
+ "title": { "sortable": true, "display_type": "text" },
19
+ "channel": { "sortable": true, "display_type": "badge" },
20
+ "moderation_status": { "sortable": true, "display_type": "badge" }
21
+ },
22
+ "display": "table"
23
+ },
24
+ "default_detail": {
25
+ "type": "detail",
26
+ "label": "Community Post",
27
+ "sections": [
28
+ { "title": "Content", "fields": ["title", "content", "channel", "context"] },
29
+ { "title": "Moderation", "fields": ["moderation_status", "helpful_count", "not_helpful_count", "accepted_answer_id"] }
30
+ ]
31
+ }
32
+ },
13
33
  "fields": {
14
- "title": { "label": "Title", "required": true, "data_type": "text" },
15
- "content": { "label": "Content", "required": true, "data_type": "text" },
16
- "context": { "label": "Context", "required": true, "data_type": "text", "options": ["support", "community"] },
17
- "channel": { "label": "Channel", "required": true, "data_type": "text", "default": "general", "options": ["general", "announcements", "help", "show-and-tell"] },
18
- "moderation_status": { "label": "Moderation Status", "data_type": "text", "default": "pending", "options": ["pending", "approved", "flagged"] },
19
- "helpful_count": { "label": "Helpful Votes", "data_type": "integer", "default": 0 },
20
- "not_helpful_count": { "label": "Not Helpful Votes", "data_type": "integer", "default": 0 },
21
- "accepted_answer_id": { "label": "Accepted Answer", "data_type": "uuid" }
34
+ "title": { "label": "Title", "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
35
+ "content": { "label": "Content", "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
36
+ "context": { "label": "Context", "required": true, "data_type": "select", "options": [{"label":"Support","value":"support"},{"label":"Community","value":"community"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
37
+ "channel": { "label": "Channel", "required": true, "data_type": "select", "default": "general", "options": [{"label":"General","value":"general"},{"label":"Announcements","value":"announcements"},{"label":"Help","value":"help"},{"label":"Show and Tell","value":"show-and-tell"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
38
+ "moderation_status": { "label": "Moderation Status", "data_type": "select", "default": "pending", "options": [{"label":"Pending","value":"pending"},{"label":"Approved","value":"approved"},{"label":"Flagged","value":"flagged"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
39
+ "helpful_count": { "label": "Helpful Votes", "data_type": "number", "default": 0, "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
40
+ "not_helpful_count": { "label": "Not Helpful Votes", "data_type": "number", "default": 0, "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
41
+ "accepted_answer_id": { "label": "Accepted Answer", "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } }
22
42
  },
23
- "record_permissions": {
24
- "member": ["create", "read", "update"],
25
- "system_admin": ["create", "read", "update", "delete"]
26
- }
43
+ "record_permissions": { "member": ["create", "read", "update"], "support": ["create", "read", "update"], "system_admin": ["create", "read", "update", "delete"] },
44
+ "functionality": null
27
45
  },
28
46
  "validation_schema": {}
29
47
  },
@@ -38,20 +56,38 @@
38
56
  "is_active": true,
39
57
  "design_schema": {
40
58
  "scope": "platform",
59
+ "views": {
60
+ "default_list": {
61
+ "type": "list",
62
+ "label": "Course Lessons",
63
+ "fields": {
64
+ "title": { "sortable": true, "display_type": "text" },
65
+ "sequence": { "sortable": true, "display_type": "number" },
66
+ "estimated_duration": { "sortable": true, "display_type": "number" }
67
+ },
68
+ "display": "table"
69
+ },
70
+ "default_detail": {
71
+ "type": "detail",
72
+ "label": "Course Lesson",
73
+ "sections": [
74
+ { "title": "Content", "fields": ["title", "content", "video_url", "estimated_duration"] },
75
+ { "title": "Settings", "fields": ["sequence", "progress_required", "prerequisites"] }
76
+ ]
77
+ }
78
+ },
41
79
  "fields": {
42
- "title": { "label": "Title", "required": true, "data_type": "text" },
43
- "content": { "label": "Content", "required": true, "data_type": "text" },
44
- "context": { "label": "Context", "required": true, "data_type": "text", "options": ["kb", "course"] },
45
- "sequence": { "label": "Lesson Sequence", "data_type": "integer" },
46
- "video_url": { "label": "Video URL", "data_type": "url" },
47
- "estimated_duration": { "label": "Estimated Duration (minutes)", "data_type": "integer" },
48
- "progress_required": { "label": "Progress Required", "data_type": "boolean", "default": true },
49
- "prerequisites": { "label": "Prerequisite Lessons", "data_type": "uuid[]" }
80
+ "title": { "label": "Title", "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
81
+ "content": { "label": "Content", "required": true, "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
82
+ "context": { "label": "Context", "required": true, "data_type": "select", "options": [{"label":"Knowledge Base","value":"kb"},{"label":"Course","value":"course"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
83
+ "sequence": { "label": "Lesson Sequence", "data_type": "number", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
84
+ "video_url": { "label": "Video URL", "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
85
+ "estimated_duration": { "label": "Estimated Duration (minutes)", "data_type": "number", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
86
+ "progress_required": { "label": "Progress Required", "data_type": "boolean", "default": true, "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
87
+ "prerequisites": { "label": "Prerequisite Lessons", "data_type": "json", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } }
50
88
  },
51
- "record_permissions": {
52
- "all": ["read"],
53
- "support": ["read", "create", "update"]
54
- }
89
+ "record_permissions": { "member": ["read"], "support": ["read", "create", "update"], "system_admin": ["create", "read", "update", "delete"] },
90
+ "functionality": null
55
91
  },
56
92
  "validation_schema": {}
57
93
  },
@@ -66,18 +102,39 @@
66
102
  "is_active": true,
67
103
  "design_schema": {
68
104
  "scope": "account",
105
+ "views": {
106
+ "default_list": {
107
+ "type": "list",
108
+ "label": "Integrity Reports",
109
+ "fields": {
110
+ "title": { "sortable": true, "display_type": "text" },
111
+ "is_active": { "sortable": true, "display_type": "badge" },
112
+ "created_at": { "sortable": true, "display_type": "timestamp" }
113
+ },
114
+ "display": "table"
115
+ },
116
+ "default_detail": {
117
+ "type": "detail",
118
+ "label": "Integrity Report",
119
+ "sections": [
120
+ { "title": "Hashes", "fields": ["core_hash", "manifest_hash", "deploy_id", "deploy_url"] },
121
+ { "title": "Metadata", "fields": ["is_active", "created_at", "updated_at"] }
122
+ ]
123
+ }
124
+ },
69
125
  "fields": {
70
- "title": { "label": "Title", "system": true, "required": true, "data_type": "text" },
71
- "status": { "label": "Status", "system": true, "required": false, "data_type": "text" },
72
- "is_active": { "label": "Active", "system": true, "required": true, "data_type": "boolean" },
73
- "core_hash": { "label": "Core Hash", "system": false, "required": true, "data_type": "text" },
74
- "manifest_hash": { "label": "Manifest Hash", "system": false, "required": true, "data_type": "text" },
75
- "deploy_id": { "label": "Deploy ID", "system": false, "required": false, "data_type": "text" },
76
- "deploy_url": { "label": "Deploy URL", "system": false, "required": false, "data_type": "text" },
77
- "created_at": { "label": "Created", "system": true, "readonly": true, "required": false, "data_type": "datetime" },
78
- "updated_at": { "label": "Updated", "system": true, "readonly": true, "required": false, "data_type": "datetime" }
126
+ "title": { "label": "Title", "system": true, "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
127
+ "status": { "label": "Status", "system": true, "required": false, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
128
+ "is_active": { "label": "Active", "system": true, "required": true, "data_type": "boolean", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
129
+ "core_hash": { "label": "Core Hash", "system": false, "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
130
+ "manifest_hash": { "label": "Manifest Hash", "system": false, "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
131
+ "deploy_id": { "label": "Deploy ID", "system": false, "required": false, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
132
+ "deploy_url": { "label": "Deploy URL", "system": false, "required": false, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
133
+ "created_at": { "label": "Created", "system": true, "readonly": true, "required": false, "data_type": "datetime", "permissions": { "system_admin": ["read"], "support": ["read"], "member": ["read"] } },
134
+ "updated_at": { "label": "Updated", "system": true, "readonly": true, "required": false, "data_type": "datetime", "permissions": { "system_admin": ["read"], "support": ["read"], "member": ["read"] } }
79
135
  },
80
- "record_permissions": { "system_admin": ["create", "read", "update", "delete"] }
136
+ "record_permissions": { "member": ["read"], "support": ["read", "create", "update"], "system_admin": ["create", "read", "update", "delete"] },
137
+ "functionality": null
81
138
  },
82
139
  "validation_schema": {}
83
140
  },
@@ -92,44 +149,64 @@
92
149
  "is_active": true,
93
150
  "design_schema": {
94
151
  "scope": "customer",
152
+ "views": {
153
+ "default_list": {
154
+ "type": "list",
155
+ "label": "Support Tickets",
156
+ "fields": {
157
+ "title": { "sortable": true, "display_type": "text" },
158
+ "status": { "sortable": true, "display_type": "badge" },
159
+ "priority": { "sortable": true, "display_type": "badge" },
160
+ "escalated": { "sortable": true, "display_type": "boolean" }
161
+ },
162
+ "display": "table"
163
+ },
164
+ "default_detail": {
165
+ "type": "detail",
166
+ "label": "Support Ticket",
167
+ "sections": [
168
+ { "title": "Request", "fields": ["title", "description", "status", "priority", "context"] },
169
+ { "title": "AI Handling", "fields": ["ai_response", "ai_confidence", "escalated", "aim_triage_agent_id", "aim_escalation_reason", "aim_human_assignee_id"] },
170
+ { "title": "Analysis", "fields": ["ca_true_problem", "ca_reported_issue", "ca_final_solution", "ca_solution_steps", "ca_customer_temperature", "ca_time_to_resolution", "ca_automation_potential"] },
171
+ { "title": "KB Generation", "fields": ["ca_kb_candidate", "kb_proposed_kb_id", "kb_redacted_draft", "kb_approved_at", "kb_approved_by", "kb_human_edits"] }
172
+ ]
173
+ }
174
+ },
95
175
  "fields": {
96
- "title": { "label": "Title", "system": true, "required": true, "data_type": "text" },
97
- "description": { "label": "Description", "system": true, "required": true, "data_type": "text" },
98
- "status": { "label": "Status", "system": true, "data_type": "text", "default": "open", "options": ["open", "ai_responding", "human_assigned", "in_progress", "resolved", "closed"] },
99
- "context": { "label": "Context", "required": true, "data_type": "text", "options": ["support", "community"] },
100
- "priority": { "label": "Priority", "data_type": "text", "default": "medium", "options": ["low", "medium", "high", "urgent"] },
101
- "escalated": { "label": "Escalated to Human", "data_type": "boolean", "default": false },
102
- "ai_response": { "label": "AI Response", "data_type": "text" },
103
- "ai_confidence": { "label": "AI Confidence", "data_type": "float" },
104
- "aim_triage_agent_id": { "label": "Triage Agent ID", "data_type": "text" },
105
- "aim_escalation_reason": { "label": "Escalation Reason", "data_type": "text", "options": ["low_confidence", "thumbs_down", "customer_request", "none"] },
106
- "aim_human_assignee_id": { "label": "Human Assignee", "data_type": "text" },
107
- "aim_confidence_threshold": { "label": "Confidence Threshold", "data_type": "number", "default": 0.75 },
108
- "aim_confidence_at_response": { "label": "Confidence at Response", "data_type": "number" },
109
- "ca_true_problem": { "label": "True Problem Identified", "data_type": "text" },
110
- "ca_reported_issue": { "label": "Reported Issue", "data_type": "text" },
111
- "ca_final_solution": { "label": "Final Solution Summary", "data_type": "text" },
112
- "ca_solution_steps": { "label": "Steps to Solve", "data_type": "json" },
113
- "ca_diagnostic_steps": { "label": "Steps to Diagnose", "data_type": "json" },
114
- "ca_analysis_tags": { "label": "Analysis Tags", "data_type": "json" },
115
- "ca_escalation_required": { "label": "Was Escalated to Human Agent", "data_type": "boolean" },
116
- "ca_customer_temperature": { "label": "Customer Temperature", "data_type": "text", "options": ["positive", "neutral", "negative", "frustrated"] },
117
- "ca_time_to_resolution": { "label": "Time to Resolution (minutes)", "data_type": "number" },
118
- "ca_back_and_forth_count": { "label": "Number of Back and Forths", "data_type": "number" },
119
- "ca_automation_potential": { "label": "Automation Potential", "data_type": "text", "options": ["high", "medium", "low"] },
120
- "ca_sentiment_progression": { "label": "Customer Sentiment Progression", "data_type": "json" },
121
- "ca_kb_candidate": { "label": "KB Candidate", "data_type": "boolean" },
122
- "kb_proposed_kb_id": { "label": "Proposed KB Article ID", "data_type": "text" },
123
- "kb_redacted_draft": { "label": "Redacted Draft", "data_type": "text" },
124
- "kb_approved_at": { "label": "Approved At", "data_type": "datetime" },
125
- "kb_approved_by": { "label": "Approved By", "data_type": "text" },
126
- "kb_human_edits": { "label": "Human Edits", "data_type": "text" }
176
+ "title": { "label": "Title", "system": true, "required": true, "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
177
+ "description": { "label": "Description", "system": true, "required": true, "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
178
+ "status": { "label": "Status", "system": true, "data_type": "select", "default": "open", "options": [{"label":"Open","value":"open"},{"label":"AI Responding","value":"ai_responding"},{"label":"Human Assigned","value":"human_assigned"},{"label":"In Progress","value":"in_progress"},{"label":"Resolved","value":"resolved"},{"label":"Closed","value":"closed"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
179
+ "context": { "label": "Context", "required": true, "data_type": "select", "options": [{"label":"Support","value":"support"},{"label":"Community","value":"community"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read", "write"] } },
180
+ "priority": { "label": "Priority", "data_type": "select", "default": "medium", "options": [{"label":"Low","value":"low"},{"label":"Medium","value":"medium"},{"label":"High","value":"high"},{"label":"Urgent","value":"urgent"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
181
+ "escalated": { "label": "Escalated to Human", "data_type": "boolean", "default": false, "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
182
+ "ai_response": { "label": "AI Response", "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
183
+ "ai_confidence": { "label": "AI Confidence", "data_type": "number", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
184
+ "aim_triage_agent_id": { "label": "Triage Agent ID", "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
185
+ "aim_escalation_reason": { "label": "Escalation Reason", "data_type": "select", "options": [{"label":"Low Confidence","value":"low_confidence"},{"label":"Thumbs Down","value":"thumbs_down"},{"label":"Customer Request","value":"customer_request"},{"label":"None","value":"none"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
186
+ "aim_human_assignee_id": { "label": "Human Assignee", "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
187
+ "aim_confidence_threshold": { "label": "Confidence Threshold", "data_type": "number", "default": 0.75, "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
188
+ "aim_confidence_at_response": { "label": "Confidence at Response", "data_type": "number", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
189
+ "ca_true_problem": { "label": "True Problem Identified", "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
190
+ "ca_reported_issue": { "label": "Reported Issue", "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
191
+ "ca_final_solution": { "label": "Final Solution Summary", "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
192
+ "ca_solution_steps": { "label": "Steps to Solve", "data_type": "json", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
193
+ "ca_diagnostic_steps": { "label": "Steps to Diagnose", "data_type": "json", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
194
+ "ca_analysis_tags": { "label": "Analysis Tags", "data_type": "json", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
195
+ "ca_escalation_required": { "label": "Was Escalated to Human Agent", "data_type": "boolean", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
196
+ "ca_customer_temperature": { "label": "Customer Temperature", "data_type": "select", "options": [{"label":"Positive","value":"positive"},{"label":"Neutral","value":"neutral"},{"label":"Negative","value":"negative"},{"label":"Frustrated","value":"frustrated"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
197
+ "ca_time_to_resolution": { "label": "Time to Resolution (minutes)", "data_type": "number", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
198
+ "ca_back_and_forth_count": { "label": "Number of Back and Forths", "data_type": "number", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
199
+ "ca_automation_potential": { "label": "Automation Potential", "data_type": "select", "options": [{"label":"High","value":"high"},{"label":"Medium","value":"medium"},{"label":"Low","value":"low"}], "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
200
+ "ca_sentiment_progression": { "label": "Customer Sentiment Progression", "data_type": "json", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
201
+ "ca_kb_candidate": { "label": "KB Candidate", "data_type": "boolean", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
202
+ "kb_proposed_kb_id": { "label": "Proposed KB Article ID", "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
203
+ "kb_redacted_draft": { "label": "Redacted Draft", "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } },
204
+ "kb_approved_at": { "label": "Approved At", "data_type": "datetime", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
205
+ "kb_approved_by": { "label": "Approved By", "data_type": "text", "permissions": { "system_admin": ["read", "write"], "support": ["read"], "member": ["read"] } },
206
+ "kb_human_edits": { "label": "Human Edits", "data_type": "textarea", "permissions": { "system_admin": ["read", "write"], "support": ["read", "write"], "member": ["read"] } }
127
207
  },
128
- "record_permissions": {
129
- "member": ["create", "read", "update"],
130
- "support": ["create", "read", "update"],
131
- "system_admin": ["create", "read", "update", "delete"]
132
- }
208
+ "record_permissions": { "member": ["create", "read", "update"], "support": ["create", "read", "update"], "system_admin": ["create", "read", "update", "delete"] },
209
+ "functionality": null
133
210
  },
134
211
  "validation_schema": {}
135
212
  }