DEV Community

Sanskar Kharya
Sanskar Kharya

Posted on

I’m building an opportunity finder that hides most opportunities

Most discovery platforms index everything and bury you in filters. Fuko inverts that — it shows an opportunity only when it matches your real profile. Here's the design and the AWS serverless pipeline behind it.

Every "find opportunities" platform I've used works the same way: index everything, then hand you filters and a search bar and wish you luck. You end up scrolling a thousand hackathons and issues that have nothing to do with you.

I'm building Fuko to do the opposite. It's still in progress, but the core idea is already working and it's the part worth writing about: an opportunity is shown only when it matches your profile. No endless list. A small ranked set, and every result annotated with why it matched.

This is a build-in-public post — I'll show what's live, the design decisions behind the matching engine, and the AWS serverless pipeline that keeps it fed. It's open source (repo here), and I'll be honest about what's still on the roadmap at the end.

The one principle everything hangs on

Opportunity type is a preference, not a relevance signal.

When you tell Fuko you're interested in hackathons, that records intent — it does not make every hackathon relevant to you. Type can raise a result's ranking, but it can never qualify a result on its own. To show up at all, an opportunity needs at least one concrete profile signal: a matched skill, interest, or location.

Anything that matches on type alone gets dropped. That single rule is what keeps the feed small and honest.

Two streams, classified by how you engage

Rather than sorting by source, Fuko sorts opportunities by engagement model:

  • Contribute — remote, skill-based work: GitHub issues, bounties, contribution campaigns. Ranked by skill and tech overlap. Location is ignored entirely.
  • Near you — in-person events: hackathons, meetups, college competitions. Ranked by location first, then interest.

The same profile drives both, but each stream weights signals differently, because "what can I contribute to from my laptop" and "what's happening near me this weekend" are genuinely different questions.

Making matches structural, not keyword soup

The easy version of this is substring matching on titles. It's also wrong — cli matches client, AWS gets mangled, and you get garbage reasons. Fuko avoids that in two ways:

Token-accurate comparison. Matching runs on whole tokens with safe normalization, so acronyms like CSS, AWS, and iOS survive intact and cli never matches client.

Metadata enrichment. GitHub opportunities are enriched with each repo's real languages and topics — pulled from the API, not inferred from the description. So a reason like Matched on TypeScript · Python reflects the actual stack of the repo, not a keyword that happened to appear in the title.

// The relevance gate, in spirit:
const signals = [matchedSkills, matchedInterests, matchedLocation]
  .flat()
  .filter(Boolean);

if (signals.length === 0) return null; // type-only? dropped.

return { opportunity, score: rank(signals, opportunity), reasons: signals };
Enter fullscreen mode Exit fullscreen mode

There's also a deterministic reveal: results load five at a time with true counts. If three qualify, you see three. If nothing qualifies, you get an explicit empty state instead of a padded list. An empty feed is a feature — it means Fuko isn't lying to you.

The AWS pipeline keeping it fed

The frontend is Next.js (App Router) + TypeScript + Tailwind + Framer Motion. The interesting infrastructure is behind it, and it's fully serverless:

  • DynamoDB stores the opportunities table (ap-south-1).
  • Lambda + SAM run the automated opportunity sync from sources (GitHub issues/campaigns and Brabble.ai for events).
  • Amplify hosts the app.

Enrichment is a separate, resumable step — it writes only on a successful fetch and skips records that already have data, so re-running it is safe and cheap:

npx tsx --env-file=.env.local scripts/enrich-github.ts
Enter fullscreen mode Exit fullscreen mode

One decision I'm happy with: a USE_MOCK_DB=true flag that swaps DynamoDB for an in-memory store. Local development doesn't touch AWS at all, which keeps the inner loop fast and my bill at zero while iterating on matching logic.

The little touches

Three themes — Light, Dark, and Funky (the last one adds color and motion). Spring-based interactions, including a "considering" transition that actually renders the ranking pass before results resolve, so you see it thinking. And all motion respects prefers-reduced-motion, because a discovery tool shouldn't make anyone motion-sick.

Where it honestly stands

What's working today: onboarding, the two-stream feed, the matching engine and relevance gate, themes, and opportunity detail views. The AWS sync pipeline is deployed.

Still in progress:

  • Continuous live sync — the Lambda + EventBridge schedule is built; I'm wiring enrichment into every scheduled run so the feed stays current automatically.
  • Editable profiles that re-resolve the feed live.
  • A deeper skill/topic taxonomy for more precise matching.

On the roadmap: Google sign-in with a full guest mode, encrypted portable profiles, saved opportunities with deadline reminders, and more sources (GitLab, Devpost, Unstop).

It's open source — contributions welcome

Fuko is MIT licensed and built in the open. If any of this resonates, I'd genuinely appreciate the help — whether that's a PR, an issue, or just telling me the relevance gate is too strict:

Good first contributions right now: expanding the skill/topic taxonomy, adding a new opportunity source adapter (GitLab, Devpost, Unstop), or refining the matching normalization. Star it if you want to follow along — more updates as the live sync lands.

Top comments (0)