Inspiration
A common struggle that UWaterloo students face is the gruelling search for co-op opportunities. As future UWaterloo students, we aimed to fix this issue through our AI job search/resume generator. We intended to remove the grunt work from the equation, so that students could focus their energy on important matters, like real co-op experience. So we built Opportune.
What it does
- Upload your resume once we parse the PDF into a structured profile.
- Get matched to real job and scholarship openings across Canada and the US.
- See your fit, scored out of 100, with an honest "why you fit" and "what's missing."
- One click drafts a tailored résumé and cover letter for that specific posting.
- Edit and download a polished PDF.
How we built it
- Frontend: vanilla React via Babel-standalone Backend: Flask + SQLAlchemy + SQLite. Intelligence: the Claude API Real data: the Adzuna API for live job listings and Claude's web search for scholarships. PDF: Tectonic compiles a LaTeX resume.
Challenges we ran into
Our links lied to us. When we originally implemented the feature to go to the actual job listing, the link would only work about half of the time. It could send us to the site, but the opportunity could be outdated, or not even available. Therefore, we switched from web-search tool to Adzuna Api, using Adzuna searching was faster, links were consistent, and the job pool was bigger
Load times were super long. Originally when we asked it to find jobs/generate resumes, it could take upwards of a minute to do these tasks. To fix this we implemented thread pools, where we had idle threads waiting for tasks, rather than terminating and creating them every time.
Accomplishments that we're proud of
As mentioned above, one of the biggest challenges we faced was the loading times and hallucinations when calling ClaudeAPI for live job searching. We tested it countless times; consulting mentors, looking deep into documentation, and trying different Claude models, but we ultimately landed on using AdzunaAPI to scrape the web. This allowed us to not only speed up the search process while also sanity-checking that the web postings are still valid. We also cut down time by parallelizing API calls with thread pools and assigning the right models for each job; Sonnet for drafting and Haiku for scoring. In doing so, we turned a roughly 40-second wait into a fast and responsive product.
What we learned
- Use the right tool for a specific job. We use Claude-Sonnet-4-6 for the high-stakes generative work, but Claude-Haiku-4-5 for scoring, where speed matters more than depth.
- Never fully trust an LLM. Our model would hand back URLs that look perfect. However, that job doesn't exist, or it's not outdated.
What's next for Opportune
Broader, global coverage (e.g. Google-for-Jobs sources), application deadline tracking, and learning from which drafts a user actually keeps so that Opportune gets sharper the more you use it.
Log in or sign up for Devpost to join the conversation.