tallyfy 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl
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.
Potentially problematic release.
This version of tallyfy might be problematic. Click here for more details.
- tallyfy/__init__.py +8 -4
- tallyfy/core.py +8 -8
- tallyfy/form_fields_management/__init__.py +70 -0
- tallyfy/form_fields_management/base.py +109 -0
- tallyfy/form_fields_management/crud_operations.py +234 -0
- tallyfy/form_fields_management/options_management.py +222 -0
- tallyfy/form_fields_management/suggestions.py +411 -0
- tallyfy/task_management/__init__.py +81 -0
- tallyfy/task_management/base.py +125 -0
- tallyfy/task_management/creation.py +221 -0
- tallyfy/task_management/retrieval.py +211 -0
- tallyfy/task_management/search.py +196 -0
- tallyfy/template_management/__init__.py +85 -0
- tallyfy/template_management/analysis.py +1093 -0
- tallyfy/template_management/automation.py +469 -0
- tallyfy/template_management/base.py +56 -0
- tallyfy/template_management/basic_operations.py +477 -0
- tallyfy/template_management/health_assessment.py +763 -0
- tallyfy/user_management/__init__.py +69 -0
- tallyfy/user_management/base.py +146 -0
- tallyfy/user_management/invitation.py +286 -0
- tallyfy/user_management/retrieval.py +339 -0
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/METADATA +120 -56
- tallyfy-1.0.5.dist-info/RECORD +28 -0
- tallyfy/BUILD.md +0 -5
- tallyfy/README.md +0 -634
- tallyfy/form_fields_management.py +0 -582
- tallyfy/task_management.py +0 -356
- tallyfy/template_management.py +0 -2607
- tallyfy/user_management.py +0 -235
- tallyfy-1.0.3.dist-info/RECORD +0 -14
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/WHEEL +0 -0
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {tallyfy-1.0.3.dist-info → tallyfy-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Form field management functionality for Tallyfy SDK
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import List, Optional, Dict, Any
|
|
6
|
-
from .models import Capture, Step, Template, TallyfyError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class FormFieldManagement:
|
|
10
|
-
"""Handles form field operations"""
|
|
11
|
-
|
|
12
|
-
def __init__(self, sdk):
|
|
13
|
-
self.sdk = sdk
|
|
14
|
-
|
|
15
|
-
def add_form_field_to_step(self, org_id: str, template_id: str, step_id: str, field_data: Dict[str, Any]) -> Optional[Capture]:
|
|
16
|
-
"""
|
|
17
|
-
Add form fields (text, dropdown, date, etc.) to a step.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
org_id: Organization ID
|
|
21
|
-
template_id: Template ID
|
|
22
|
-
step_id: Step ID
|
|
23
|
-
field_data: Form field creation data including field_type, label, required, etc.
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
Created Capture object or None if creation failed
|
|
27
|
-
|
|
28
|
-
Raises:
|
|
29
|
-
TallyfyError: If the request fails
|
|
30
|
-
"""
|
|
31
|
-
try:
|
|
32
|
-
endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures"
|
|
33
|
-
|
|
34
|
-
# Validate required fields
|
|
35
|
-
required_fields = ['field_type', 'label']
|
|
36
|
-
for field in required_fields:
|
|
37
|
-
if field not in field_data:
|
|
38
|
-
raise ValueError(f"Missing required field: {field}")
|
|
39
|
-
|
|
40
|
-
# Set defaults for optional fields
|
|
41
|
-
capture_data = {
|
|
42
|
-
'field_type': field_data['field_type'],
|
|
43
|
-
'label': field_data['label'],
|
|
44
|
-
'required': field_data.get('required', True),
|
|
45
|
-
'position': field_data.get('position', 1)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
# Add optional fields if provided
|
|
49
|
-
optional_fields = ['guidance', 'options', 'default_value', 'default_value_enabled']
|
|
50
|
-
for field in optional_fields:
|
|
51
|
-
if field in field_data:
|
|
52
|
-
capture_data[field] = field_data[field]
|
|
53
|
-
|
|
54
|
-
response_data = self.sdk._make_request('POST', endpoint, data=capture_data)
|
|
55
|
-
|
|
56
|
-
if isinstance(response_data, dict) and 'data' in response_data:
|
|
57
|
-
capture_data = response_data['data']
|
|
58
|
-
return Capture.from_dict(capture_data)
|
|
59
|
-
else:
|
|
60
|
-
self.sdk.logger.warning("Unexpected response format for form field creation")
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
except TallyfyError as e:
|
|
64
|
-
self.sdk.logger.error(f"Failed to add form field to step {step_id}: {e}")
|
|
65
|
-
raise
|
|
66
|
-
|
|
67
|
-
def update_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str, **kwargs) -> Optional[Capture]:
|
|
68
|
-
"""
|
|
69
|
-
Update form field properties, validation, options.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
org_id: Organization ID
|
|
73
|
-
template_id: Template ID
|
|
74
|
-
step_id: Step ID
|
|
75
|
-
field_id: Form field ID
|
|
76
|
-
**kwargs: Form field properties to update
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
Updated Capture object or None if update failed
|
|
80
|
-
|
|
81
|
-
Raises:
|
|
82
|
-
TallyfyError: If the request fails
|
|
83
|
-
"""
|
|
84
|
-
try:
|
|
85
|
-
endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures/{field_id}"
|
|
86
|
-
|
|
87
|
-
# Build update data from kwargs
|
|
88
|
-
update_data = {}
|
|
89
|
-
allowed_fields = [
|
|
90
|
-
'field_type', 'label', 'guidance', 'position', 'required',
|
|
91
|
-
'options', 'default_value', 'default_value_enabled'
|
|
92
|
-
]
|
|
93
|
-
|
|
94
|
-
for field, value in kwargs.items():
|
|
95
|
-
if field in allowed_fields:
|
|
96
|
-
update_data[field] = value
|
|
97
|
-
else:
|
|
98
|
-
self.sdk.logger.warning(f"Ignoring unknown form field: {field}")
|
|
99
|
-
|
|
100
|
-
if not update_data:
|
|
101
|
-
raise ValueError("No valid form field properties provided for update")
|
|
102
|
-
|
|
103
|
-
response_data = self.sdk._make_request('PUT', endpoint, data=update_data)
|
|
104
|
-
|
|
105
|
-
if isinstance(response_data, dict) and 'data' in response_data:
|
|
106
|
-
capture_data = response_data['data']
|
|
107
|
-
return Capture.from_dict(capture_data)
|
|
108
|
-
else:
|
|
109
|
-
self.sdk.logger.warning("Unexpected response format for form field update")
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
except TallyfyError as e:
|
|
113
|
-
self.sdk.logger.error(f"Failed to update form field {field_id}: {e}")
|
|
114
|
-
raise
|
|
115
|
-
|
|
116
|
-
def move_form_field(self, org_id: str, template_id: str, from_step: str, field_id: str, to_step: str, position: int = 1) -> bool:
|
|
117
|
-
"""
|
|
118
|
-
Move form field between steps.
|
|
119
|
-
|
|
120
|
-
Args:
|
|
121
|
-
org_id: Organization ID
|
|
122
|
-
template_id: Template ID
|
|
123
|
-
from_step: Source step ID
|
|
124
|
-
field_id: Form field ID to move
|
|
125
|
-
to_step: Target step ID
|
|
126
|
-
position: Position in target step (default: 1)
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
True if move was successful
|
|
130
|
-
|
|
131
|
-
Raises:
|
|
132
|
-
TallyfyError: If the request fails
|
|
133
|
-
"""
|
|
134
|
-
try:
|
|
135
|
-
endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{from_step}/captures/{field_id}/move"
|
|
136
|
-
|
|
137
|
-
move_data = {
|
|
138
|
-
'to_step_id': to_step,
|
|
139
|
-
'position': position
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
response_data = self.sdk._make_request('POST', endpoint, data=move_data)
|
|
143
|
-
|
|
144
|
-
# Check if move was successful
|
|
145
|
-
return isinstance(response_data, dict) and response_data.get('success', False)
|
|
146
|
-
|
|
147
|
-
except TallyfyError as e:
|
|
148
|
-
self.sdk.logger.error(f"Failed to move form field {field_id}: {e}")
|
|
149
|
-
raise
|
|
150
|
-
|
|
151
|
-
def delete_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str) -> bool:
|
|
152
|
-
"""
|
|
153
|
-
Delete a form field from a step.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
org_id: Organization ID
|
|
157
|
-
template_id: Template ID
|
|
158
|
-
step_id: Step ID
|
|
159
|
-
field_id: Form field ID
|
|
160
|
-
|
|
161
|
-
Returns:
|
|
162
|
-
True if deletion was successful
|
|
163
|
-
|
|
164
|
-
Raises:
|
|
165
|
-
TallyfyError: If the request fails
|
|
166
|
-
"""
|
|
167
|
-
try:
|
|
168
|
-
endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures/{field_id}"
|
|
169
|
-
|
|
170
|
-
response_data = self.sdk._make_request('DELETE', endpoint)
|
|
171
|
-
|
|
172
|
-
# Check if deletion was successful
|
|
173
|
-
return isinstance(response_data, dict) and response_data.get('success', False)
|
|
174
|
-
|
|
175
|
-
except TallyfyError as e:
|
|
176
|
-
self.sdk.logger.error(f"Failed to delete form field {field_id}: {e}")
|
|
177
|
-
raise
|
|
178
|
-
|
|
179
|
-
def get_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str) -> List[str]:
|
|
180
|
-
"""
|
|
181
|
-
Get current dropdown options for analysis.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
org_id: Organization ID
|
|
185
|
-
template_id: Template ID
|
|
186
|
-
step_id: Step ID
|
|
187
|
-
field_id: Form field ID
|
|
188
|
-
|
|
189
|
-
Returns:
|
|
190
|
-
List of dropdown option strings
|
|
191
|
-
|
|
192
|
-
Raises:
|
|
193
|
-
TallyfyError: If the request fails
|
|
194
|
-
"""
|
|
195
|
-
try:
|
|
196
|
-
endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}/captures/{field_id}"
|
|
197
|
-
|
|
198
|
-
response_data = self.sdk._make_request('GET', endpoint)
|
|
199
|
-
|
|
200
|
-
if isinstance(response_data, dict) and 'data' in response_data:
|
|
201
|
-
capture_data = response_data['data']
|
|
202
|
-
options = capture_data.get('options', [])
|
|
203
|
-
|
|
204
|
-
# Extract option values/labels
|
|
205
|
-
if isinstance(options, list):
|
|
206
|
-
return [opt.get('label', opt.get('value', str(opt))) if isinstance(opt, dict) else str(opt) for opt in options]
|
|
207
|
-
else:
|
|
208
|
-
return []
|
|
209
|
-
else:
|
|
210
|
-
self.sdk.logger.warning("Unexpected response format for form field options")
|
|
211
|
-
return []
|
|
212
|
-
|
|
213
|
-
except TallyfyError as e:
|
|
214
|
-
self.sdk.logger.error(f"Failed to get dropdown options for field {field_id}: {e}")
|
|
215
|
-
raise
|
|
216
|
-
|
|
217
|
-
def update_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str, options: List[str]) -> bool:
|
|
218
|
-
"""
|
|
219
|
-
Update dropdown options (for external data integration).
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
org_id: Organization ID
|
|
223
|
-
template_id: Template ID
|
|
224
|
-
step_id: Step ID
|
|
225
|
-
field_id: Form field ID
|
|
226
|
-
options: List of new option strings
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
True if update was successful
|
|
230
|
-
|
|
231
|
-
Raises:
|
|
232
|
-
TallyfyError: If the request fails
|
|
233
|
-
"""
|
|
234
|
-
try:
|
|
235
|
-
# Format options for API
|
|
236
|
-
formatted_options = []
|
|
237
|
-
for i, option in enumerate(options):
|
|
238
|
-
if isinstance(option, str):
|
|
239
|
-
formatted_options.append({
|
|
240
|
-
'value': option.lower().replace(' ', '_'),
|
|
241
|
-
'label': option,
|
|
242
|
-
'position': i + 1
|
|
243
|
-
})
|
|
244
|
-
elif isinstance(option, dict):
|
|
245
|
-
formatted_options.append(option)
|
|
246
|
-
else:
|
|
247
|
-
formatted_options.append({
|
|
248
|
-
'value': str(option),
|
|
249
|
-
'label': str(option),
|
|
250
|
-
'position': i + 1
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
# Update the field with new options
|
|
254
|
-
updated_capture = self.update_form_field(
|
|
255
|
-
org_id, template_id, step_id, field_id,
|
|
256
|
-
options=formatted_options
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
return updated_capture is not None
|
|
260
|
-
|
|
261
|
-
except TallyfyError as e:
|
|
262
|
-
self.sdk.logger.error(f"Failed to update dropdown options for field {field_id}: {e}")
|
|
263
|
-
raise
|
|
264
|
-
|
|
265
|
-
def suggest_form_fields_for_step(self, org_id: str, template_id: str, step_id: str) -> List[Dict[str, Any]]:
|
|
266
|
-
"""
|
|
267
|
-
AI-powered suggestions for relevant form fields based on step content.
|
|
268
|
-
|
|
269
|
-
Args:
|
|
270
|
-
org_id: Organization ID
|
|
271
|
-
template_id: Template ID
|
|
272
|
-
step_id: Step ID to analyze
|
|
273
|
-
|
|
274
|
-
Returns:
|
|
275
|
-
List of suggested form field configurations
|
|
276
|
-
|
|
277
|
-
Raises:
|
|
278
|
-
TallyfyError: If the request fails
|
|
279
|
-
"""
|
|
280
|
-
try:
|
|
281
|
-
# Get the step details to analyze
|
|
282
|
-
step = self.get_step(org_id, template_id, step_id)
|
|
283
|
-
if not step:
|
|
284
|
-
raise TallyfyError(f"Step {step_id} not found")
|
|
285
|
-
|
|
286
|
-
# Get template context for better suggestions
|
|
287
|
-
template = self.sdk.get_template(org_id, template_id)
|
|
288
|
-
if not template:
|
|
289
|
-
raise TallyfyError(f"Template {template_id} not found")
|
|
290
|
-
|
|
291
|
-
# Analyze step content for intelligent suggestions
|
|
292
|
-
step_title = step.title.lower() if step.title else ''
|
|
293
|
-
step_summary = step.summary.lower() if step.summary else ''
|
|
294
|
-
step_type = step.step_type or 'task'
|
|
295
|
-
existing_fields = step.captures or []
|
|
296
|
-
|
|
297
|
-
# Combined text for analysis
|
|
298
|
-
step_text = f"{step_title} {step_summary}".strip()
|
|
299
|
-
|
|
300
|
-
suggestions = []
|
|
301
|
-
|
|
302
|
-
# Rule-based suggestions based on common patterns
|
|
303
|
-
field_patterns = {
|
|
304
|
-
# Approval and Review patterns
|
|
305
|
-
'approval': {
|
|
306
|
-
'keywords': ['approve', 'review', 'sign off', 'accept', 'reject', 'confirm'],
|
|
307
|
-
'fields': [
|
|
308
|
-
{
|
|
309
|
-
'field_type': 'dropdown',
|
|
310
|
-
'label': 'Decision',
|
|
311
|
-
'options': [{'value': 'approved', 'label': 'Approved'}, {'value': 'rejected', 'label': 'Rejected'}, {'value': 'needs_revision', 'label': 'Needs Revision'}],
|
|
312
|
-
'required': True,
|
|
313
|
-
'reasoning': 'Approval steps typically need a decision field'
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
'field_type': 'textarea',
|
|
317
|
-
'label': 'Comments',
|
|
318
|
-
'required': False,
|
|
319
|
-
'reasoning': 'Comments are useful for providing feedback'
|
|
320
|
-
}
|
|
321
|
-
]
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
# Contact and Communication patterns
|
|
325
|
-
'contact': {
|
|
326
|
-
'keywords': ['contact', 'call', 'email', 'phone', 'reach out', 'communicate'],
|
|
327
|
-
'fields': [
|
|
328
|
-
{
|
|
329
|
-
'field_type': 'text',
|
|
330
|
-
'label': 'Contact Method',
|
|
331
|
-
'required': True,
|
|
332
|
-
'reasoning': 'Track how contact was made'
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
'field_type': 'date',
|
|
336
|
-
'label': 'Contact Date',
|
|
337
|
-
'required': True,
|
|
338
|
-
'reasoning': 'Record when contact was made'
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
'field_type': 'textarea',
|
|
342
|
-
'label': 'Contact Notes',
|
|
343
|
-
'required': False,
|
|
344
|
-
'reasoning': 'Document the conversation or interaction'
|
|
345
|
-
}
|
|
346
|
-
]
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
# Document and File patterns
|
|
350
|
-
'document': {
|
|
351
|
-
'keywords': ['document', 'file', 'upload', 'attach', 'report', 'contract', 'agreement'],
|
|
352
|
-
'fields': [
|
|
353
|
-
{
|
|
354
|
-
'field_type': 'file',
|
|
355
|
-
'label': 'Document Upload',
|
|
356
|
-
'required': True,
|
|
357
|
-
'reasoning': 'File upload for document-related steps'
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
'field_type': 'text',
|
|
361
|
-
'label': 'Document Title',
|
|
362
|
-
'required': False,
|
|
363
|
-
'reasoning': 'Name or title of the document'
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
'field_type': 'textarea',
|
|
367
|
-
'label': 'Document Description',
|
|
368
|
-
'required': False,
|
|
369
|
-
'reasoning': 'Brief description of the document'
|
|
370
|
-
}
|
|
371
|
-
]
|
|
372
|
-
},
|
|
373
|
-
|
|
374
|
-
# Payment and Financial patterns
|
|
375
|
-
'payment': {
|
|
376
|
-
'keywords': ['payment', 'invoice', 'cost', 'price', 'amount', 'bill', 'expense'],
|
|
377
|
-
'fields': [
|
|
378
|
-
{
|
|
379
|
-
'field_type': 'number',
|
|
380
|
-
'label': 'Amount',
|
|
381
|
-
'required': True,
|
|
382
|
-
'reasoning': 'Financial steps need amount tracking'
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
'field_type': 'dropdown',
|
|
386
|
-
'label': 'Currency',
|
|
387
|
-
'options': [{'value': 'USD', 'label': 'USD'}, {'value': 'EUR', 'label': 'EUR'}, {'value': 'GBP', 'label': 'GBP'}],
|
|
388
|
-
'required': True,
|
|
389
|
-
'reasoning': 'Specify currency for financial transactions'
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
'field_type': 'date',
|
|
393
|
-
'label': 'Payment Date',
|
|
394
|
-
'required': False,
|
|
395
|
-
'reasoning': 'Track when payment was made'
|
|
396
|
-
}
|
|
397
|
-
]
|
|
398
|
-
},
|
|
399
|
-
|
|
400
|
-
# Quality and Testing patterns
|
|
401
|
-
'quality': {
|
|
402
|
-
'keywords': ['test', 'quality', 'check', 'verify', 'validate', 'inspect'],
|
|
403
|
-
'fields': [
|
|
404
|
-
{
|
|
405
|
-
'field_type': 'dropdown',
|
|
406
|
-
'label': 'Test Result',
|
|
407
|
-
'options': [{'value': 'pass', 'label': 'Pass'}, {'value': 'fail', 'label': 'Fail'}, {'value': 'partial', 'label': 'Partial Pass'}],
|
|
408
|
-
'required': True,
|
|
409
|
-
'reasoning': 'Quality steps need result tracking'
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
'field_type': 'textarea',
|
|
413
|
-
'label': 'Test Notes',
|
|
414
|
-
'required': False,
|
|
415
|
-
'reasoning': 'Document test findings and issues'
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
'field_type': 'number',
|
|
419
|
-
'label': 'Score',
|
|
420
|
-
'required': False,
|
|
421
|
-
'reasoning': 'Numerical rating for quality assessment'
|
|
422
|
-
}
|
|
423
|
-
]
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
# Schedule and Time patterns
|
|
427
|
-
'schedule': {
|
|
428
|
-
'keywords': ['schedule', 'meeting', 'appointment', 'deadline', 'due', 'time'],
|
|
429
|
-
'fields': [
|
|
430
|
-
{
|
|
431
|
-
'field_type': 'datetime',
|
|
432
|
-
'label': 'Scheduled Time',
|
|
433
|
-
'required': True,
|
|
434
|
-
'reasoning': 'Scheduling steps need date and time'
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
'field_type': 'text',
|
|
438
|
-
'label': 'Location',
|
|
439
|
-
'required': False,
|
|
440
|
-
'reasoning': 'Meeting location or venue'
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
'field_type': 'textarea',
|
|
444
|
-
'label': 'Agenda',
|
|
445
|
-
'required': False,
|
|
446
|
-
'reasoning': 'Meeting agenda or notes'
|
|
447
|
-
}
|
|
448
|
-
]
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
# Check existing field types to avoid duplicates
|
|
453
|
-
existing_field_types = set()
|
|
454
|
-
existing_field_labels = set()
|
|
455
|
-
for field in existing_fields:
|
|
456
|
-
if hasattr(field, 'field_type'):
|
|
457
|
-
existing_field_types.add(field.field_type)
|
|
458
|
-
if hasattr(field, 'label'):
|
|
459
|
-
existing_field_labels.add(field.label.lower())
|
|
460
|
-
|
|
461
|
-
# Analyze step content against patterns
|
|
462
|
-
matched_patterns = []
|
|
463
|
-
for pattern_name, pattern_data in field_patterns.items():
|
|
464
|
-
keyword_matches = sum(1 for keyword in pattern_data['keywords'] if keyword in step_text)
|
|
465
|
-
if keyword_matches > 0:
|
|
466
|
-
matched_patterns.append((pattern_name, keyword_matches, pattern_data))
|
|
467
|
-
|
|
468
|
-
# Sort by relevance (number of keyword matches)
|
|
469
|
-
matched_patterns.sort(key=lambda x: x[1], reverse=True)
|
|
470
|
-
|
|
471
|
-
# Generate suggestions from matched patterns
|
|
472
|
-
suggested_count = 0
|
|
473
|
-
max_suggestions = 5
|
|
474
|
-
|
|
475
|
-
for pattern_name, matches, pattern_data in matched_patterns:
|
|
476
|
-
if suggested_count >= max_suggestions:
|
|
477
|
-
break
|
|
478
|
-
|
|
479
|
-
for field_config in pattern_data['fields']:
|
|
480
|
-
if suggested_count >= max_suggestions:
|
|
481
|
-
break
|
|
482
|
-
|
|
483
|
-
# Skip if similar field already exists
|
|
484
|
-
field_label_lower = field_config['label'].lower()
|
|
485
|
-
if field_label_lower in existing_field_labels:
|
|
486
|
-
continue
|
|
487
|
-
|
|
488
|
-
# Add suggestion with metadata
|
|
489
|
-
suggestion = {
|
|
490
|
-
'field_config': field_config.copy(),
|
|
491
|
-
'confidence': min(0.9, 0.3 + (matches * 0.2)), # Confidence based on keyword matches
|
|
492
|
-
'pattern_matched': pattern_name,
|
|
493
|
-
'keyword_matches': matches,
|
|
494
|
-
'priority': 'high' if matches >= 2 else 'medium' if matches >= 1 else 'low'
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
# Add position suggestion
|
|
498
|
-
suggestion['field_config']['position'] = len(existing_fields) + suggested_count + 1
|
|
499
|
-
|
|
500
|
-
suggestions.append(suggestion)
|
|
501
|
-
suggested_count += 1
|
|
502
|
-
|
|
503
|
-
# If no specific patterns matched, provide generic useful fields
|
|
504
|
-
if not suggestions:
|
|
505
|
-
generic_suggestions = [
|
|
506
|
-
{
|
|
507
|
-
'field_config': {
|
|
508
|
-
'field_type': 'textarea',
|
|
509
|
-
'label': 'Notes',
|
|
510
|
-
'required': False,
|
|
511
|
-
'position': len(existing_fields) + 1
|
|
512
|
-
},
|
|
513
|
-
'confidence': 0.6,
|
|
514
|
-
'pattern_matched': 'generic',
|
|
515
|
-
'keyword_matches': 0,
|
|
516
|
-
'priority': 'medium',
|
|
517
|
-
'reasoning': 'Notes field is useful for most steps to capture additional information'
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
'field_config': {
|
|
521
|
-
'field_type': 'dropdown',
|
|
522
|
-
'label': 'Status',
|
|
523
|
-
'options': [{'value': 'completed', 'label': 'Completed'}, {'value': 'in_progress', 'label': 'In Progress'}, {'value': 'blocked', 'label': 'Blocked'}],
|
|
524
|
-
'required': False,
|
|
525
|
-
'position': len(existing_fields) + 2
|
|
526
|
-
},
|
|
527
|
-
'confidence': 0.5,
|
|
528
|
-
'pattern_matched': 'generic',
|
|
529
|
-
'keyword_matches': 0,
|
|
530
|
-
'priority': 'low',
|
|
531
|
-
'reasoning': 'Status tracking can be helpful for workflow management'
|
|
532
|
-
}
|
|
533
|
-
]
|
|
534
|
-
suggestions = generic_suggestions
|
|
535
|
-
|
|
536
|
-
# Add implementation guidance
|
|
537
|
-
for suggestion in suggestions:
|
|
538
|
-
suggestion['implementation'] = {
|
|
539
|
-
'method': 'add_form_field_to_step',
|
|
540
|
-
'parameters': {
|
|
541
|
-
'org_id': org_id,
|
|
542
|
-
'template_id': template_id,
|
|
543
|
-
'step_id': step_id,
|
|
544
|
-
'field_data': suggestion['field_config']
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return suggestions
|
|
549
|
-
|
|
550
|
-
except TallyfyError as e:
|
|
551
|
-
self.sdk.logger.error(f"Failed to suggest form fields for step {step_id}: {e}")
|
|
552
|
-
raise
|
|
553
|
-
|
|
554
|
-
def get_step(self, org_id: str, template_id: str, step_id: str) -> Optional[Step]:
|
|
555
|
-
"""
|
|
556
|
-
Get a specific step with its details.
|
|
557
|
-
|
|
558
|
-
Args:
|
|
559
|
-
org_id: Organization ID
|
|
560
|
-
template_id: Template ID
|
|
561
|
-
step_id: Step ID
|
|
562
|
-
|
|
563
|
-
Returns:
|
|
564
|
-
Step object or None if not found
|
|
565
|
-
|
|
566
|
-
Raises:
|
|
567
|
-
TallyfyError: If the request fails
|
|
568
|
-
"""
|
|
569
|
-
try:
|
|
570
|
-
endpoint = f"organizations/{org_id}/checklists/{template_id}/steps/{step_id}"
|
|
571
|
-
response_data = self.sdk._make_request('GET', endpoint)
|
|
572
|
-
|
|
573
|
-
if isinstance(response_data, dict) and 'data' in response_data:
|
|
574
|
-
step_data = response_data['data']
|
|
575
|
-
return Step.from_dict(step_data)
|
|
576
|
-
else:
|
|
577
|
-
self.sdk.logger.warning("Unexpected response format for step")
|
|
578
|
-
return None
|
|
579
|
-
|
|
580
|
-
except TallyfyError as e:
|
|
581
|
-
self.sdk.logger.error(f"Failed to get step {step_id}: {e}")
|
|
582
|
-
raise
|