<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Panta</title>
    <description>The latest articles on DEV Community by Panta (@withpanta23).</description>
    <link>https://dev.to/withpanta23</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4237864%2Fe9cca3e3-1df6-4a5c-b789-3348a0885449.png</url>
      <title>DEV Community: Panta</title>
      <link>https://dev.to/withpanta23</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/withpanta23"/>
    <language>en</language>
    <item>
      <title>How to evaluate an AI-generated job shortlist</title>
      <dc:creator>Panta</dc:creator>
      <pubDate>Tue, 10 Nov 2026 20:39:03 +0000</pubDate>
      <link>https://dev.to/withpanta23/how-to-evaluate-an-ai-generated-job-shortlist-2680</link>
      <guid>https://dev.to/withpanta23/how-to-evaluate-an-ai-generated-job-shortlist-2680</guid>
      <description>&lt;p&gt;A job-search assistant returns five roles. The titles sound relevant, the links look plausible, and the summary says they match your requirements.&lt;/p&gt;

&lt;p&gt;What has it actually established?&lt;/p&gt;

&lt;p&gt;A useful evaluation separates three things: whether the assistant completed the task, whether the sources support its claims, and whether the roles fit the person's stated constraints. A fluent answer is not evidence for any of them.&lt;/p&gt;

&lt;p&gt;This walkthrough adapts an existing job-shortlist review protocol into a small, repeatable evaluation. It works with any assistant or job-search tool. The worked examples below are fictional; they are not benchmark results.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Freeze the task and the first results
&lt;/h2&gt;

&lt;p&gt;Make the request testable. For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find five software engineering roles with a physical workplace in Switzerland. I work in English and cannot meet a mandatory German requirement. I need an advertised workload that includes 80%. Preserve original listing links and explain unresolved requirements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Record the exact request, date, product, visible model setting and any relevant search settings. Keep that run record separately from the result rows. Avoid placing a CV or private employment history in a public evaluation file.&lt;/p&gt;

&lt;p&gt;Freeze the first five results in their original order before checking sources. If the assistant returns three, record three. If a link is broken, keep that result in the review. Replacing it with a better result would change what you are evaluating.&lt;/p&gt;

&lt;p&gt;Five results are enough to inspect a workflow. They are not enough to rank assistants generally.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep three different language questions separate
&lt;/h2&gt;

&lt;p&gt;A source's language tag, the language of the written advert and the working-language requirement are different attributes.&lt;/p&gt;

&lt;p&gt;For example, an advert written in English can explicitly require German. A German-written advert might describe English as the working language. A missing language statement settles neither question.&lt;/p&gt;

&lt;p&gt;Evaluate the claim the assistant made against the actual requirement. Use explicit labels such as required, preferred, alternative, conflicting and unstated. Do not turn “German is an advantage” into a mandatory requirement, or “not mentioned” into an English-only guarantee.&lt;/p&gt;

&lt;p&gt;The same distinction applies elsewhere: a Swiss headquarters is not evidence that a particular role is based in Switzerland, and a remote label is not evidence that the employer permits work from Switzerland.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Review each claim against its source
&lt;/h2&gt;

&lt;p&gt;For every result, record the suggested URL, the source you could actually inspect and the check time. Then review these dimensions independently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Evidence to inspect&lt;/th&gt;
&lt;th&gt;Common unsupported inference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source access&lt;/td&gt;
&lt;td&gt;Readable employer advert or authorized ATS page&lt;/td&gt;
&lt;td&gt;An HTTP 200 response proves the advert is readable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role&lt;/td&gt;
&lt;td&gt;Role-specific duties and requirements&lt;/td&gt;
&lt;td&gt;A matching title proves occupational fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workplace&lt;/td&gt;
&lt;td&gt;Stated work location and explicit remote eligibility&lt;/td&gt;
&lt;td&gt;Headquarters country determines workplace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;Exact required/preferred/alternative wording&lt;/td&gt;
&lt;td&gt;English-written means English-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workload&lt;/td&gt;
&lt;td&gt;Advertised percentage or range&lt;/td&gt;
&lt;td&gt;80–100% guarantees an agreed 80% contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schedule&lt;/td&gt;
&lt;td&gt;Stated days, hours or shifts&lt;/td&gt;
&lt;td&gt;80% means a four-day week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract and experience&lt;/td&gt;
&lt;td&gt;Contract terms and accepted experience pathways&lt;/td&gt;
&lt;td&gt;“Junior” means no experience is required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;Explicit closure evidence and relevant application destination&lt;/td&gt;
&lt;td&gt;An Apply button guarantees the job is still open&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An inaccessible page should produce an access result: blocked, unavailable, redirect-only or not checked. It should not produce an invented answer about the job.&lt;/p&gt;

