Job posting triage: four engines, one labelled test split

Four engines answered the same four questions about the same postings: each saw 1,000 postings from the test split of the public fake job postings dataset, drawn with seed 0 as a uniform random sample without replacement. A question is scored on the rows that carry a gold answer: 1,000 for fraudulent, 627 for required_experience, 520 for required_education, 1,000 for salary_range_stated. Every answer, its latency, its confidence and its cost were recorded. The fraud question holds 53 positive postings in this sample.

Fraud triage

The two floor rows carry a neutral rule and a floor chip. A 3px rule in teal marks an arm that clears the 94.7% majority floor on this question, crimson marks one that does not. Precision, recall and F1 belong to the fraudulent class, the rare one, and the number under each of them is its 95% interval (the file records the interval frame as: 53 positives in this frame).
armnaccuracymacro F1precisionrecallF1PR-AUCinvalid JSONp50 latency
classifier_dev1,00094.7%64.2%50.0%[0.31, 0.69]22.6%[0.13, 0.36]31.2%[0.18, 0.45]0.2760.0%2.9 ms
classifier_dev_no_rubric1,00093.0%61.7%30.2%[0.19, 0.45]24.5%[0.15, 0.38]27.1%[0.16, 0.39]0.2600.0%6.5 ms
tfidffitted1,00097.0%84.2%74.5%[0.60, 0.85]66.0%[0.53, 0.77]70.0%[0.59, 0.79]0.7800.0%0.002 ms
gliner1,00089.7%52.5%9.7%[0.05, 0.20]11.3%[0.05, 0.23]10.4%[0.04, 0.18]0.0700.0%930 ms
llm_localno spread1,0005.3%5.0%5.3%[0.04, 0.07]100.0%[0.93, 1.00]10.1%[0.08, 0.13]0.0530.0%12,261 ms
llm_hosted_json_object1,00094.8%65.1%60.0%[0.39, 0.78]22.6%[0.13, 0.36]32.9%[0.19, 0.47]0.2820.5%4,055 ms
majority_classfloor1,00094.7%48.6%n/an/a0.0%[0.00, 0.07]0.0%[0.00, 0.00]n/a0.0%n/a
dummy_stratifiedfloor1,00090.6%51.4%8.2%[0.03, 0.19]7.5%[0.03, 0.18]7.8%[0.02, 0.16]0.0550.0%n/a

The table scrolls sideways on a narrow screen.

Precision, recall, PR-AUC and the invalid-JSON rate all belong to the fraudulent class, the rare one. 53 of 1,000 scored postings carry the fraudulent label, which is 5.3%. p50 latency is the median answer time per posting for this question.

On F1, tfidf's interval [0.59, 0.79] starts above the highest upper bound of every other arm (0.47), so that ordering does not depend on where the threshold sits. The other arms' intervals overlap in part (llm_hosted_json_object, classifier_dev, classifier_dev_no_rubric, llm_local, gliner), and their accuracy runs from 5.3% to 94.8%, against a 94.7% majority-class floor on accuracy. The file records the interval frame as: 53 positives in this frame.

Arms with no spread

These arms answer nearly the same thing about every posting, so each row is a single operating point. The numbers come from the same rows as the table above.

Per-class precision and recall
armnlegitimate precisionlegitimate recallfraudulent precisionfraudulent recall
classifier_dev1,00095.8%98.7%50.0%22.6%
classifier_dev_no_rubric1,00095.8%96.8%30.2%24.5%
gliner1,00095.0%94.1%9.7%11.3%
llm_hosted_json_object1,00096.0%98.8%60.0%22.6%
llm_local1,000n/a0.0%5.3%100.0%
tfidf1,00098.1%98.7%74.5%66.0%
majority_classfloor1,00094.7%100.0%n/a0.0%
dummy_stratifiedfloor1,00094.8%95.2%8.2%7.5%

The table scrolls sideways on a narrow screen.

  • Why a cell reads n/a
  • llm_local / legitimate, precision: no row of this arm was predicted with this label, so precision has no denominator and is undefined
  • majority_class / fraudulent, precision: always predicts 'legitimate' (947/1000 = 94.7% of the scored subset) and carries no distribution, so brier, ece, pr_auc and the routing sweep are null for it

One posting, four verdicts

Subsea Process System Engineer

Corporate overviewAker Solutions is a global provider of products, systems and services to the oil and gas industry. Our engineering, design and technology bring discoveries into production and maximize recovery from each petroleum field. We employ approximately 28,000 people in about 30 countries. Go to #URL_0fa3f7c5e23a16de16a841e368006cae916884407d90b154dfef39764

