INTERACTIVE DEMO — SYNTHETIC DATA
CV Screening Pipeline
An LLM reads every CV against the job description it was submitted under. This page runs the parts that are code — the redaction, the score boundary, the hash that pins a job description — and refuses to fake the part that is a model.
Every record on this page is fabricated. No production system, customer, employee or credential is involved.
The production system behind this demo was engineered by Prada Dipa — LinkedIn profile, opens in a new tab and Luthfi Aditya — LinkedIn profile, opens in a new tab. I managed and directed it — requirements, technical review, QA and rollout.
THE PIPELINE — SIX STAGES, TWO OF THEM RUNNING HERE
An application appears in the HR platform. Six stages later a recruiter has a ranked shortlist with the reasoning attached. Choose a stage to see what it does and what, if anything, of it executes on this page.
03 · REDACT
Two passes over the text, immediately before it leaves the machine.
The rubric grades experience against a job description and needs none of a candidate's identifiers. The prompt already instructs the model to ignore them, but being told to ignore a signal is weaker than never receiving one — and a name carries ethnic and gender signal by itself. Pass one uses what the recruiter recorded plus the parsed name; pass two uses what the extractor parsed. Neither source is complete on its own, and the incident that produced the second pass is exactly that: relying on the profile alone let a phone number the model had missed cross to a US API in full, with nothing in the record to show that it had.
ON THIS PAGE — All of it. src/cv/redact.ts has zero imports, so the file that runs in the service is the file running in this tab.
REDACTION — THIS IS THE PRODUCTION FILE, RUNNING
The rubric grades experience against a job description and needs none of a candidate's identifiers. The prompt already instructs the model to ignore them, but being told to ignore a signal is weaker than never receiving one — and a name carries ethnic and gender signal by itself.
- 1,170 bytes
- 1,170 bytes
- 0 bytes
RANI KUSUMAWARDHANI
Inventory & merchandising data — Bandung, West Java
rani.kusuma@example.invalid · +62 811 5550 132
linkedin.com/in/ranikusumawardhani
PROFILE
Four years working with stock and sales data for apparel manufacturers.
Comfortable owning a weekly reporting cycle end to end, from the query to the
one page the planning team actually reads.
EXPERIENCE
Inventory Analyst — PT Bina Rajut Sejahtera (Mar 2022 – present)
- Rebuilt the weekly stock-cover report in SQL, replacing a spreadsheet that
three people maintained by hand.
- Wrote the reorder rule the planning team still uses, and the one-page note
that goes out with it every Monday.
- Cut the reporting cycle from two days to under three hours.
Junior Merchandiser — CV Anggrek Tekstil (Jan 2021 – Jun 2021)
- Maintained the size-curve workbook for two seasons.
- Chased suppliers on late deliveries and kept the arrival board current.
EDUCATION
S1 Statistics, Universitas Kanaya, 2020
SKILLS
SQL (PostgreSQL), spreadsheets, Python (pandas), a little dbt
REFERENCES
Available on request. Reachable on 0811-5550-132 during office hours, or on
WhatsApp 0811 5550 187 outside them.IDENTIFIERS STILL PRESENT IN THE TEXT ABOVE
- Rani
- Kusumawardhani
- ranikusumawardhani
- rani.kusuma@example.invalid
- +62 811 5550 132
- 0811-5550-132
- 0811 5550 187
Nothing has run yet — this is the text as the extractor read it. Choose a pass.
THE BOUNDARY — VERBATIM, AND YOU CHOOSE WHAT CROSSES IT
The model's response is schema-validated before this point, so on the live path a malformed score has already been rejected. This check exists anyway, because the loop is otherwise pure trust in whatever client was handed in — and because the failure it guards against is silent rather than loud.
ACCEPTED — now, and only now, compared against the floor: SCORED.
An integer inside the band. It crosses, and only now is it compared against the floor.
WHAT MUST NOT MOVE THE SCORE
Four things that routinely move a human reader. Switch one on and the document above changes; the rule it violates lights below; the number does not move. Read the note at the bottom of this panel before you conclude anything from that.
78
The document is unmodified.
It is the same number because this panel is a statement of the rubric, not a measurement of a model. Whether gpt-4o-mini obeys the ignore list under an adversarial CV is a bias audit, and a bias audit is not a thing a portfolio page can perform.
THE BREAKDOWN THE SHAPE REQUIRES
The prompt works criterion by criterion through the pinned job description, quotes the evidence, and states absence as absence. This is that shape, filled in by hand for Inventory Data Analyst — PT SANDANG NUSA.
METAt least 3 years working with commercial or inventory data
Four dated years in inventory data, continuous and current.
METSQL you can write unaided, including joins and window functions
Ownership of the SQL is evidenced and dated. Window functions are never named; the rebuild described is hard to do without them, but that is inference and this is not scored on inference.
METSpreadsheet fluency, and the judgement to know when a spreadsheet is the wrong tool
The requirement asks for the judgement rather than the tool, and the CV shows the judgement being exercised.
METAble to explain a number to someone who does not work with data
Indirect but concrete: a recurring written explanation aimed at a planning team rather than at analysts.
METNice to have — Python for data work
Stated as a skill, not evidenced in a role. Sufficient for a nice-to-have.
NOT METNice to have — experience with a BI tool
No BI tool is named anywhere in the document. A nice-to-have, so it caps the top of the range rather than opening a gap.
METNice to have — exposure to apparel or FMCG stock behaviour
Directly on point, across both roles.
RED FLAGS — THINGS TO ASK AT INTERVIEW, NOT REJECTIONS
- Unexplained gap of eight months between roles
THE PIN — EDIT THE JOB DESCRIPTION AND WATCH IT REFUSE TO RE-RANK
The HR platform keeps no version history for an opening's description, so a recruiter can reword it mid-round and nothing anywhere records that they did. Candidates scored under two different wordings are not comparable, but a shortlist sorts them together and looks perfectly fine.
- cd978b46…a1bd839b
- cd978b46…a1bd839b
Application 1
SCORED
Scored under the pinned snapshot.
Application 2
SCORED
Same pin, same criteria.
Application 3 — arrives now
PENDING
Will pin whatever snapshot is current when it is scored.
Unedited, so the current text and the pinned snapshot are the same bytes and the same digest.
WHAT THIS PAGE ESTABLISHES, AND WHAT IT DOES NOT
A demo can only be evidence for the claims it actually exercises. These are the eight claims a visitor might reasonably take from this page, and which of them survive being checked.
| CLAIM | HERE? | WHAT BACKS IT |
|---|---|---|
| The redaction code here is the production redaction code | VERIFIED | src/cv/redact.ts imports nothing, so it was copied rather than ported. Its header names the only edit — one comment's example handle. The tests on this page run that file. |
| Both passes run before any text leaves the machine | VERIFIED | The composition is the service's own expression, un-nested. The panel checks each identifier's literal presence in the output rather than asserting that it is gone. |
| Editing a job description cannot silently re-rank a scored candidate | VERIFIED | The digest is computed live from what you type. The pinned constant it starts from was produced by Node's createHash, and a test fails if the WebCrypto port disagrees with it. |
| A NaN score cannot become a candidate's status | VERIFIED | The refusal expression is verbatim and you can hand it any value. The floor comparison is shown separately, which is the whole point: on its own it says pass. |
| The model obeys the ignore list | Nothing on this page calls a model. The bias panel shows what the prompt forbids and that the identifiers are removed before the call — not what a model does with an adversarial CV. Establishing that needs a bias audit against a held-out set, which is a different piece of work and has not been done. | |
| The scores on this page came out of gpt-4o-mini | They did not. They are written by hand to the schema the real prompt returns, following its rubric. A number invented here and a number from a model would look identical, so the honest move is to label it rather than to dress it up. | |
| The vendor retains nothing once a CV crosses the border | A contractual fact, not something a browser can demonstrate. The service's README still lists confirming zero-data-retention on the vendor account as an open question. | |
| The whole pipeline has run end to end on real applicants | It has not. Sync, intake, scoring and push-back are merged and tested; there has been no piloted end-to-end run, and CV storage has to move off ephemeral disk first. |
WHY THERE IS NO LIVE SIMULATION HERE
The decision this system makes lives in a prompt executed by a hosted model. Running it from this page would need an API key in a browser, which is never acceptable and would be the opposite of the posture the rest of the system takes. And a live call would produce a number that neither you nor this page could tell apart from a plausible invention — so the demo would be asking for trust in exactly the place the system is designed not to require it.
Feeding it a real CV is out of the question in every direction: those documents belong to people who applied for a job, not to a portfolio. So the line is drawn down the middle of the system. The parts that are code run, and you can break them. The part that is a model is quoted, labelled, and left alone.
THIS SERVICE NEVER ACCEPTS OR REJECTS ANYONE — IT RANKS, EXPLAINS, AND HANDS THE DECISION TO A PERSON