# Tallyfy SDK

A comprehensive Python SDK for interacting with the Tallyfy API. This SDK provides a clean, modular interface for managing users, tasks, templates, and form fields in your Tallyfy organization.

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [Core Features](#core-features)
- [API Reference](#api-reference)
- [Data Models](#data-models)
- [Error Handling](#error-handling)
- [Examples](#examples)
- [Advanced Usage](#advanced-usage)
- [Contributing](#contributing)

## Installation

```bash
pip install tallyfy
```

**Dependencies:**
- `requests` - HTTP client library
- `typing` - Type hints support (Python 3.5+)

## Quick Start

```python
from tallyfy import TallyfySDK

# Initialize the SDK
sdk = TallyfySDK(
    api_key="your_api_key_here"
)

# Get organization users
users = sdk.users.get_organization_users(org_id="your_org_id")

# Get current user's tasks
my_tasks = sdk.tasks.get_my_tasks(org_id="your_org_id")

# Search for templates
templates = sdk.search(org_id="your_org_id", search_query="onboarding", search_type="blueprint")

# Get a specific template
template = sdk.templates.get_template(org_id="your_org_id", template_name="Employee Onboarding")
```

## Architecture

The Tallyfy SDK follows a modular architecture with specialized management classes and comprehensive data models:

### Core Components

- **`TallyfySDK`** - Main SDK class that orchestrates all operations with backward compatibility methods
- **`BaseSDK`** - Base class with HTTP request handling, retry logic, and connection pooling
- **Management Modules:**
  - `UserManager` - User and guest operations with modular retrieval and invitation components
  - `TaskManager` - Task and process operations with search and creation capabilities
  - `TemplateManager` - Template/checklist operations with automation analysis and health assessment
  - `FormFieldManager` - Form field operations with AI-powered suggestions and dropdown management
- **Error Handling:**
  - `TallyfyError` - Custom exception handling with status codes and response data

### File Structure

```
tallyfy/
├── __init__.py                        # SDK exports and version
├── core.py                            # BaseSDK and TallyfySDK classes
├── models.py                          # Data models and types
├── user_management/                   # User and guest management (modular)
│   ├── __init__.py                    # UserManager with unified interface
│   ├── base.py                        # Common validation and error handling
│   ├── retrieval.py                   # User and guest retrieval operations
│   └── invitation.py                  # User invitation operations
├── task_management/                   # Task and process management (modular)
│   ├── __init__.py                    # TaskManager with unified interface
│   ├── base.py                        # Common task operations base
│   ├── retrieval.py                   # Task and process retrieval
│   ├── search.py                      # Search functionality
│   └── creation.py                    # Task creation operations
├── template_management/               # Template management (modular)
│   ├── __init__.py                    # TemplateManager with unified interface
│   ├── base.py                        # Common template operations
│   ├── basic_operations.py            # Template retrieval and CRUD operations
│   ├── automation.py                  # Automation rule management
│   └── analysis.py                    # Step dependency and visibility analysis
├── form_fields_management/            # Form field management (modular)
│   ├── __init__.py                    # FormFieldManager with unified interface
│   ├── base.py                        # Common form field operations
│   ├── crud_operations.py             # CRUD operations for form fields
│   ├── options_management.py          # Dropdown options management
│   └── suggestions.py                 # AI-powered field suggestions
└── README.md                          # This documentation
```

## Core Features

### 🔐 Authentication & Security
- Bearer token authentication with session management
- Configurable request timeouts and connection pooling
- Automatic retry logic for transient failures (5xx errors)
- No retry for client errors (4xx) to prevent API abuse
- Comprehensive error handling with detailed error information

### 👥 User Management
- **Modular architecture** with specialized retrieval and invitation components
- Get organization members with full profile data (country, timezone, job details)
- Get minimal user lists for performance-critical operations
- Manage guests and external users with guest-specific features
- **Enhanced search capabilities** - Find users by email or name with fuzzy matching
- **Batch invitation support** - Invite multiple users with default roles and messages
- **Advanced invitation features** - Custom messages, role validation, and resend functionality
- Support for user groups and permissions with flexible query parameters
- **Convenience methods** - Get all members (users + guests) in a single call

### ✅ Task Management  
- Get tasks for specific users or processes with filtering
- Create standalone tasks with rich assignment options
- Search processes by name with fuzzy matching
- Advanced filtering for organization runs (status, owners, tags, etc.)
- Universal search across processes, templates, and tasks

### 📋 Template Management
- Get templates with full metadata and step details
- Search templates by name with exact and fuzzy matching
- Update template properties and metadata
- Duplicate templates with permission copying options
- **Automation management** - Create, update, and analyze conditional rules
- **Step dependency analysis** - Understand step visibility conditions
- **AI-powered deadline suggestions** for individual steps
- **Template health assessment** - Comprehensive analysis of template quality
- **Automation consolidation** - Optimize and merge redundant rules
- Add assignees and edit step descriptions
- Kickoff field management for template launch forms

### 📝 Form Field Management
- Add form fields to template steps with comprehensive validation
- Support for text, dropdown, date, file upload, WYSIWYG editor fields
- Update field properties, validation rules, and positioning
- Move fields between steps with automatic reordering
- **AI-powered field suggestions** based on step analysis
- Manage dropdown options with bulk updates
- **Smart field recommendations** with confidence scoring
- Field dependency management and conditional logic

### 🔍 Search & Discovery
- Universal search across processes, templates, and tasks
- Exact and fuzzy matching with relevance scoring
- Pagination support with configurable page sizes
- Rich search results with metadata and context
- Process and template name-based search with suggestions

## API Reference

### SDK Initialization

```python
TallyfySDK(
    api_key: str,
    timeout: int = 30,
    max_retries: int = 3,
    retry_delay: float = 1.0
)
```

### User Management

```python
# Get all organization users with optional group data
users = sdk.users.get_organization_users(org_id, with_groups=False)

# Get minimal user list for performance
users_list = sdk.users.get_organization_users_list(org_id)

# Get organization guests with optional statistics
guests = sdk.users.get_organization_guests(org_id, with_stats=False)

# Get current user info
current_user = sdk.users.get_current_user_info(org_id)

# Enhanced search capabilities
user = sdk.users.get_user_by_email(org_id, "john@company.com")
guest = sdk.users.get_guest_by_email(org_id, "contractor@company.com")

# Search members by name (fuzzy matching)
search_results = sdk.users.search_members_by_name(org_id, "John Smith", include_guests=True)
matching_users = search_results['users']
matching_guests = search_results['guests']

# Get all members in one call
all_members = sdk.users.get_all_organization_members(
    org_id, include_guests=True, with_groups=True, with_stats=True
)

# Invite single user to organization
user = sdk.users.invite_user_to_organization(
    org_id, email="new.hire@company.com", 
    first_name="John", last_name="Doe",
    role="standard", 
    message="Welcome! Please complete your onboarding process."
)

# Batch invite multiple users
invitations = [
    {
        "email": "user1@company.com",
        "first_name": "Jane",
        "last_name": "Smith",
        "role": "standard"
    },
    {
        "email": "user2@company.com", 
        "first_name": "Bob",
        "last_name": "Johnson",
        "message": "Welcome to the engineering team!"
    }
]
results = sdk.users.invite_multiple_users(
    org_id, invitations, 
    default_role="light",
    default_message="Welcome to our organization!"
)

# Resend invitation
success = sdk.users.resend_invitation(
    org_id, email="pending@company.com",
    message="Reminder: Please accept your invitation to join our organization."
)

# Generate custom invitation message
message = sdk.users.get_invitation_template_message(
    org_name="ACME Corp",
    custom_text="We're excited to have you join our innovative team!"
)

# Validate invitation data before sending
invitation_data = {
    "email": "new@company.com",
    "first_name": "Alice",
    "last_name": "Wilson",
    "role": "admin"
}
validated_data = sdk.users.validate_invitation_data(invitation_data)

# Access modular components directly (advanced usage)
# Retrieval operations
users_with_groups = sdk.users.retrieval.get_organization_users(org_id, with_groups=True)

# Invitation operations  
batch_results = sdk.users.invitation.invite_multiple_users(org_id, invitations)
```

### Task Management

```python
# Get current user's tasks
my_tasks = sdk.tasks.get_my_tasks(org_id)

# Get specific user's tasks
user_tasks = sdk.tasks.get_user_tasks(org_id, user_id)

# Get tasks for a process
process_tasks = sdk.tasks.get_tasks_for_process(org_id, process_id=None, process_name="My Process")

# Get organization processes with filtering
runs = sdk.tasks.get_organization_runs(
    org_id, 
    status="active",
    owners="123,456", 
    checklist_id="template_id"
)

# Create standalone task
task = sdk.tasks.create_task(
    org_id, title="Review Document", 
    deadline="2024-12-31T23:59:59Z",
    description="Please review the attached document"
)

# Search processes, templates, or tasks
results = sdk.tasks.search(org_id, "onboarding", search_type="process")

# Search for specific entity types
templates = sdk.tasks.search(org_id, "employee onboarding", search_type="blueprint")
tasks = sdk.tasks.search(org_id, "review", search_type="task")
```

### Template Management

```python
# Get template by ID or name
template = sdk.templates.get_template(org_id, template_id=None, template_name="Onboarding")

# Get template with full step details
template_data = sdk.templates.get_template_with_steps(org_id, template_id)

# Update template metadata
updated = sdk.templates.update_template_metadata(
    org_id, template_id,
    title="New Title",
    summary="Updated summary",
    guidance="New guidance text"
)

# Get template steps
steps = sdk.templates.get_template_steps(org_id, template_id)

# Analyze step dependencies  
dependencies = sdk.templates.get_step_dependencies(org_id, template_id, step_id)

# Advanced template operations
duplicated = sdk.templates.duplicate_template(org_id, template_id, "New Template Name", copy_permissions=True)

# Automation management
automation_data = {
    "automated_alias": "Auto-assign reviewer",
    "conditions": [{"field_id": "department", "operator": "equals", "value": "Engineering"}],
    "actions": [{"type": "assign_step", "step_id": "review_step", "assignee_type": "user", "assignee_id": "123"}]
}
automation = sdk.templates.create_automation_rule(org_id, template_id, automation_data)

# Template metadata management
updated = sdk.templates.update_template_metadata(
    org_id, template_id,
    title="Updated Template Title",
    guidance="New guidance text",
    is_featured=True
)

# Add assignees to step
assignees = {"users": [123, 456], "groups": ["managers"], "guests": ["contractor@company.com"]}
sdk.templates.add_assignees_to_step(org_id, template_id, step_id, assignees)

# Edit step description
sdk.templates.edit_description_on_step(org_id, template_id, step_id, "Updated step description with detailed instructions")

# Add new step to template
step_data = {
    "title": "Quality Review",
    "description": "Perform final quality check",
    "position": 5,
    "assignees": {"users": [789]}
}
new_step = sdk.templates.add_step_to_template(org_id, template_id, step_data)
```

### Form Field Management

```python
# Add form field to step
field_data = {
    "field_type": "text",
    "label": "Customer Name", 
    "required": True,
    "position": 1
}
field = sdk.form_fields.add_form_field_to_step(org_id, template_id, step_id, field_data)

# Update form field
updated_field = sdk.form_fields.update_form_field(
    org_id, template_id, step_id, field_id,
    label="Updated Label",
    required=False
)

# Move field between steps
success = sdk.form_fields.move_form_field(
    org_id, template_id, from_step, field_id, to_step, position=2
)

# Delete form field
success = sdk.form_fields.delete_form_field(org_id, template_id, step_id, field_id)

# Get dropdown options
options = sdk.form_fields.get_dropdown_options(org_id, template_id, step_id, field_id)

# Update dropdown options
success = sdk.form_fields.update_dropdown_options(
    org_id, template_id, step_id, field_id, 
    ["Option 1", "Option 2", "Option 3"]
)
```

## Data Models

The SDK provides comprehensive dataclasses for type safety and easy data access:

### Core Models

- **`User`** - Organization member with full profile data (country, timezone, job details)
- **`Guest`** - External user with limited access and guest-specific details
- **`GuestDetails`** - Extended guest information and statistics
- **`Task`** - Individual work item with owners, deadlines, and process linkage
- **`Run`** - Process instance (workflow execution) with progress tracking
- **`Template`** - Complete template/checklist definition with automation rules
- **`Step`** - Individual step within a template with conditions and assignments

### Assignment and Ownership Models

- **`TaskOwners`** - Task assignment information supporting users, guests, and groups
- **`RunProgress`** - Process completion tracking with step-level progress
- **`Country`** - Geographic data for user profiles
- **`Folder`** - Organizational folder structure for templates and processes

### Template and Automation Models

- **`Tag`** - Industry and topic classification tags
- **`PrerunField`** - Form fields for template kickoff with validation rules
- **`AutomationCondition`** - Conditional logic for workflow automation
- **`AutomationAction`** - Actions triggered by automation rules
- **`AutomatedAction`** - Complete automation rule with conditions and actions
- **`AutomationDeadline`** - Deadline configuration for automated actions
- **`AutomationAssignees`** - Assignee configuration for automated actions

### Step and Form Models

- **`Capture`** - Form fields within steps with validation and positioning
- **`StepStartDate`** - Start date configuration for steps
- **`StepDeadline`** - Deadline configuration for individual steps
- **`StepBpToLaunch`** - Sub-process launch configuration

### Search and Utility Models

- **`SearchResult`** - Unified search results for templates, processes, and tasks
- **`TallyfyError`** - Custom exception with status codes and response data

### Model Features

- **Automatic parsing** from API responses via `from_dict()` class methods
- **Type safety** with comprehensive type hints
- **Nested object support** for complex data structures
- **Default value handling** for optional fields

Example model usage:

```python
# Models automatically parse API responses
users = sdk.users.get_organization_users(org_id)
for user in users:
    print(f"{user.full_name} ({user.email})")
    if user.country:
        print(f"Country: {user.country.name}")

# Access nested data safely
template = sdk.templates.get_template(org_id, template_name="Onboarding")
if template.automated_actions:
    for automation in template.automated_actions:
        print(f"Automation: {automation.automated_alias}")
        for condition in automation.conditions:
            print(f"  Condition: {condition.statement}")
```

## Error Handling

The SDK provides comprehensive error handling through the `TallyfyError` class:

```python
from tallyfy import TallyfyError

try:
    users = sdk.users.get_organization_users("invalid_org_id")
except TallyfyError as e:
    print(f"API Error: {e}")
    print(f"Status Code: {e.status_code}")
    print(f"Response Data: {e.response_data}")
except ValueError as e:
    print(f"Validation Error: {e}")
```

### Error Types

- **`TallyfyError`** - API-specific errors with status codes and response data
- **`ValueError`** - Input validation errors for required parameters
- **`RequestException`** - Network and connection errors (automatically retried)

### Retry Logic

The SDK automatically retries failed requests with configurable settings:

- **Server errors (5xx)** - Automatically retried up to `max_retries` times
- **Client errors (4xx)** - Not retried (indicates user/input error)
- **Network errors** - Retried with exponential backoff
- **Timeout errors** - Retried with configurable delay

## Examples

### Complete User Onboarding Workflow

```python
from tallyfy import TallyfySDK, TaskOwners

sdk = TallyfySDK(api_key="your_api_key")
org_id = "your_org_id"

# 1. Invite new user
new_user = sdk.users.invite_user_to_organization(
    org_id, 
    email="new.hire@company.com",
    first_name="John", 
    last_name="Doe",
    role="standard",
    message="Welcome to the team! Please complete your onboarding."
)

# 2. Create onboarding task
if new_user:
    owners = TaskOwners(users=[new_user.id])
    onboarding_task = sdk.tasks.create_task(
        org_id,
        title="Complete Employee Onboarding",
        deadline="2025-12-31 17:00:00",
        description="Please complete all onboarding steps including HR paperwork and IT setup.",
        owners=owners
    )
    print(f"Created onboarding task: {onboarding_task.id}")
```

### Template Analysis and Enhancement

```python
# Get template with full details
template_data = sdk.templates.get_template_with_steps(org_id, template_name="Project Kickoff")

if template_data:
    template = template_data['template']
    print(f"Template: {template.title}")
    print(f"Steps: {template_data['step_count']}")
    print(f"Automations: {template_data['automation_count']}")
    
    # Analyze each step for improvements
    for step_data in template_data['steps']:
        step_id = step_data['id']
        
        # Get dependency analysis
        dependencies = sdk.templates.get_step_dependencies(org_id, template.id, step_id)
        if dependencies['has_conditional_visibility']:
            print(f"Step '{step_data['title']}' has conditional logic")
```

### Advanced Process Management

```python
# Get all active processes with comprehensive filtering
active_runs = sdk.tasks.get_organization_runs(
    org_id,
    status="active",
    with_data="checklist,tasks,assets,tags",
    form_fields_values=True,
    starred=True
)

# Group by template
template_usage = {}
for run in active_runs:
    template_id = run.checklist_id
    if template_id not in template_usage:
        template_usage[template_id] = {
            'template_name': run.checklist_title,
            'active_count': 0,
            'runs': []
        }
    template_usage[template_id]['active_count'] += 1
    template_usage[template_id]['runs'].append(run)

# Show most used templates
sorted_templates = sorted(template_usage.items(), key=lambda x: x[1]['active_count'], reverse=True)
for template_id, data in sorted_templates[:5]:
    print(f"{data['template_name']}: {data['active_count']} active processes")
```

## Advanced Usage

### Custom Request Configuration

```python
# SDK with custom configuration
sdk = TallyfySDK(
    api_key="your_api_key",
    base_url="https://api.tallyfy.com",
    timeout=60,  # 60 second timeout
    max_retries=5,  # Retry up to 5 times
    retry_delay=2.0  # 2 second delay between retries
)
```

### Accessing Raw API Responses

```python
# For advanced users who need raw API data
template_data = sdk.templates.get_template_with_steps(org_id, template_id)
raw_api_response = template_data['raw_data']

# Access nested API data not exposed in models
custom_fields = raw_api_response.get('custom_metadata', {})
```

### Batch Operations

```python
# Efficiently process multiple operations
org_users = sdk.users.get_organization_users(org_id)
user_tasks = {}

for user in org_users[:10]:  # Process first 10 users
    try:
        tasks = sdk.tasks.get_user_tasks(org_id, user.id)
        user_tasks[user.id] = tasks
        print(f"{user.full_name}: {len(tasks)} tasks")
    except TallyfyError as e:
        print(f"Failed to get tasks for {user.full_name}: {e}")
```


## Contributing

### Code Style

- Follow PEP 8 style guidelines
- Use type hints for all functions and methods
- Add comprehensive docstrings for public APIs
- Include error handling for all external API calls

### Adding New Features

1. Add methods to appropriate management class
2. Create or update data models in `models.py`
3. Add comprehensive docstrings and type hints
4. Include error handling and logging
5. Update this README with usage examples


## Support

For bugs, feature requests, or questions:

1. Check existing issues in the project repository
2. Contact us at: support@tallyfy.com
---

**Version:** 1.0.14
**Last Updated:** 2025