Parsa Falahati Portfolio / Things I Made / Opportunita / Case Study
Back
Find Understand Prepare Manage
Opportunita mark Opportunità
Academic opportunity platform · 2025–2026

Make your next academic move.

Opportunita is a platform for people applying to PhD, PostDoc and research positions. It collects opportunities from official university sources, turns each one into structured, comparable information, and carries the applicant from finding a position all the way through preparing and tracking the application.

I designed and built it: the product, the architecture, the interface, and the system that runs it in production.

Founder · Product Architect · UX & Product Design · Full-stack implementation · Production operations {{ readingLabel }}
Opportunita public landing

The first screen states provenance before it states value. Search is written as language, not as a filter form.

At a glance

What this actually is.

What

An academic opportunity platform: discovery, offer intelligence, personalized recommendations, an academic resume studio, and application management, connected as one path rather than five tools.

For whom

PhD, PostDoc and research applicants. People moving between countries and university systems they have never dealt with before.

The problem

Academic opportunities live on hundreds of different university career pages, in different structures, with the important details buried. And once you find one, the work of applying starts somewhere else entirely.

My role

I defined the problem, designed the product and its architecture, directed the interface and the brand, specified and drove the implementation, and run it in production.

PHP · WordPress as runtime · MySQL · React · REST · custom relational schema · scheduled background processing · provider-agnostic AI layer · server-side PDF generation
The problem

The information exists. It just doesn't exist in one shape.

A PhD position is published on the university's own career page. So is the next one, on a different university's page, in a different structure, in a different language, with the salary written as a national pay-scale code instead of a number.

Scattered.

Every university publishes on its own site, in its own system.

Incomparable.

Salary, contract length, workload and deadline are written differently everywhere, and often not written at all.

Unstructured.

Requirements sit inside a wall of prose. Nothing is filterable.

Disconnected.

After discovery, the actual work happens in a spreadsheet, a folder, and a chat window.

The scarce resource here is not opportunities. It is comparability, and the continuity between finding something and acting on it.

The decision

Turn official postings into structured data, and never pretend to be the source.

The founding decision was not "build a better job board". It was: take what universities already publish, convert it into a consistent structure, explain it in plain language, and keep the official page as the authority the whole way through.

That single constraint shaped everything downstream. If the official source always wins, then the system's job is collection, normalization, classification and explanation, never substitution. It is why every opportunity carries its source domain, why the apply button leaves for the university's own site, and why the interface says "not provided by source" instead of guessing.

PRINCIPLE, ENFORCED IN ARCHITECTURE · NOT A FEATURE
The spine

Find Understand Prepare Apply.

Every part of the platform sits on one of these four steps. Nothing was built that could not be placed on this line.

01
Find

Verified opportunities from official university pages, filterable by the things that actually decide a choice: country, discipline, language, salary, deadline, position type.

02
Understand

Each opportunity as structured fields plus a plain-language summary, salary scheme decoding, ranking context and institutional signals.

03
Prepare

An academic resume built as structured evidence, checked, tailored per application, and exported as a real document.

04
Apply & manage

A personal workspace where each application moves stage by stage, with notes, tasks and deadlines, and applies on the university's own site.

Find

A directory is only useful if you can narrow it honestly.

Browse is built around the filters that actually decide an academic application: position type, country, language, university, discipline, salary range and deadline window, not around keyword search alone.

Live rebuild · try the filters
Quick filters
{{ countLabel }}
173 ACTIVE91 PHD67 POSTDOC21 UNIVERSITIES2 COUNTRIES
{{ o.type }} Official
{{ o.title }}
{{ o.org }}
{{ o.place }}
{{ o.salaryLine }} {{ o.pay }}
{{ o.daysLabel }}

A working rebuild of the browse surface. Density is a user choice, not a designer's, and where a university published a pay-scale reference instead of a number the card says so rather than inventing a range.

Browse opportunities, dark appearance

The shipped screen. The counters above the filters are the honest ones, so the size of the index is never implied, it is stated.

Every card shows its source status. The full filter rail carries keyword, position type, country, language, university, disciplines, salary range and deadline window, and the density switcher changes how much comparison fits on screen without changing the query.

User view

I can narrow hundreds of postings down to the handful that are genuinely possible for me.

Builder view

