Back to Experiments

Experiment 007

Stable

From 30 Minutes to 5 Seconds: Automating Mass CV Screening Without In-System AI.

Can 1–5 CV PDFs be screened from a 30-minute manual review to a 5-second ranked shortlist — without heavy AI or hallucination — using only pdf.js, 24-node n8n orchestration, and a predictable rule-based scoring on a private server? Deadline: 3 days. Delivered in 5 hours.

System Requirements

Processor

Intel Core I5 11400F

RAM

DDR4 16GB

GPU

AMD RX 6700 XT 12GB VRAM

Inference Engine

llama.cpp

Model (Assistant Only)

Ornith 1.0 35B A3B IQ4_NL

Harness / IDE

OPENCODE + MCP

Orchestration

n8n

Database

PostgreSQL

Frontend

HTML5 + Tailwind CDN

Logic

Vanilla JS + pdf.js 2.16

Server

Python 3 (Private)

Status

STABLE — 5 Hours

Screenshots

Screenshot 1 — hero of CV screening landing page — Seleksi CV 10x Lebih Cepat, upload 1-5 PDF, webhook card
Hero — Seleksi CV 10x Lebih Cepat, 1–5 PDF
Screenshot 2 — upload queue showing 5 files with badges cv_1..cv_5, size, remove X, drag & drop zone
Upload Queue — 5 Files, Badges cv_1..cv_5, Max 5MB
Screenshot 3 — ranking table result with 5 CV ranked by score 80,75,50,35,25 and Lolos/Tidak status
Ranking Result — 5 CV Ranked, 2 Lolos (≥70%)
Screenshot 4 — n8n workflow Technical Test with 24 nodes from Webhook to Postgres Clean
n8n Workflow — 24 Nodes, 3 Branches, No Hallucination
Screenshot 5 — local server running on private computer via Python, localhost:8010
Private Server — Python 3, No Data Leaves
Screenshot 6 — local URL proof http://localhost:8010 and webhook http://localhost:5678
Local URL — Private, No Public Link

Visualization

Predictable Scoring — 70% Threshold, No LLM Feeling

20 skill = 40% + 6 education = 30% + 6 experience = 30%. 5 CV samples: 80,75,50,35,25 — only 2 Lolos ≥70%. The rest are predictable, auditable, and adjustable.

Ranking — 5 CV Samples

2 Lolos at ≥70%
0 score / 100 — threshold 70 (dashed line in analysis, not in chart) 100

CV3 (Drafter & 3D, 10y) 80 → Lolos, CV1 (Arch, 7y) 75 → Lolos, CV4/2/5 (50,35,25) → Tidak Lolos. CSV says Lolos 80% utk Drafter & 3D Visualization — HR knows why.

Workflow Orchestration, Not Hallucination

Bot without in-system AI — local LLM only as syntax assistant.

1–5 PDF (Browser) pdf.js extract → JSON Array POST /webhook/upload-cv
Switch Posisi A/B/C Code Hitung 40/30/30 Merge + Rank Postgres + CSV/JSON
TRUNCATE after delivery → fresh for next batch Private server — no data leaves

Local LLM (Ornith 35B) = assistant only, supervised by builder — not the brain

Deadline was 3 days (Fri 4 Sept 2026) — delivered in 5 hours. That speed came from keeping AI outside the system and keeping the workflow predictable.

Experiment Details

Problem

A manufacturing company is hiring fast for 3 roles (Junior Architect, Drafter & 3D Visualization, Interior Designer). HR screens CVs manually — opening 5 PDFs one-by-one takes ~30 minutes, is subjective, and doesn’t scale. The AI Specialist test asks for a POC in 3 days that is end-to-end, lightweight, HR-friendly, mobile-friendly, and runs locally on a private server (not public, no login, no SaaS).