Posting test-6 from the public dataset, showing the first 400 characters of the text the engines read. Their input is the title plus the description, cut at 6,000 characters. The text is verbatim, so a link in the source appears as a #URL_ placeholder and runs of whitespace stay as they were.

What each engine said about it

Bar length is the probability the engine gave its own answer. Teal marks a call that matches the gold label, crimson marks a call that does not.

  1. classifier_devlegitimate1.00wrong
  2. classifier_dev_no_rubriclegitimate0.99wrong
  3. glinerlegitimate1.00wrong
  4. llm_hosted_json_objectlegitimate0.98wrong
  5. llm_localfraudulent1.00right
  6. tfidffraudulent0.61right

Gold label: fraudulent. 4 of 6 engines disagreed with it.

No answer on this posting from: majority_class, dummy_stratified.

The four engines

6 arms ran on this sample, and they come from 4 engines: Jev, served by classifier.dev, TF-IDF with logistic regression, the GLiNER2.5-base zero-shot encoder, Qwen2.5-1.5B-Instruct, served locally and over a hosted endpoint, and the other 2 arms vary one of them rather than adding an engine.

classifier_dev
Jev 1.13.0, served by classifier.dev. One request carries up to 1,000 postings and returns a probability for every label of a question in a single pass. No tokens generated.
classifier_dev_no_rubric
The same Jev arm with the per-question rubric text dropped from the request. This is the ablation for what the instruction text buys.
tfidf
TF-IDF over word unigrams and bigrams, then one logistic regression per question, fitted on the train and validation rows that carry a gold label.
gliner
fastino/gliner2.5-base-v1, a 194M-parameter zero-shot encoder. All four questions are compiled into one schema and read in one forward pass per posting. Its window is 512 subwords, so long postings are truncated before they are scored.
llm_local
Qwen2.5-1.5B-Instruct Q4_K_M, served by llama-server on this machine. One chat completion per posting answers all four questions under a strict JSON schema at temperature 0. The model reports its own confidence.
llm_hosted_json_object
The same LLM code path pointed at a hosted model over an OpenAI-compatible endpoint, kept as its own row so the two are never confused. The gateway rejects json_schema, so the reply shape goes in the prompt and the reply is validated afterwards.

Calibration

ECE sorts every answered posting into ten bins of stated probability and averages the gap between the probability an arm claimed in a bin and the share of those answers it got right. 0.000 means the stated number can be read as a probability; 0.100 means the arm is off by ten points on average.

A 3px rule in teal marks an ECE of 0.05 or less, where the stated probability is within five points of the outcome on average; crimson marks an arm above that. A Brier score is the mean squared error of the same probabilities, so lower is better in both columns.
armnBrierECEexcluded
classifier_dev1,0000.0540.0460
classifier_dev_no_rubric1,0000.0730.0700
tfidf1,0000.0330.0330
gliner1,0000.0940.0820
llm_local1,0000.9470.9470
llm_hosted_json_object9950.0470.0155

The table scrolls sideways on a narrow screen.

Stated probability against accuracy per arm on the fraudulent question
Calibration on the fraud question, drawn by the evaluator from the same records these tables come from. A well calibrated arm sits on the diagonal.

The bins below cover llm_hosted_json_object, tfidf, the two arms with the lowest ECE on this question, and llm_local, gliner, the two with the highest. These are the ends of the ranking: the arms whose stated probabilities land closest to what they get right, and furthest from it. 21 bins across these arms held no answer at all and are left out.

bin of stated probabilitynmean stated probabilityaccuracy in the bin
llm_hosted_json_object
0.6 to 0.750.6200.800
0.7 to 0.8450.7230.867
0.8 to 0.9690.8350.899
0.9 to 1.08760.9590.962
tfidf
0.1 to 0.2210.1380.429
0.2 to 0.3100.2631.000
0.3 to 0.440.3441.000
0.4 to 0.520.4111.000
0.5 to 0.610.5711.000
0.6 to 0.750.6481.000
0.7 to 0.830.7721.000
0.8 to 0.910.8241.000
0.9 to 1.09530.9680.981
llm_local
0.9 to 1.01,0001.0000.053
gliner
0.5 to 0.6150.5500.733
0.6 to 0.7130.6640.308
0.7 to 0.8130.7630.769
0.8 to 0.9290.8610.793
0.9 to 1.09300.9910.913

The table scrolls sideways on a narrow screen.

Routing and cost