Faceted querying over a normalized relational schema with 73 columns on the opportunity table alone, indexed for the facets that matter, with the presentation density decoupled from the query.

Information architecture

An opportunity is not a paragraph. It is a record.

The difference between a job board and this platform is what happens to a posting after it is collected. Every opportunity is classified into a fixed discipline taxonomy and indexed against extracted research keywords.

The discipline directory shows every category, including the ones with zero open positions. That is deliberate: a taxonomy that hides its empty branches is a taxonomy you cannot trust. Keywords and subjects then let a researcher move sideways through the index, from one position to the adjacent field, rather than starting a new search.

Discipline directory

Eight fixed discipline categories, each with a live count. Zero-count categories stay visible so the shape of the taxonomy stays honest.

Explore similar paths

Related paths generated from the record itself: country, institution, discipline, position type, and extracted research keywords.

User view

I can move from one position to the next relevant one without starting over.

Builder view

A fixed discipline taxonomy with per-offer classification records (primary flag, source, confidence and evidence retained), plus a separate keyword index, so classification stays auditable and correctable rather than being a guess baked into a text field.

Understand

Everything a decision needs, before the decision.

The offer page answers the questions an applicant actually asks first: salary, deadline, contract length, employment type, department, as fields, at the top, before a single paragraph of the university's prose.

See the same posting
university career page · plain text

The Institute invites applications for a part-time research associate position with the opportunity to pursue a doctoral degree. The position is initially limited and remuneration is paid according to the applicable public-sector collective agreement, subject to the fulfilment of the personal requirements. Applications should be submitted through the online portal, quoting the reference number, and should include a letter of motivation, a curriculum vitae, copies of certificates and the names of two academic referees. The closing date for applications can be found in the header of this announcement. Working hours amount to a percentage of the regular weekly working time and may be adjusted by agreement. The Institute is committed to increasing the proportion of women in research and therefore expressly encourages qualified women to apply. Applicants with disabilities will be given preference in the case of equal suitability. Further information about the research group, the doctoral programme, the supervision arrangements and the location can be obtained from the contact person named below.

Salary, contract length, workload, deadline and employment type are all in there. None of them is a field, so none of them can be filtered, sorted or compared with the next posting.

PhD Active German · English Verified official source
Part-time research associate with the opportunity to pursue a doctoral degree
Leibniz Association · Germany · Dortmund
Salary
Pay scale E13
decoded from source
Deadline
22 days left
normalized date
Contract
36 months
fixed term
Workload
65 percent
part time
Department
Working environment
Posted
14 days ago
Referees
2 required
Start date
Not provided by source
source · leibniz-institut.de · verified 14 days ago
Apply on the university's own site →

Switch between the two views. This is the whole product in one gesture: the prose is still there, it is just no longer the only way to read the position. Where the source published nothing, the field says so instead of guessing.

A summary that labels itself at the point of use.

Underneath sits a plain-language summary of the position, generated from the stored posting and labelled as such, with a permanent line reminding the reader to confirm details in the official posting. The apply panel names the source domain and states the verification status. The primary action leaves for the university's own site.

Plain-language offer summary and apply panel

Trust is a UI decision, not a policy page.

Ranking references and institutional signals

Ranking references and sponsor signals carry their own disclaimer. A signal that cannot be over-read is worth more than a signal that impresses.

Context that is expensive to gather alone.

University ranking references, and institutional signals such as a recognised-sponsor register entry. Each one is framed as a source-backed signal, explicitly not a guarantee of admission or a visa.

RUNNING IN PRODUCTION
Provider-agnostic, validated before display, and activated deliberately rather than shipped on.
User view

I know what this position pays, how long it runs, what it asks, and where it came from.

Builder view

An enrichment layer of its own, with seventeen owned tables, that never mutates the canonical record. It annotates it. Provider-agnostic, model-agnostic, and validated before display. It ships inert and requires an explicit operator decision to activate, so no deployment can silently start generating text on its own.

The pipeline

One clean page. Six steps behind it.

Getting an opportunity onto that page is the least visible and most demanding part of the system. Each source publishes differently, so each source gets a purpose-built adapter.