Specifications
  1. 1.

    Single-page landing `index.html` 626 lines — `HTML5 + Tailwind CDN + Vanilla JS + pdf.js 2.16.105`, `Inter + Manrope`, `bg-grid`, `data-reveal`, `mobile sticky bottom` bar, `toast` (success/error/info, auto 3.2s), `progress Mengekstrak 3/5...` with bar, validation `PDF only, max 5MB/file, max 5 files, dup check`, badge `cv_1..cv_5`, no login.

  2. 2.

    Client-side extraction: pdfjsLib.getDocument({data: arrayBuffer}).promise → getPage → getTextContent → join, returns {text, page_count}, wraps as JSON Array [{id: "cv_1", filename, size_kb, page_count, text, uploaded_at}] — array directly, for n8n `Split In Batches`, with `AbortController 15s` and `fallback Download JSON` if webhook offline.

  3. 3.

    n8n workflow `Technical Test` 24 nodes — `Webhook POST /upload-cv (responseNode)` → `Code Extractor` (pecah 5 CV) → `Code Keyword Router` (Posisi A/B/C) → `Edit Fields Rapihin` (10 fields) → `Switch Posisi` (3 outputs + fallback) → 3 branches each `Pemecah (1 per 1) → Normalisasi → Edit Fields → Hitung 40/30/30 → ekstrak` → `Merge Gabungan (Append 3)` → `Code Ranking (sort desc)` → `database (Postgres upsert)` → `Respond Ranking (allIncomingItems)` → `Postgres Clean (TRUNCATE)`.

  4. 4.

    Keyword routing: Posisi A `Junior Architect` (12 keywords: autocad, revit, sketchup, lumion, building code...), Posisi B `Drafter & 3D Visualization` (15: enscape, d5 render, shop drawing, as-built...), Posisi C `Interior Designer` (10: furniture, lighting, photoshop, illustrator...).

  5. 5.

    Predictable scoring (per report, 70% threshold): 20 skill = 40% + 6 edu = 30% + 6 exp = 30%, total = round(skillScore+eduScore+expScore), ≥70% Lolos, <70% Tidak — implemented as 1 CV per 1, no batch OOM, 3 jalur duplicated for fairness per position.

  6. 6.

    Output: ranking `rank 1..5` with {id_cv, nama_file, posisi, total_score, status, alasan} — e.g., `Lolos 80% utk Drafter & 3D Visualization` — table in landing page + `Download Ranking (JSON)` + `Download CSV` with Indonesian headers Peringkat, ID CV, Nama File, Posisi Dilamar, Skor (0-100), Status, Alasan + BOM 0xEFBBBF for Excel.

  7. 7.

    Local private server: Python 3 http.server on http://localhost:8010 (8000 taken) + webhook http://localhost:5678/webhook/upload-cv — no public URL, no data leaves, PDFs only in browser memory.

  8. 8.

    Mobile & robust: breakpoints `sm 640 md 768 lg 1024`, upload zone `full width`, file cards `full width`, ranking table `overflow-x-auto scrollbar-thin`, try/catch per file, `firstLine` name fallback, `slice(0,5)` guard, `TRUNCATE` after delivery → fresh for next batch.

Hypothesis

For mass hiring with a 3-day deadline, workflow orchestration (pdf.js + n8n + Postgres) with a predictable, auditable rule is more maintainable, predictable, and resource-efficient than embedding an LLM (even local) inside the scoring. Hallucination risk outweighs benefit; local LLM is better as a supervised syntax assistant (Ornith 35B via OpenCode/MCP) rather than the system’s brain.

Experiment

I built the landing page per component (upload zone, list with `cv_1..cv_5`, pdf.js extract, JSON POST, ranking table) with deterministic prompts via OpenCode + MCP, each validated in small windows. I built the n8n workflow 1 node per checkpoint (18 checkpoints, 24 nodes), tested per node, and confirmed with the owner before next. The whole POC was delivered in 5 hours — well under the 3-day deadline — on consumer hardware, 100% locally.

What Failed
  1. 1.

    Small quantized LLM → unstable: aggressive quant made scoring unpredictable, hard to debug, hallucinated skills — abandoned for rule-based 40/30/30.

  2. 2.

    Big LLM → heavy: more stable but needs strong hardware (my 12GB VRAM cannot sustain 5 CV parallel efficiently) — abandoned for workflow lightness.

  3. 3.

    One-shot n8n generation → wrong: missed `body` vs `body.data` wrapping, Switch missed keyword, Merge misaligned — fixed by 1-node-per-checkpoint with explicit `Map` and `responseNode`.

  4. 4.

    pdf.js on scanned PDFs → empty: no text layer → `text.length <10` — handled with toast + `_error` flag per file, still pushing item instead of failing batch, plus 15s `AbortController` and fallback `Download JSON`.

Iteration

Pivoted from “AI inside” to “workflow without AI inside, AI only as assistant”. Extracted PDFs in the browser (not server) to keep n8n light and allow fallback. Duplicated 3 branches (A/B/C) for fairness per position instead of one generic flow. Made scoring auditable (HR can tweak 20/6/6 or threshold 70). Delivered in 5 hours because context was kept small and each node was tested before the next.

Evidence

Figures from the live POC on private server. You can verify via screenshots above and the workflow ID v1lQYQUZndoeoR6w.

Landing Lines

626

index.html

n8n Nodes

24

Technical Test

Branches

3

A/B/C + fallback

CV Tested

5

+ Vacancy PDF

Threshold

70%

Lolos / Tidak

Time

~5s

vs 30min manual

Deadline

5 Hours

of 3 days

Ranking

2 Lolos

80,75 / 50,35,25

Result

POC is stable and delivered well within the 3-day deadline — actually completed in just 5 hours — live on private server http://localhost:8010. HR uploads on mobile or desktop, gets a ranked shortlist in ~5 seconds (2 Lolos of 5), downloads CSV for Excel, and Postgres is truncated automatically — no token cost, no data leaves, no manual cleanup, HR only does 3 clicks: Upload → Send → Download.

What I Learned

Predictability beats probability for HR: a simple auditable formula HR can tweak is more valuable than a black-box LLM. Workflow orchestration is the skill — supervision over syntax, bug potential, and ease of maintenance matters more than model size. Keeping AI as assistant (Ornith 35B) rather than the system’s brain made the POC shippable in 5 hours on consumer hardware, 100% locally.

Practical Implication

