"""Ticket-type registry driving query/parse/label behaviour per ticket flavour. Each :class:`TicketType` bundles everything that differs between the offboarding and suspension flows: the JQL queries, the regexes that detect and parse the ticket description, the date extractor, the downstream Auth0 operation, and the Jira label set. The query handler in :mod:`jira_client.app` is driven entirely by these objects, so adding a future ticket flavour is a matter of adding one more registry entry rather than forking the handler. This module sits at the top of the package's internal dependency chain: it imports from :mod:`jira_client.constants` and :mod:`jira_client.utils`, and neither of those imports back from here. """ import re from collections.abc import Callable from dataclasses import dataclass from jira_client.constants import JiraLabels, JQLQueries, RegexPatterns from jira_client.utils import SearchUtils @dataclass(frozen=True) class LabelSet: """Jira labels for one ticket type's two-phase flow.""" approved: str automation_complete: str no_actions_required: str pending_approval: str validation_failed: str @dataclass(frozen=True) class TicketType: """Config describing how to query and parse one flavour of Jira ticket. :param name: Human-readable type name (``offboarding`` | ``suspension``). :param jql_new: JQL for the Phase 1 (new/open) query. :param jql_approved: JQL for the Phase 2 (approved) query. :param sentinel_re: Regex that confirms a description is this ticket type. :param full_name_re: Regex whose first group captures the employee name. :param date_extractor: Callable pulling the effective ISO date from the text. :param auth0_action: Phase 2 Auth0 operation (``delete`` | ``suspend``). :param operation: GitHub-issue operation wording (``offboard`` | ``suspend``). :param labels: The Jira label set for this flow. """ name: str jql_new: str jql_approved: str sentinel_re: re.Pattern[str] full_name_re: re.Pattern[str] date_extractor: Callable[[str], str | None] auth0_action: str operation: str labels: LabelSet OFFBOARDING = TicketType( name='offboarding', jql_new=JQLQueries.NEW_OPEN_OFFBOARDING_TICKETS, jql_approved=JQLQueries.APPROVED_OFFBOARDING_TICKETS, sentinel_re=RegexPatterns.OFFBOARDING_SENTINEL_RE, full_name_re=RegexPatterns.OFFBOARDING_FULL_NAME_RE, date_extractor=SearchUtils.find_last_working_day, auth0_action='delete', operation='offboard', labels=LabelSet( approved=JiraLabels.APPROVED_FOR_OFFBOARDING, automation_complete=JiraLabels.AUTOMATION_COMPLETE, no_actions_required=JiraLabels.NO_ACTIONS_REQUIRED, pending_approval=JiraLabels.PENDING_APPROVAL, validation_failed=JiraLabels.VALIDATION_FAILED, ), ) SUSPENSION = TicketType( name='suspension', jql_new=JQLQueries.NEW_OPEN_SUSPEND_TICKETS, jql_approved=JQLQueries.APPROVED_SUSPEND_TICKETS, sentinel_re=RegexPatterns.SUSPEND_SENTINEL_RE, full_name_re=RegexPatterns.SUSPEND_FULL_NAME_RE, date_extractor=SearchUtils.find_suspension_start_date, auth0_action='suspend', operation='suspend', labels=LabelSet( approved=JiraLabels.APPROVED_FOR_SUSPENSION, automation_complete=JiraLabels.SUSPENSION_COMPLETE, no_actions_required=JiraLabels.SUSPEND_NO_ACTIONS_REQUIRED, pending_approval=JiraLabels.SUSPEND_PENDING_APPROVAL, validation_failed=JiraLabels.SUSPEND_VALIDATION_FAILED, ), )