An adapter finds the posting on the official career page, reads it, and maps it into the platform's internal structure. From there it is shared machinery: normalization of dates, salaries, contract terms and languages; a validation matrix that scores the result; de-duplication against source URL, external identifier and content hash; classification; then enrichment. Every run is logged and every version snapshotted, so a wrong field can be traced back to the run that produced it.

The adapters do not decide anything important. Whether an opportunity becomes public, and what happens when it disappears from the source, is decided centrally, so eighteen different adapters cannot drift into eighteen different behaviours.

Currently 18 official source adapters, each written against a different university CMS or applicant-tracking system.
Official university source
the posting as the university published it
Collection adapter
one per source system, extract and map only
Normalization
dates, salaries, contract terms, languages
Validation & de-duplication
scored by matrix, matched on url, id, hash
Classification
discipline taxonomy and research keywords
Enrichment & public page
summary, signals, then the page a reader sees
User view

The positions are real, current, and linked to where they came from.

Builder view

A data ingestion pipeline, not a scraper: extraction, normalization, a four-tier validation matrix producing an explicit quality score, a six-step de-duplication precedence, lifecycle management, and full run logging with snapshots for traceability.

Personalization

Say what you want in a sentence, and see what it costs you.

Preferences are the hardest screen in the product, because every option a user sets silently changes what they will never see again. So the screen was rebuilt to say that out loud.

Your search
click any chip to change its rule

Write it as a sentence. Each chip carries its own rule, so there is no separate list of required and excluded values to keep in sync.

I want this among my options Only this Never this
{{ g.group }}
{{ survivors }}
match now
Narrowed from 173 open opportunities by {{ hardRuleLabel }}.
Then ranked
{{ recs }}
personalized recommendations from {{ survivors }} opportunities passing your rules.
no percentage · no score · ordinal bands only
Why you are seeing these {{ recs }}

{{ r.text }}

Only-this and never-this rules decide which opportunities appear at all. Preferences only change their order. A settings screen that overstates its own power is a settings screen that quietly loses people opportunities.
Personalized feed with the explanation panel open

The shipped surface. The funnel is shown, not hidden, and where the source published no salary the card says not provided by source.

Recommendation feed with ordinal match bands

Bands, not numbers. Top match and strong match are orderings a person can argue with.

There is no percentage anywhere in this surface, and that is a permanent product rule rather than a gap. A number attached to a person's chances is a claim the system cannot honestly make, so it does not make it. Reasons can be argued with; a score cannot.

RUNNING IN PRODUCTION
Consent-gated, and released only after passing its own evaluation gate.
User view

I can see the cost of every rule I set, while I set it, and why each recommendation is here.

Builder view

Per-facet effect truthfulness enforced in the UI layer, and a deterministic matching engine behind it: profile projections, a scoring cascade with candidate generation instead of all-pairs comparison, versioned algorithms, and an evaluation framework with a formal release gate the engine had to pass before it was allowed to serve members. Scores exist internally and are permanently denylisted from reaching a member.

Prepare

From one CV to an application-ready academic profile.

An academic CV is not a one-page résumé. It is a structured record of evidence (publications, grants, teaching, conferences, supervision, methods) and it changes shape for every application.

Beat 01 · Build
0 academic item types

Built as data, not as a document, arranged into sections you can order and title yourself, with up to 20 tailored variants per person.

Beat 02 · Check
0 deterministic rules

No AI, no credits. It reports what a machine reader will and will not find, and each finding carries why it matters and how to fix it.

Beat 03 · Export
1:1 preview to output

Real page breaks, real page count, before generating. The document can also be produced in another language from the same evidence.

Resume export with true paginated preview

The preview renders the true PDF pagination, not an approximation. Getting that right is the difference between a preview and a promise.

Resume style studio

Style studio: accent theme, typography scale and spacing density. The resume inherits the platform's design language rather than a template marketplace's.

Resume structural check

A warning that says "an email address is saved but hidden from this export" is worth more than a score. The score is there too. It just is not the point.

Export language selector

The export can also be produced in another language, so the same structured evidence can address a German or Italian institution without maintaining a second CV.

User view

One profile, many applications, and I know what a reviewer will actually see.

Builder view

The resume is the system's structured user evidence, which is what makes matching possible at all. Server-side PDF generation and uploaded-document text extraction are vendored so production never runs a package manager, and both degrade honestly to "unavailable" rather than failing mid-request.

Apply & manage