Read a row as: at stated probability tau or above, this share of postings is acted on, and this is how the acted-on set scores. The 3px rule marks a tau whose precision clears 0.90 in teal and one that does not in crimson. tau is a sequence, so the rows are ordered rather than numbered.
taucoverageprecisionrecallfalse negatives among acted onpositives acted on
classifier_dev
0.502.4%50.0%22.6%4112 of 53
0.701.2%83.3%18.9%4310 of 53
0.900.8%87.5%13.2%467 of 53
0.950.6%100.0%11.3%476 of 53
classifier_dev_no_rubric
0.504.4%29.5%24.5%4013 of 53
0.702.2%50.0%20.8%4211 of 53
0.901.1%63.6%13.2%467 of 53
0.950.8%75.0%11.3%476 of 53
tfidf
0.501.0%100.0%18.9%4310 of 53
0.700.4%100.0%7.5%494 of 53
0.900.0%n/a0.0%530 of 53
0.950.0%n/a0.0%530 of 53
gliner
0.506.2%9.7%11.3%476 of 53
0.704.9%12.2%11.3%476 of 53
0.903.9%10.3%7.5%494 of 53
0.953.4%8.8%5.7%503 of 53
llm_local
0.50100.0%5.3%100.0%053 of 53
0.70100.0%5.3%100.0%053 of 53
0.90100.0%5.3%100.0%053 of 53
0.95100.0%5.3%100.0%053 of 53
llm_hosted_json_object
0.502.0%60.0%22.6%4112 of 53
0.702.0%60.0%22.6%4112 of 53
0.900.7%100.0%13.2%467 of 53
0.950.5%100.0%9.4%485 of 53
majority_class
0.500.0%n/a0.0%530 of 53
0.700.0%n/a0.0%530 of 53
0.900.0%n/a0.0%530 of 53
0.950.0%n/a0.0%530 of 53
dummy_stratified
0.504.9%8.2%7.5%494 of 53
0.704.9%8.2%7.5%494 of 53
0.904.9%8.2%7.5%494 of 53
0.954.9%8.2%7.5%494 of 53

The table scrolls sideways on a narrow screen.

classifier.dev is a shared service and its probabilities shift slightly between fetches of the same posting, so a threshold-dependent cell in this table can move by one record between reruns while the label-based tables in Fraud triage do not. Treat the routing cells as indicative at high thresholds, where few postings carry the decision: at tau 0.95, 3 arms act on fewer than 1% of the 1,000 scored postings (llm_hosted_json_object 5, classifier_dev 6, classifier_dev_no_rubric 8).

False negatives are the fraudulent postings a tau leaves in the unreviewed pile, so they are what higher precision costs. 6 of these rows sit at a tau that no score in the arm reaches, so nothing is acted on: coverage is 0.0% and precision has no value.

Latency and tokens are measured on the fraud question. A classification is counted per answer, so one posting on one question is one classification.
armp50 per postingp95 per postingmean tokens inmean tokens outclassifications spentwall seconds
classifier_dev2.9 ms2.9 msn/an/a3,14754.4 s
classifier_dev_no_rubric6.5 ms6.5 msn/an/a3,1476.5 s
tfidf0.002 ms0.002 msn/an/an/a86.7 s
gliner930 ms1,067 ms247n/an/a1,915.4 s
llm_local12,261 ms17,586 ms45091n/a3,249.2 s
llm_hosted_json_object4,055 ms12,138 ms648800n/a1,259.9 s

The table scrolls sideways on a narrow screen.

For classifier_dev, classifier_dev_no_rubric, tfidf, p50 equals p95 in this run, so those two columns describe one measured value rather than a spread.

Per-row latency is wall clock on the machine that ran the arm; arms that ran concurrently include each other's load in these numbers, so the projection is an upper bound for this machine rather than a benchmark.

Projection at 10,000 postings a day

Precision and recall of the acted-on set as tau rises
Precision and recall of the acted-on set as tau rises, drawn by the evaluator. Each point is one tau from the table above.

Method checks

The rubric ablation

One field differs between these rows: the instructions text carrying the per-question rubric. The model, the postings and the labels are the same.

request variantaccuracymacro F1precision (fraudulent)recall (fraudulent)PR-AUC
classifier_devwith the rubric94.7%64.2%50.0%22.6%0.276
classifier_dev_no_rubricwithout the rubric93.0%61.7%30.2%24.5%0.260

The table scrolls sideways on a narrow screen.

Unusable answers

An answer is unusable when the arm failed its own output validation. Those rows stay in the run and are scored wrong, so they cost accuracy rather than disappearing from it.

Frame check: every arm answered the same set of postings on every question, so the rows in the tables above are directly comparable.

Leakage guard: no arm rises more than 3 points above the 82.2% majority floor on the salary control question, whose label lives in the dataset and not in the posting text. The guard is one-directional: only an accuracy above the floor plus 3 points fails, because only that direction can mean the gold label leaked into the posting text. Under the floor: classifier_dev_no_rubric sits -57.7 points; gliner sits -55.7 points, which reads as an arm that is not answering the question that was asked rather than as a leak.

What this does not prove