Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion libs/lbox-alignerr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ Alignerr workspace management for Labelbox.

This package provides functionality for managing Alignerr projects, including:
- Project creation and configuration
- Rate management for labelers and reviewers
- Domain and tag management
- Workforce management
2 changes: 1 addition & 1 deletion libs/lbox-alignerr/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lbox-alignerr"
version = "0.2.0"
version = "0.3.0"
description = "Alignerr workspace management for Labelbox"
authors = [
{ name = "Labelbox", email = "engineering@labelbox.com" }
Expand Down
20 changes: 9 additions & 11 deletions libs/lbox-alignerr/src/alignerr/alignerr_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import logging

from alignerr.schema.project_rate import ProjectRateV2
from alignerr.schema.project_domain import ProjectDomain
from alignerr.schema.enchanced_resource_tags import (
EnhancedResourceTag,
Expand All @@ -17,6 +16,11 @@

logger = logging.getLogger(__name__)

PAY_BY_ROLE_REMOVED_MSG = (
"Pay By Role rates were removed. "
"Configure project rates in the Labelbox Rates UI (Pay By Activity)."
)


if TYPE_CHECKING:
from labelbox import Client
Expand Down Expand Up @@ -63,17 +67,11 @@ def add_domain(self, project_domain: ProjectDomain):
domain_ids=[project_domain.uid],
)

def get_project_rates(self) -> list["ProjectRateV2"]:
return ProjectRateV2.get_by_project_id(
client=self.client, project_id=self.project.uid
)
def get_project_rates(self, *args, **kwargs):
raise NotImplementedError(PAY_BY_ROLE_REMOVED_MSG)

def set_project_rate(self, project_rate_input):
return ProjectRateV2.set_project_rate(
client=self.client,
project_id=self.project.uid,
project_rate_input=project_rate_input,
)
def set_project_rate(self, *args, **kwargs):
raise NotImplementedError(PAY_BY_ROLE_REMOVED_MSG)

def set_tags(self, tag_names: list[str], tag_type: ResourceTagType):
# Convert tag names to tag IDs
Expand Down
115 changes: 8 additions & 107 deletions libs/lbox-alignerr/src/alignerr/alignerr_project_builder.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import datetime
from enum import Enum
from typing import TYPE_CHECKING, Optional, Union, List
import logging

from alignerr.schema.project_rate import BillingMode
from alignerr.schema.project_rate import ProjectRateInput
from alignerr.alignerr_project import PAY_BY_ROLE_REMOVED_MSG
from alignerr.schema.project_domain import ProjectDomain
from alignerr.schema.enchanced_resource_tags import (
EnhancedResourceTag,
Expand All @@ -28,18 +26,15 @@ class ValidationType(Enum):

if TYPE_CHECKING:
from labelbox import Client
from alignerr.alignerr_project import AlignerrProject, AlignerrRole
from alignerr.alignerr_project import AlignerrProject


class AlignerrProjectBuilder:
def __init__(self, client: "Client"):
self.client = client
self._alignerr_rates: dict[str, ProjectRateInput] = {}
self._customer_rate: Optional[ProjectRateInput] = None
self._domains: list[ProjectDomain] = []
self._enhanced_resource_tags: list[EnhancedResourceTag] = []
self._project_owner_email: Optional[str] = None
self.role_name_to_id = self._get_role_name_to_id()

def set_name(self, name: str):
self.project_name = name
Expand All @@ -49,72 +44,11 @@ def set_media_type(self, media_type: "MediaType"):
self.project_media_type = media_type
return self

def set_alignerr_role_rate(
self,
*,
role_name: "AlignerrRole",
rate: float,
billing_mode: BillingMode,
effective_since: datetime.datetime,
effective_until: Optional[datetime.datetime] = None,
):
if role_name.value not in self.role_name_to_id:
raise ValueError(f"Role {role_name.value} not found")

role_id = self.role_name_to_id[role_name.value]
role_name_str = role_name.value

# Convert datetime objects to ISO format strings
effective_since_str = (
effective_since.isoformat()
if isinstance(effective_since, datetime.datetime)
else effective_since
)
effective_until_str = (
effective_until.isoformat()
if isinstance(effective_until, datetime.datetime)
else effective_until
)

self._alignerr_rates[role_name_str] = ProjectRateInput(
rateForId=role_id,
isBillRate=False,
billingMode=billing_mode,
rate=rate,
effectiveSince=effective_since_str,
effectiveUntil=effective_until_str,
)
return self
def set_alignerr_role_rate(self, *args, **kwargs):
raise NotImplementedError(PAY_BY_ROLE_REMOVED_MSG)

def set_customer_rate(
self,
*,
rate: float,
billing_mode: BillingMode,
effective_since: datetime.datetime,
effective_until: Optional[datetime.datetime] = None,
):
# Convert datetime objects to ISO format strings
effective_since_str = (
effective_since.isoformat()
if isinstance(effective_since, datetime.datetime)
else effective_since
)
effective_until_str = (
effective_until.isoformat()
if isinstance(effective_until, datetime.datetime)
else effective_until
)

self._customer_rate = ProjectRateInput(
rateForId="", # Empty string for customer rate
isBillRate=True,
billingMode=billing_mode,
rate=rate,
effectiveSince=effective_since_str,
effectiveUntil=effective_until_str,
)
return self
def set_customer_rate(self, *args, **kwargs):
raise NotImplementedError(PAY_BY_ROLE_REMOVED_MSG)

def set_domains(self, domains: list[str]):
for domain in domains:
Expand Down Expand Up @@ -189,18 +123,12 @@ def create(self, skip_validation: Union[bool, List[ValidationType]] = False):
self.client, labelbox_project, _internal=True
)

self._create_rates(alignerr_project)
self._create_domains(alignerr_project)
self._create_resource_tags(alignerr_project)
self._create_project_owner(alignerr_project)

return alignerr_project

def _create_rates(self, alignerr_project: "AlignerrProject"):
for alignerr_role, project_rate in self._alignerr_rates.items():
logger.info(f"Setting project rate for {alignerr_role}")
alignerr_project.set_project_rate(project_rate)

def _create_domains(self, alignerr_project: "AlignerrProject"):
if self._domains:
logger.info(f"Setting domains: {[domain.name for domain in self._domains]}")
Expand Down Expand Up @@ -248,30 +176,11 @@ def _create_project_owner(self, alignerr_project: "AlignerrProject"):
project_owner_user_id=user_id,
)

