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}, }