Skip to content
Piyak Labs
Go back

Building K-Saju 3: Casting friends as a K-Pop group

Series Building K-Saju (3/3)

What I built

I added a group feature to K-Saju. The flow is simple.

  1. Someone creates a crew and drops the link into a group chat.
  2. Each person opens the link and enters only their birth date (and optionally a nickname).
  3. Once everyone has joined, they get cast into a K-Pop group based on saju.

Each member receives a role—leader, visual, main vocalist, rapper, maknae, and more, across eight possible positions. The result also includes a pairwise chemistry-score matrix for the whole group, the highest-chemistry duo, and a tension line: the lowest-scoring pair, presumably the two who have been debating the dinner menu for 40 minutes. The group itself receives a one-line concept such as “the nation’s complete group.”

You can try it here: K-Saju Crew

K-Saju crew result page with a group concept card, member position cards, and a chemistry-score matrix Example K-Saju crew OG poster with chick characters and K-Pop position badges

The problem: this service stores nothing

K-Saju started with one architectural principle: do not permanently store personal information; keep the product stateless. There is no login and no database. Every result page is rendered on the server from GET parameters. /card?date=1995-08-28, /match?a=...&b=...—the URL is the entire state.

But the ideal UX for a group feature is “share a link → everyone enters their own birthday → participants accumulate.” Someone has to collect those inputs, and that is state. The product principle and the desired UX collided head-on.

I considered three options.

The solution: the lobby lasts 30 days, the result lasts forever

I chose a hybrid of B and A. The tension disappears when the state is split into “while people are gathering” and “after everyone has joined.”

The creator’s deletion permission is handled with an admin key in the URL fragment (/crew/abc123#k=...). The fragment is never sent to the server, so the admin key does not appear in server logs. It is a small detail, but one I like.

There are also two layers of abuse protection: serialized join requests to prevent concurrent-join races, and daily creation/join limits per IP address.

The casting engine: deterministic, not random

If positions were assigned randomly, the fun would not last a day. The same members need to get the same result when they run it again—“Wait, is this real?” is half the viral loop—and there needs to be a reason for every assignment.

// engine/src/groupChemistry.ts — signature only
export function computeGroupChemistry(members: CardPayload[]): GroupChemistryResult;
// 3–7 people. Pairwise matrix + eight-position casting + group concept.
// Pure function, deterministic.

I limited the group size to three through seven. It feels like the right size for an idol group, and the number of pairs grows as C(n,2), which quickly hurts both matrix readability and OG-rendering cost.

What I learned


K-Saju is a K-culture entertainment experience made for fun. Please enjoy saju readings as content, not as a prediction. 🐣



Next Post
Building K-Saju 2: the hard part after launch was promotion

Comments

Loading comments...

Leave a comment

Your comment will be published after admin approval.