def _validate_alignerr_rates(self):
# Import here to avoid circular imports
from alignerr.alignerr_project import AlignerrRole

required_role_rates = set(
[AlignerrRole.Labeler.value, AlignerrRole.Reviewer.value]
)

for role_name in self._alignerr_rates.keys():
required_role_rates.remove(role_name)
if len(required_role_rates) > 0:
raise ValueError(f"Required role rates are not set: {required_role_rates}")

def _validate_customer_rate(self):
if self._customer_rate is None:
raise ValueError("Customer rate is not set")

def _validate_project_owner(self):
if self._project_owner_email is None:
raise ValueError("Project owner is not set")

def _validate(self):
self._validate_alignerr_rates()
self._validate_customer_rate()
self._validate_project_owner()

def _validate_selective(self, skip_validations: List[ValidationType]):
Expand All @@ -280,19 +189,11 @@ def _validate_selective(self, skip_validations: List[ValidationType]):
Args:
skip_validations: List of ValidationType enums to skip
"""
if ValidationType.ALIGNERR_RATE not in skip_validations:
self._validate_alignerr_rates()

if ValidationType.CUSTOMER_RATE not in skip_validations:
self._validate_customer_rate()

# ALIGNERR_RATE / CUSTOMER_RATE are retained for callers that still pass them
# in skip_validation lists; rate validation itself has been removed.
if ValidationType.PROJECT_OWNER not in skip_validations:
self._validate_project_owner()

def _get_role_name_to_id(self) -> dict[str, str]:
roles = self.client.get_roles()
return {role.name: role.uid for role in roles.values()}

def _find_user_by_email(self, email: str) -> Optional[str]:
"""Find user ID by email in the organization.

Expand Down
Loading
Loading