Finding an opportunity is only the beginning.

Most tools in this space stop at discovery. The application itself, with its deadlines, versions, follow-ups and outcomes, is where the actual work is, and where applicants lose track.

Live rebuild · click a card to move it forward · scroll the board sideways
Server model {{ st.name }}
{{ c.name }} {{ c.count }}
{{ c.meaning }}
{{ k.type }}
{{ k.title }}
{{ k.org }}
{{ k.meta }}

Six columns over a nine-stage model. Accepted, rejected and offer are outcomes, not lanes of work, so they collapse into one column: a board is for work in progress, not for results. Expired and withdrawn postings stay visible and labelled rather than disappearing and taking your history with them.

Application board

The shipped board, with its honest lifecycle states.

Workspace home

"Do this next" is the primary object on the home screen. A workspace that opens on statistics makes you decide what to do before it helps you do it.

Your opportunities

Everything saved, viewed or started, in one place, sorted by urgency rather than by date added.

User view

I can see every application I have running, and what each one is waiting on.

Builder view

A nine-stage server pipeline with automatic timestamping at the meaningful transitions, presented through a deliberately different six-column view model. The private workspace is a separate system with its own tables, and the dependency runs one way, so the public product keeps working if the workspace is disabled.

Design system

One system, three appearances.

Dark, dim and light are not three themes bolted on at the end. They are one token system resolved three ways, and the product ships all three because people read job postings at midnight and at noon.

PhD

PhD candidate in mechanism-informed world models and agents

University of Amsterdam · Netherlands

€3,059 – €3,881 gross monthly

Official source · 34 days left

Nothing moves. Only the canvas and surface tiers resolve differently, while the accent and the semantic colours stay constant.

The layout, density, hierarchy and component behaviour are identical across all three. The teal accent and the semantic colours (verified, closing soon, expired) stay constant, so a user who learns the colour language in one appearance keeps it in the others.

A 35-icon hand-authored SVG set on a 24×24 grid, no external icon library anywhere in the product, and reduced-motion preferences honoured across every animated surface.
Dark appearance
Dim appearance
Light appearance

The same shipped screen in all three appearances.

Architecture

Seven systems, one direction of dependency.

The platform is not one application. It is seven purpose-built systems with explicit ownership boundaries, and the reason is simple: public opportunity data and private user data should never live in the same place.

direction of data
{{ l.name }}
{{ l.owns }}
{{ l.note }}

{{ layerNote }}

Ownership is strict. The public catalog owns opportunities, institutions, countries, the taxonomy and the ingestion pipeline. The private workspace owns everything belonging to a person. The matching engine owns only derived intelligence: it never owns an input. Cross-system reads happen through published contracts with explicit allowlists, not by reaching into another system's tables, and every contract client fails closed.

The result is that each subsystem can be disabled without taking the platform down. The public product runs without the workspace. The intelligence layer degrades to an empty read when the catalog is absent. The support layer runs with all three siblings inactive. That was designed in, not discovered later.

Internally: Core · Workspace · Matrix · Explicify · Mail · Support · Marketing.
Technology

What it runs on, and what was built rather than installed.

PHP MySQL Custom relational schema WordPress as runtime React REST contract Scheduled background processing Provider-agnostic AI layer Server-side PDF generation Document text extraction

None of the product is assembled from page builders or off-the-shelf job-board plugins. The data model, the ingestion pipeline, the routing, the SEO and structured data, the matching engine, the resume system, the mail gateway and the operations tooling are purpose-built. WordPress supplies the runtime; everything that makes this a product sits above it.

Principles

The rules the product is not allowed to break.

01Official sources first.The platform collects, normalizes, explains and organizes. It never substitutes, and applying always leaves for the official site.
02Evidence before assumption.AI work stops rather than deliver a low-quality result, and a failed action never costs the user a credit.
03Unknown is not missing.Where a source published nothing, the interface says so. Guessing to fill a layout is how a data product loses trust.
04No unsupported certainty.No probability of acceptance, no grade attached to a person. Recommendations explain themselves in words that can be argued with.
05Control stays with the user.Personalization and analysis activate only on explicit consent. Full export and deletion are first-class.
01
Official sources first.

The university's own page is the authority. The platform collects, normalizes, explains and organizes; it never substitutes. Applying always leaves for the official site.

