this post was submitted on 28 Sep 2025
234 points (98.3% liked)

Programming

22921 readers
325 users here now

Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!

Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.

Hope you enjoy the instance!

Rules

Rules

  • Follow the programming.dev instance rules
  • Keep content related to programming in some way
  • If you're posting long videos try to add in some form of tldr for those who don't want to watch videos

Wormhole

Follow the wormhole through a path of communities !webdev@programming.dev



founded 2 years ago
MODERATORS
 

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.
you are viewing a single comment's thread
view the rest of the comments
[–] Kissaki@programming.dev 4 points 3 days ago (1 children)

I find this kind of graph a bit misleading/ambiguous. I intuitively want to follow horizontal association between in and out. For example, Recruiter at the bottom splits into three at the bottom.

Not sure if there's a better way to do this. Disconnect in-bar and out-bar with a condensed point/circle to indicate non-conformity?

[–] deegeese@sopuli.xyz 2 points 3 days ago (1 children)

If you know something better I’d like to give it a spin.

[–] squaresinger@lemmy.world 2 points 3 days ago (1 children)

The better option is to keep colors from the original input stream for the flows instead of making the flows an uniform color.

In the input on the left you have pink, green and blue.

Keep these colors throughout the graph.

Except of the input, all of the other stages only ever split up and never merge, so keeping this single set of colors is enough.

The other option would be to get rid of the "leads" stage, since it actually doesn't change any state. All the other stages are an action that happens (e.g. "Applied" changes the state of the application from being just a lead to being an open application and it also filters out data for being e.g. abandoned). But the "leads" stage means the same thing as the first stage. So drop the "leads" stage and instead make flows go from all three input stages directly into "bad lead", "abandoned" or "applied".

Combine both to get the best result.

[–] deegeese@sopuli.xyz 2 points 3 days ago (1 children)

Is there any site that does this?

[–] squaresinger@lemmy.world 1 points 2 days ago (1 children)

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.xyz 1 points 2 days ago (1 children)

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 1 points 2 days 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.