tallyfy 1.0.4__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.

Files changed (33) hide show
  1. tallyfy/__init__.py +8 -4
  2. tallyfy/core.py +8 -8
  3. tallyfy/form_fields_management/__init__.py +70 -0
  4. tallyfy/form_fields_management/base.py +109 -0
  5. tallyfy/form_fields_management/crud_operations.py +234 -0
  6. tallyfy/form_fields_management/options_management.py +222 -0
  7. tallyfy/form_fields_management/suggestions.py +411 -0
  8. tallyfy/task_management/__init__.py +81 -0
  9. tallyfy/task_management/base.py +125 -0
  10. tallyfy/task_management/creation.py +221 -0
  11. tallyfy/task_management/retrieval.py +211 -0
  12. tallyfy/task_management/search.py +196 -0
  13. tallyfy/template_management/__init__.py +85 -0
  14. tallyfy/template_management/analysis.py +1093 -0
  15. tallyfy/template_management/automation.py +469 -0
  16. tallyfy/template_management/base.py +56 -0
  17. tallyfy/template_management/basic_operations.py +477 -0
  18. tallyfy/template_management/health_assessment.py +763 -0
  19. tallyfy/user_management/__init__.py +69 -0
  20. tallyfy/user_management/base.py +146 -0
  21. tallyfy/user_management/invitation.py +286 -0
  22. tallyfy/user_management/retrieval.py +339 -0
  23. {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/METADATA +120 -56
  24. tallyfy-1.0.5.dist-info/RECORD +28 -0
  25. tallyfy/BUILD.md +0 -5
  26. tallyfy/form_fields_management.py +0 -582
  27. tallyfy/task_management.py +0 -356
  28. tallyfy/template_management.py +0 -2607
  29. tallyfy/user_management.py +0 -235
  30. tallyfy-1.0.4.dist-info/RECORD +0 -13
  31. {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/WHEEL +0 -0
  32. {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/licenses/LICENSE +0 -0
  33. {tallyfy-1.0.4.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