02
Evidence before assumption.

Nothing is presented as settled without a source. AI work stops rather than deliver a low-quality result, and a failed action never costs the user a credit.

03
Unknown is not missing.

Where a source published nothing, the interface says not provided by source. Guessing to fill a layout is how a data product loses trust.

04
No unsupported certainty.

No probability of acceptance. No grade attached to a person. Recommendations explain themselves in words, and the words can be argued with.

05
Control stays with the user.

Personalization and analysis activate only on explicit consent. Full export and deletion are first-class. Any message category a user cannot see and switch off is never sent.

Each principle is tied to a shipped behaviour. That is what separates a product rule from a brand statement.

What was actually hard

Five decisions worth defending.

01
Every source is a different system.

A thin per-source adapter that only extracts and maps, with normalization, validation, de-duplication, lifecycle and visibility centralised. Adding a source became a bounded, repeatable job, and no adapter can invent its own behaviour.

02
AI is useful and cannot be trusted with truth.

AI never writes to the canonical record. It annotates, validated before display and labelled at the point of use. The factual layer stays auditable back to a source; the explanatory layer can be switched off without touching it.

03
Public and private data want different homes.

Separate systems, separate tables, one-way dependency, cross-system reads only through allowlisted contracts that fail closed. Personal data never enters the surface that serves anonymous traffic.

04
A matching engine that cannot explain itself should not ship.

Build the engine, the surface and the evaluation gate, then hold the algorithm behind that gate until it passes, and permanently forbid scores from reaching a member. Ordinal bands and plain-language reasons instead of a fabricated percentage.

05
A settings screen can silently cost someone an opportunity.

Express search as a sentence of chips with per-chip rules, show a live count of what survives, and state plainly which rules exclude versus which only reorder. Users can see the cost of a rule while setting it, and the interface never claims an effect the engine does not have.

01
Every source is a different system.
Problem

Eighteen institutions, eighteen content systems, no shared structure, and no API.

Decision

A thin per-source adapter that only extracts and maps, with all shared logic (normalization, validation, de-duplication, lifecycle, visibility) centralised.

Result

Adding a source is a bounded, repeatable job, and no adapter can invent its own behaviour.

02
AI is useful and cannot be trusted with truth.
Problem

Language models are excellent at explanation and unreliable as a source of record.

Decision

AI never writes to the canonical record. It annotates. Output is validated before display, labelled at the point of use, provider- and model-agnostic, and activated by an explicit operator decision rather than switched on by default.

Result

The platform's factual layer is auditable back to a source, and the explanatory layer can be improved or switched off without touching it.

03
Public and private data want different homes.
Problem

Anonymous discovery traffic and private resumes have opposite requirements for caching, access and retention.

Decision

Separate systems, separate tables, one-way dependency, cross-system reads only through allowlisted contracts that fail closed.

Result

The public product survives the workspace being disabled, and personal data never enters the surface that serves anonymous traffic.

04
A matching engine that cannot explain itself should not ship.
Problem

Ranking people against opportunities produces numbers that look authoritative and are not.

Decision

Build the engine, the surface and the evaluation gate, then hold the algorithm behind that gate until it can pass it, and permanently forbid scores from reaching a member.

Result

A personalization layer that is complete in code and honest in production, shipping ordinal bands and plain-language reasons rather than a fabricated percentage.

05
A settings screen can silently cost someone an opportunity.
Problem

Preference controls imply more power than the engine actually has, and a user cannot tell which choice removed results.

Decision

Express search as a sentence of chips with per-chip rules, show a live count of what survives, and state plainly which rules exclude versus which only reorder.

Result

Users can see the cost of a rule while setting it, and the interface never claims an effect the engine does not have.

The platform at a glance

One product, many surfaces.

Public discovery, offer intelligence, personalized recommendations, the resume studio, the application board, notifications, operations and SEO tooling, designed as one system with one visual language. Grouped here by the appearance each surface ships in, because that grouping is itself the architecture: the public platform runs dark, the private workspace runs dim, and every component resolves in light too.