For business owners/HR: you don’t need a SaaS ATS — a local workflow you own 100% cuts 30 min to 5 sec, with a clear Lolos 70% utk Junior Architect reason per candidate, fair per position, private. For recruiters: this proves the builder can ship landing page + n8n + Postgres end-to-end in 5 hours, 3-day deadline, with no hallucination and no data exfiltration — predictable engineering.

Configuration Template

pdf.js — browser-side extract (keeps n8n light)

async function extractTextFromPDF(file){
  const pdf = await pdfjsLib.getDocument({ 
    data: await file.arrayBuffer() 
  }).promise;
  let fullText = "";
  for(let i=1; i<=pdf.numPages; i++){
    const page = await pdf.getPage(i);
    fullText += (await page.getTextContent())
      .items.map(it=>it.str||"").join(" ") + "\n";
  }
  return { text: fullText.trim(), page_count: pdf.numPages };
}
// → wrap as [{id: "cv_1", filename, size_kb, page_count, text, uploaded_at}]

n8n scoring — predictable 40/30/30, 70% threshold (report)

// 20 skill = 40% + 6 edu = 30% + 6 exp = 30% → 70% Lolos (per report)
const skillScore = (Math.min(keahlian.length,20)/20)*40;
const eduScore   = (Math.min(edukasi.length,6)/6)*30;
const expScore   = (Math.min(expTahun,6)/6)*30;
const total = Math.round(skillScore+eduScore+expScore);
const status = total>=70 ? "Lolos" : "Tidak Lolos";
const alasan = status==="Lolos"
  ? `Lolos ${total}% utk ${posisi}`
  : `Tidak Lolos ${total}% utk ${posisi}`;
Do not copy this snippet blindly. The key is pdf.js in browser + rule 40/30/30: local LLM was only a syntax assistant (Ornith 35B), supervised — not the brain. That kept the POC predictable and shippable in 5 hours.
  1. 1.

    I extracted PDFs in the browser so n8n only receives clean JSON — no binary handling, demo works even if n8n is offline (fallback Download JSON).

  2. 2.

    I kept scoring rule-based (20/6/6, 70%) so HR can audit and tweak it — not an LLM guess that hallucinates skills.

  3. 3.

    I duplicated 3 branches A/B/C for fairness — Architect scored on Revit, Drafter on Enscape, Interior on Photoshop — same 40/30/30 but different skill sets.

  4. 4.

    I kept it private & fresh: Python 3 on localhost:8010, TRUNCATE after delivery — no data leaves, no manual DB cleanup.

FAQ

Frequently Asked Questions

Why not put AI inside the scoring system?

Small quantized models hallucinate and are unpredictable; big models are heavy and need strong hardware (RX 6700 XT 12GB cannot sustain 5 CV parallel). Workflow orchestration (pdf.js + n8n + rule 40/30/30, 70% threshold) is stable, fast (~5s), auditable, and 100% local — AI was only a supervised syntax assistant (Ornith 35B).

How does HR know why a CV passed or failed?

Each row has a clear reason: e.g., Lolos 80% utk Drafter & 3D Visualization or Tidak Lolos 35% utk Junior Architect. The CSV header is Indonesian — Peringkat, Skor (0-100), Status, Alasan — so HR opens it in Excel and knows immediately. Threshold 70% is adjustable.

What if the CV is a scanned PDF or the webhook is offline?

Scanned PDFs with no text layer trigger a toast (text <10 chars) but don’t fail the batch — the item is sent with empty text and an _error flag. If the webhook is offline (15s timeout), the landing page shows a fallback Download JSON so the demo still works.

For those of you unable to read this data from technical standpoint, here is the conclusion

This is not a demo — it’s a hiring tool that cuts 30 minutes of manual CV opening to about 5 seconds. You upload 1–5 PDFs on your phone or laptop (drag & drop or tap), and it ranks them for you.

It’s fair per position: a Junior Architect is scored on AutoCAD/Revit, a Drafter on Enscape/D5, an Interior Designer on Photoshop — so no one is judged on the wrong skills. You get a table (Rank, CV, Position, Score, Status) and a CSV you open in Excel — with a clear reason like “Lolos 75% utk Junior Architect”.

It doesn’t use heavy AI inside. The score is a simple, auditable rule HR can change (20 skills 40% + 6 edu 30% + 6 exp 30%, pass at 70%). That makes it predictable and fast, with no hallucination.

It runs 100% on your own private computer — the PDFs never leave your browser, and the database cleans itself after each batch. No SaaS fee, no token cost.

It was due in 3 days — it was delivered in 5 hours. For a small team hiring a few roles at a time, it’s sufficient, stable, and you own it 100%.

Disclaimer

A Note on Results

This is a 3-day Proof of Concept — actually delivered in 5 hours — not a full Applicant Tracking System. The 70% threshold and 40/30/30 weights are subjective and based on the 5 CV samples — intentionally adjustable to the recruitment team’s criteria. The result (2 Lolos of 5) is a reference for HR to decide who to interview, not a final hiring decision. For its scope (5 CVs at a time, local, no token cost), it is sufficient and stable, but a scanned PDF with no text layer will still need manual review.