<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[PepLookup]]></title><description><![CDATA[PepLookup is a price comparison site for research peptides. We compare listings from 88 suppliers and calculate the cost per mg, so prices can be compared like ]]></description><link>https://peplookup.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aec889f0573fcb7197ece2a/d2eb4b91-b598-4b0f-bced-8fec134c2207.jpg</url><title>PepLookup</title><link>https://peplookup.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 07:29:03 GMT</lastBuildDate><atom:link href="https://peplookup.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why I built a COA checklist instead of a PDF parser]]></title><description><![CDATA[Why PepLookup's COA Reader is a checklist, not a PDF parser: modelling lab-report trust as data, with a four-state verdict and no uploads.]]></description><link>https://peplookup.hashnode.dev/why-i-built-a-coa-checklist-instead-of-a-pdf-parser</link><guid isPermaLink="true">https://peplookup.hashnode.dev/why-i-built-a-coa-checklist-instead-of-a-pdf-parser</guid><category><![CDATA[webdev]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[privacy]]></category><category><![CDATA[showdev]]></category><dc:creator><![CDATA[PepLookup]]></dc:creator><pubDate>Thu, 05 Nov 2026 14:57:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aec889f0573fcb7197ece2a/341ba439-d461-4504-9afd-855f4edca030.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I run PepLookup, a site that compares research peptide listings from dozens of suppliers. One of its tools is the <a href="https://www.peplookup.com/tools/coa-reader">COA Reader</a>, which helps people judge a certificate of analysis (COA): the lab report a supplier publishes to show what was tested and how.</p>
<p>The obvious way to build that tool is a PDF parser: upload the report, extract the numbers, return a score. I didn't build that. I built a checklist. This post explains why, how the checklist is modelled as data, how its verdicts behave and why it runs entirely in the browser.</p>
<h2>The problem: there is no standard COA</h2>
<p>If you've worked with invoices or receipts, you know layout-agnostic extraction is hard. Lab reports are worse.</p>
<p>A COA might be a clean PDF from an independent lab, a scan of a printed page, a screenshot pasted into a product image, or a document assembled by the supplier. The same fields (batch number, method, purity, test date, analyst) appear in different places, under different names, sometimes in tables and sometimes in images.</p>
<p>A parser that works on one lab's template fails on the next. Worse, a parser can only read what is <em>written</em>. The questions that matter for trust are mostly about what is <em>missing</em> or <em>inconsistent</em>:</p>
<ul>
<li>Is there a batch number at all?</li>
<li>Does it match the number on the vial?</li>
<li>Was the same report reused across different products?</li>
<li>Do the fonts and alignment look edited?</li>
</ul>
<p>None of those are extraction problems. They're judgement problems that a person can answer in seconds while looking at the document. The software's job is to make sure they ask the right questions.</p>
<p>I took a related approach in <a href="https://dev.to/peplookup/how-i-normalise-3200-supplier-listings-to-a-fair-cost-per-mg-1gdl">my earlier post on normalising supplier listings</a>: when the input is messy and a confident wrong answer is costly, flag the uncertain case for a human instead of guessing.</p>
<h2>The decision: validate, don't extract</h2>
<p>So the tool flips the problem. Instead of reading the report for the user, it gives them a short list of things to look for and records what they find.</p>
<p>That has three practical benefits:</p>
<ol>
<li><strong>It works on any format.</strong> PDF, image or screenshot all work, because the user is the reader.</li>
<li><strong>It's harder to fool with a convincing layout.</strong> A forged report can contain perfect-looking numbers. It's much harder to fake a matching lot number, a real chromatogram and a named analyst at the same time.</li>
<li><strong>It builds a habit.</strong> The list is short enough to remember, so the questions can stick even when the tool isn't open.</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/6aec889f0573fcb7197ece2a/50a54770-24e9-440e-b21d-e4c979b79cef.png" alt="The COA Reader checklist with one red flag ticked, showing the Mixed signals result" style="display:block;margin:0 auto" />

<h2>The data model: 16 flags, two lists</h2>
<p>The checklist is plain data: 8 red flags (signs of risk) and 8 green flags (signs of trust). Each list has its own progress counter, from 0/8 to 8/8. Here is a simplified version of the structure:</p>
<pre><code class="language-ts">type FlagKind = "red" | "green";

interface Flag {
  id: string;
  kind: FlagKind;
  label: string;
}