Dark public discovery and offer pages · canvas #020712 5 surfaces
Browse
Offer page
Offer summary
Offer intelligence
Keywords
Dim the landing surface, the directories and the private workspace · canvas #0B1322 13 surfaces
Landing
Personalized feed
Disciplines
Preferences
For you
Workspace home
Your opportunities
Application board
Resume studio
Resume check
Resume export
Export language
Browse, dim appearance
Light same components, accent deepened for contrast on white 1 surface
Browse, light appearance

Every surface above resolves in all three appearances. Only one is shown here, because showing the same screen three times proves the token system once and then becomes a gallery.

Current state

Where it stands today.

0
purpose-built systems
0
official source adapters
0
REST endpoints
0
academic resume item types
0
stage application pipeline
0
interface appearances

Opportunita runs in production. Public discovery, the offer pages and directories, the offer intelligence layer, personalized recommendations, the private workspace, the resume studio and the application board are all live and serving real users. Billing runs live too: checkout, webhook fulfilment, the customer portal and reconciliation, with real payments processed.

Every one of those subsystems ships inert and requires a deliberate activation decision, and the matching engine had to pass its own evaluation gate before it was allowed to serve a member. The project keeps a written service catalogue separating what is shipped from what is partial from what is planned, so "live" is a state the system can prove rather than a claim.

LIVE ACTIVATED DELIBERATELY, NEVER BY DEFAULT TRACKED IN A WRITTEN CATALOGUE
How it was built

One person, an explicit method, and AI-assisted implementation.

This was not built by a team, and it was not generated by a prompt. It was built the way a small engineering organisation works, with one person holding every role and a deliberate method for keeping quality under that constraint.

I defined the problem and the product, designed the architecture and the ownership boundaries, made the data-model, privacy, AI-behaviour and SEO decisions, directed the interface and the brand, wrote the specifications and acceptance criteria, reviewed and corrected the implementation, ran the QA, and manage releases and deployment.

A large share of the implementation was produced through AI-assisted development, treated as an engineering method rather than a shortcut. The interesting work was not typing code. It was deciding what should exist, what must never happen, and how the parts are allowed to depend on each other.

What I did

I defined the problem and the product. I designed the architecture and the ownership boundaries between systems. I made the data-model decisions, the privacy and security boundaries, the AI behaviour rules, and the SEO architecture. I directed the interface, the information architecture and the brand. I wrote the specifications and the acceptance criteria, reviewed and corrected the implementation, ran the QA, debugged production incidents, and manage releases and deployment.

How AI fits

A large share of the implementation was produced through AI-assisted development. I treat that as an engineering method, not a shortcut: I write the requirements and the contracts, define the constraints and the acceptance criteria, review the output, reject what does not meet the contract, and own every architectural and product decision. The interesting work in this project was not typing code. It was deciding what should exist, what must never happen, and how the parts are allowed to depend on each other.

The method

Explicit contracts between subsystems. A written service catalogue that separates shipped from partial from planned. Release gates a subsystem must pass before it goes live. Failure policies decided in advance: fail closed, degrade honestly, never charge for a failed action. Those are the practices that let one person hold a system this size without it quietly becoming untrue.

What this demonstrates

From an idea to an operating platform.

Product architecture

Decomposing one problem into subsystems with explicit ownership, dependency direction and failure behaviour.

Full-stack product development

A relational data model, a REST contract, a React application, a background processing pipeline and a document generation system, working as one product.

Complex UX systems

Dense, data-heavy interfaces that stay legible: faceted discovery, a preference language, an application pipeline, and a document builder.

AI & data workflow design

Ingestion, normalization, validation and enrichment, with AI constrained by evidence, validation, consent and fail-safe behaviour.

Technical decision-making

Choosing what not to build, what to gate, and which honesty constraints are worth the features they cost.

Production operations

Versioning, release gates, migrations, deployment, incident debugging and live QA on a running platform.

The thing I would want someone to take from this is not the feature count. It is that a real, messy, fragmented problem was turned into a system with defined boundaries, honest behaviour under uncertainty, and a path a user can actually walk from finding an opportunity to submitting an application.

Opportunità

From opportunity to application.

Opportunita, an academic opportunity platform for PhD, PostDoc and research applicants. Designed, architected and built by Parsa.

Product architecture · UX & product design · Full-stack implementation · AI workflow design · Production operations
end of the case study
NOW

You arrived here from a black screen with a name on it. Now you know what one of those projects actually is. Keep scrolling to let it go.

{{ tensionHint }}