&lt;p&gt;A source can support one claim and leave another unknown. A single “verified” badge hides that distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Represent evidence before scoring it
&lt;/h2&gt;

&lt;p&gt;Here is a fictional review record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"result_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"source_access"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"readable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"role_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"supported"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"language_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Contradicted: advert explicitly requires German"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"workload_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Advert states 80–100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"schedule_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Unknown: no days stated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"availability_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"no_explicit_closure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Mandatory German contradicts the declared essential constraint"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This result is readable and relevant to the occupation, but it fails one essential constraint. It should not be called either a fully verified match or a hallucinated vacancy.&lt;/p&gt;

&lt;p&gt;For a second fictional result, imagine a source that cannot be read. Keep its requirement evidence unknown and its decision unresolved. A blocked page does not prove that the assistant invented the role.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;no_explicit_closure&lt;/code&gt; means precisely that. It is not a positive verification that applications are accepted.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Report coverage, contradictions and unknowns
&lt;/h2&gt;

&lt;p&gt;Start with counts rather than a single accuracy score. Report how many results were returned, how many sources were readable, which claims were contradicted and what remained unresolved.&lt;/p&gt;

&lt;p&gt;The linked blank worksheet uses one row per result. After reviewing it, this standard-library Python script summarizes two useful dimensions without inventing an overall accuracy metric:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ai-job-shortlist-review.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Blank template rows do not count as returned results.&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"suggested_url"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Returned results recorded: {len(rows)}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"source_access"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"role_match"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"{field}: {dict(counts)}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;readable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"source_access"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"readable"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Readable original sources: {readable}/{len(rows)}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are descriptive counts of recorded review labels, not a semantic evaluator. The script cannot determine whether a human or model labeled the evidence correctly.&lt;/p&gt;

&lt;p&gt;Keep unreadable sources in the returned-result denominator. If you also report a metric among readable sources, label that separate denominator explicitly. Otherwise a system with many inaccessible sources can look artificially strong.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Make repeated runs comparable
&lt;/h2&gt;

&lt;p&gt;For a useful comparison, keep the task, result cutoff and review criteria stable. Record differences in date, access, tool availability and model settings. Live vacancies change, so a later result is not automatically evidence of a better assistant.&lt;/p&gt;

&lt;p&gt;Preserve the original run and add later checks as new observations. Do not use today's readable page to rewrite yesterday's blocked result.&lt;/p&gt;

&lt;p&gt;For a larger evaluation, use more tasks, independent review and explicit adjudication of disagreements. This protocol alone does not establish reviewer accuracy, causal effects or a representative model ranking. It also does not establish that an application was submitted or that anyone was hired.&lt;/p&gt;

&lt;p&gt;The practical outcome is a shortlist a person can inspect: which claims are supported, which are contradicted, and which require follow-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worksheet and provenance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://withpanta.com/research/ai-job-shortlist-review.csv" rel="noopener noreferrer"&gt;Download the five-row review worksheet&lt;/a&gt;. The full &lt;a href="https://withpanta.com/insights/audit-ai-job-shortlist/" rel="noopener noreferrer"&gt;review protocol&lt;/a&gt; documents the field definitions and limitations. Protocol version: &lt;code&gt;panta-shortlist-review-v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on Panta, where this protocol was developed. This adaptation was prepared with AI assistance, including generated text. The examples are fictional, and no assistant-performance benchmark is claimed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