// Labels shortened for the article.
export const FLAGS: Flag[] = [
  // Red flags: each one is a risk
  { id: "no-lot", kind: "red", label: "No lot or batch number" },
  { id: "no-method", kind: "red", label: "No analytical method named" },
  { id: "no-trace", kind: "red", label: "No chromatogram or MS spectrum" },
  { id: "reused", kind: "red", label: "Same COA reused across products or batches" },
  // ...4 more red flags

  // Green flags: each one adds trust
  { id: "third-party", kind: "green", label: "Independent third-party lab" },
  { id: "lot-match", kind: "green", label: "Lot number matches the vial label" },
  { id: "named-method", kind: "green", label: "Named method (HPLC-UV, LC-MS)" },
  { id: "recent", kind: "green", label: "Report dated within roughly 12 months" },
  // ...4 more green flags
];
</code></pre>
<p>Modelling the flags as data rather than markup means both lists render from one source, the counters come from a simple filter and changing a label is a one-line edit with no layout work.</p>
<p>The two lists mirror each other. "No analytical method named" sits against "a named method with real parameters". "No lot number" sits against "a lot number that matches the vial". That symmetry makes the user look at each property twice, once for its absence and once for its presence.</p>
<p>If you want to see what real reports look like before using the checklist, PepLookup collects suppliers' <a href="https://www.peplookup.com/lab-reports">published lab reports</a> in one place.</p>
<h2>The verdict</h2>
<p>The user ticks what the report actually shows, on both lists. Below the lists, a result box shows the running tally (for example "4 green · 1 red") and one of four states. These are the outcomes I saw when testing the live tool:</p>
<table>
<thead>
<tr>
<th>State</th>
<th>Seen with</th>
<th>Message shown</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Not started</strong></td>
<td>Nothing ticked</td>
<td>Asks the user to tick items on both lists</td>
</tr>
<tr>
<td><strong>Needs review</strong></td>
<td>3 red and 0 green; 8 red and 0 green</td>
<td>"Several red flags are present. Treat this COA with real skepticism and ask the seller directly for the missing pieces before trusting it."</td>
</tr>
<tr>
<td><strong>Mixed signals</strong></td>
<td>1 red and 0 green; 1 green and 0 red; 4 green and 1 red</td>
<td>"A mix of good and concerning signs. Worth double-checking the specific gaps before you rely on this document."</td>
</tr>
<tr>
<td><strong>Looks legit</strong></td>
<td>8 green and 0 red</td>
<td>"A strong set of green flags with no red flags checked. This COA passes a basic sanity check."</td>
</tr>
</tbody></table>
<img src="https://cdn.hashnode.com/uploads/covers/6aec889f0573fcb7197ece2a/5d89755a-e7fa-45aa-b1c6-9026cc89e020.png" alt="The COA Reader with all eight green flags ticked, showing the Looks legit result" style="display:block;margin:0 auto" />

<p>Three design choices matter more to me than the exact tally:</p>
<ul>
<li><strong>One good sign isn't enough.</strong> Ticking a single green flag with no red flags still returns "Mixed signals". A clean result needs a strong set of green flags, so one reassuring detail can't carry the whole verdict.</li>
<li><strong>Every state says what to do next.</strong> "Needs review" tells the user to ask the seller for the missing pieces. "Mixed signals" tells them which part to double-check: the specific gaps. The verdict is a prompt for the next step, not just a label.</li>
<li><strong>The best outcome is deliberately modest.</strong> The strongest state says the COA "passes a basic sanity check". It never says a report is authentic or certified. A COA is documentation for research use, not a guarantee, and the wording is built to say so.</li>
</ul>
<h2>Privacy by design</h2>
<p>The COA Reader runs entirely in the browser. Nothing the user ticks is sent anywhere, and the page says so right above the lists.</p>
<p>That wasn't only a nice-to-have. People check reports for products they're considering, and which lab reports someone is reading is nobody else's business. A checklist made that easy to guarantee. There's no upload, so there's no file to store, scan or leak and no server endpoint to secure.</p>
<p>Compare that with the parser approach. Uploading a PDF means handling untrusted files, storing them at least briefly and writing a privacy policy that explains it all. The checklist removes that whole category of problem.</p>
<h2>What I'd change next</h2>
<p>These are ideas, not shipped features:</p>
<ul>
<li><strong>Explain each flag inline.</strong> A short "what this looks like" note or example image under items like "a chromatogram" would help first-time users.</li>
<li><strong>Make the result shareable without a server.</strong> Encoding the ticked flags in the URL (for example <code>?r=no-lot,reused&amp;g=third-party</code>) would let people share a result while keeping the no-backend design.</li>
<li><strong>Add a "not sure" state.</strong> Some items are hard to judge from a blurry image. A third option would be more honest than forcing a yes or no.</li>
</ul>
<h2>Closing note</h2>
<p>Not every data problem needs extraction. Sometimes the most reliable system is a well-designed set of questions and a person who answers them.</p>
<p>You can read <a href="https://www.peplookup.com/guides/what-is-peplookup">more about the project</a> and <a href="https://peplookup.medium.com/blog-compare-peptide-prices-ac1086a50541">background on how I compare prices</a> on Medium.</p>
<p><em>PepLookup lists products sold by third-party suppliers for laboratory research use only. This post is about data processing and makes no claims about any compound.</em></p>
]]></content:encoded></item></channel></rss>