Context: Searching for a new senior level software development job over a 9 week period in summer 2025.

  • Focused mostly on data engineering and backend roles that are in-person or hybrid in the SF Bay Area.
  • Leads from recruiters on LinkedIn were much more likely to lead to interviews+offers.
  • The winning offer came through my personal network.
  • I mostly used Hiring.cafe for prospecting. They’re a scraper with an interface I didn’t hate.
  • squaresinger@lemmy.world
    link
    fedilink
    arrow-up
    1
    ·
    15 hours ago

    Don’t know if there’s a ready-made site for stuff like that, but it’s not hard to do.

    Here’s a quick and dirty AI generated piece of trash code as a proof of concept:

    # sankey_hiring_funnel_direct.py
    # Requires: plotly
    # Install: pip install plotly
    
    import plotly.graph_objects as go
    
    # Node labels (unique)
    labels = [
        "Network",            # 0
        "Hiring.cafe",        # 1
        "Abandoned Lead",     # 2
        "Applied",            # 3
        "Rejected",           # 4
        "No Response",        # 5
        "Screener",           # 6
        "Rejected by Screen", # 7
        "Full Round",         # 8
        "Rejected by Panel",  # 9
        "Offer",              #10
        "Accepted",           #11
        "Declined"            #12
    ]
    
    # Colors for the two source groups (consistent)
    network_color = "rgba(31,119,180,0.8)"      # blue-ish
    hiring_color  = "rgba(255,127,14,0.8)"      # orange-ish
    
    sources = []
    targets = []
    values  = []
    link_colors = []
    
    def add_link(src_idx, tgt_idx, val, color):
        sources.append(src_idx)
        targets.append(tgt_idx)
        values.append(val)
        link_colors.append(color)
    
    # Direct flows from Network and Hiring.cafe into Abandoned Lead and Applied
    add_link(0, 2, 1, network_color)    # Network -> Abandoned Lead (1)
    add_link(1, 2, 58, hiring_color)    # Hiring.cafe -> Abandoned Lead (58)
    add_link(0, 3, 11, network_color)   # Network -> Applied (11)
    add_link(1, 3, 70, hiring_color)    # Hiring.cafe -> Applied (70)
    
    # Applied -> Rejected, No Response, Screener (split by original group)
    add_link(3, 4, 5, network_color)    # Applied -> Rejected (network 5)
    add_link(3, 4, 40, hiring_color)    # Applied -> Rejected (hiring 40)
    add_link(3, 5, 3, network_color)    # Applied -> No Response (network 3)
    add_link(3, 5, 15, hiring_color)    # Applied -> No Response (hiring 15)
    add_link(3, 6, 4, network_color)    # Applied -> Screener (network 4)
    add_link(3, 6, 15, hiring_color)    # Applied -> Screener (hiring 15)
    
    # Screener -> Rejected by Screen, Full Round
    add_link(6, 7, 1, network_color)    # Screener -> Rejected by Screen (network 1)
    add_link(6, 7, 5, hiring_color)     # Screener -> Rejected by Screen (hiring 5)
    add_link(6, 8, 3, network_color)    # Screener -> Full Round (network 3)
    add_link(6, 8, 10, hiring_color)    # Screener -> Full Round (hiring 10)
    
    # Full Round -> Rejected by Panel, Offer
    add_link(8, 9, 1, network_color)    # Full Round -> Rejected by Panel (network 1)
    add_link(8, 9, 7, hiring_color)     # Full Round -> Rejected by Panel (hiring 7)
    add_link(8, 10, 2, network_color)   # Full Round -> Offer (network 2)
    add_link(8, 10, 3, hiring_color)    # Full Round -> Offer (hiring 3)
    
    # Offer -> Accepted, Declined
    add_link(10, 11, 1, network_color)  # Offer -> Accepted (network 1)
    add_link(10, 12, 1, network_color)  # Offer -> Declined (network 1)
    add_link(10, 12, 3, hiring_color)   # Offer -> Declined (hiring 3)
    
    # Sanity check
    assert len(sources) == len(targets) == len(values) == len(link_colors)
    
    # Node colors (visual guidance)
    node_colors = [
        "rgba(31,119,180,0.9)",   # Network
        "rgba(255,127,14,0.9)",   # Hiring.cafe
        "rgba(220,220,220,0.9)",  # Abandoned Lead
        "rgba(200,200,200,0.9)",  # Applied
        "rgba(220,180,180,0.9)",  # Rejected
        "rgba(200,200,220,0.9)",  # No Response
        "rgba(200,220,200,0.9)",  # Screener
        "rgba(255,200,200,0.9)",  # Rejected by Screen
        "rgba(210,210,255,0.9)",  # Full Round
        "rgba(240,200,220,0.9)",  # Rejected by Panel
        "rgba(200,255,200,0.9)",  # Offer
        "rgba(140,255,140,0.9)",  # Accepted
        "rgba(255,140,140,0.9)"   # Declined
    ]
    
    fig = go.Figure(data=[go.Sankey(
        node=dict(
            pad=18,
            thickness=18,
            line=dict(color="black", width=0.5),
            label=labels,
            color=node_colors
        ),
        link=dict(
            source=sources,
            target=targets,
            value=values,
            color=link_colors,
            hovertemplate='%{source.label} → %{target.label}: %{value}<extra></extra>'
        )
    )])
    
    fig.update_layout(
        title_text="Hiring funnel Sankey — direct source flows (no Leads node)",
        font_size=12,
        height=700,
        margin=dict(l=20, r=20, t=60, b=20)
    )
    
    fig.show()
    
    # To save as interactive HTML:
    # fig.write_html("sankey_hiring_funnel_direct.html", include_plotlyjs='cdn')
    

    Couldn’t be bothered to write this by hand for just an online comment. There’s enough that can be improved with this, but I think it’s ok to show how it can be done quite easily.

    • deegeese@sopuli.xyzOP
      link
      fedilink
      arrow-up
      1
      ·
      8 hours ago

      Thanks for sharing that. Seems like a promising vis technique but would work better with fewer final states than I used for a regular Sankey.

      • squaresinger@lemmy.world
        link
        fedilink
        arrow-up
        1
        ·
        2 hours ago

        I’m sure it’s possible to move the final states to the middle positions like you did. But I didn’t want to invest more time.