59 lines
1.8 KiB
Python
59 lines
1.8 KiB
Python
from enum import Enum
|
|
|
|
|
|
class OutreachStatus(str, Enum):
|
|
"""Manual outreach funnel on talent_profiles.outreach_status.
|
|
|
|
The app never sends messages: a recruiter shortlists a sourced profile,
|
|
reaches out on LinkedIn themselves, then marks the profile contacted.
|
|
Values are the wire form the Find Talent screen PATCHes; labels are what
|
|
the tabs render.
|
|
"""
|
|
|
|
SOURCED = "sourced"
|
|
SHORTLISTED = "shortlisted"
|
|
CONTACTED = "contacted"
|
|
|
|
@property
|
|
def label(self) -> str:
|
|
return _LABELS[self]
|
|
|
|
@classmethod
|
|
def parse(cls, value):
|
|
"""Accept the stored value or the UI label. None if neither matches."""
|
|
raw = (value or "").strip()
|
|
if not raw:
|
|
return None
|
|
lowered = raw.lower()
|
|
for member in cls:
|
|
if raw == member.value or lowered == member.value or raw == member.label:
|
|
return member
|
|
return None
|
|
|
|
@classmethod
|
|
def values(cls) -> tuple[str, ...]:
|
|
return tuple(m.value for m in cls)
|
|
|
|
@classmethod
|
|
def as_list(cls) -> list[dict]:
|
|
return [{"value": m.value, "label": m.label} for m in cls]
|
|
|
|
|
|
_LABELS = {
|
|
OutreachStatus.SOURCED: "Sourced",
|
|
OutreachStatus.SHORTLISTED: "Shortlisted",
|
|
OutreachStatus.CONTACTED: "Contacted",
|
|
}
|
|
|
|
# One-way funnel with single-step undo. sourced -> contacted is disallowed so
|
|
# every contacted row carries shortlist stamps, and contacted -> sourced is
|
|
# disallowed so un-shortlisting a contacted profile takes two deliberate steps.
|
|
ALLOWED_OUTREACH_TRANSITIONS = {
|
|
OutreachStatus.SOURCED.value: {OutreachStatus.SHORTLISTED.value},
|
|
OutreachStatus.SHORTLISTED.value: {
|
|
OutreachStatus.SOURCED.value,
|
|
OutreachStatus.CONTACTED.value,
|
|
},
|
|
OutreachStatus.CONTACTED.value: {OutreachStatus.SHORTLISTED.value},
|
|
}
|