<?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: Morten Tandrup</title>
    <description>The latest articles on DEV Community by Morten Tandrup (@morten_tandrup_fa70d3e92c).</description>
    <link>https://dev.to/morten_tandrup_fa70d3e92c</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%2F4053061%2Fdb68c232-e28d-404c-a52b-7ba433bec17b.jpg</url>
      <title>DEV Community: Morten Tandrup</title>
      <link>https://dev.to/morten_tandrup_fa70d3e92c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/morten_tandrup_fa70d3e92c"/>
    <language>en</language>
    <item>
      <title>I built a wine recommendation engine. The hardest part wasn’t AI—it was trusting the data.</title>
      <dc:creator>Morten Tandrup</dc:creator>
      <pubDate>Wed, 29 Jul 2026 11:11:44 +0000</pubDate>
      <link>https://dev.to/morten_tandrup_fa70d3e92c/i-built-a-wine-recommendation-engine-the-hardest-part-wasnt-ai-it-was-trusting-the-data-2j46</link>
      <guid>https://dev.to/morten_tandrup_fa70d3e92c/i-built-a-wine-recommendation-engine-the-hardest-part-wasnt-ai-it-was-trusting-the-data-2j46</guid>
      <description>&lt;p&gt;When I started building my side project, I thought the difficult part would be machine learning.&lt;/p&gt;

&lt;p&gt;I was completely wrong.&lt;/p&gt;

&lt;p&gt;The hard part turned out to be something much less glamorous:&lt;/p&gt;

&lt;p&gt;making sure every single number could be trusted.&lt;/p&gt;

&lt;p&gt;The project&lt;/p&gt;

&lt;p&gt;For the past year I’ve been building Vinradar, a hobby project for Danish wine buyers.&lt;/p&gt;

&lt;p&gt;Every day it scans around 17,500 wine listings from ten online retailers, collects professional critic ratings, tracks historical prices and tries to identify wines that are genuinely good value.&lt;/p&gt;

&lt;p&gt;The stack is intentionally simple.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;SQLite&lt;/li&gt;
&lt;li&gt;Static site generation&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;Cloudflare Pages&lt;/li&gt;
&lt;li&gt;Cloudflare Functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Kubernetes.&lt;br&gt;
No distributed systems.&lt;br&gt;
Just one machine doing scheduled jobs three times a day.&lt;/p&gt;

&lt;p&gt;My first mistake&lt;/p&gt;

&lt;p&gt;Like many developers today, I initially thought:&lt;/p&gt;

&lt;p&gt;“I’ll let an LLM help enrich the data.”&lt;/p&gt;

&lt;p&gt;That lasted about a week.&lt;/p&gt;

&lt;p&gt;The model wrote perfectly convincing vintage descriptions.&lt;/p&gt;

&lt;p&gt;Unfortunately, they occasionally contradicted the actual vintage ratings.&lt;/p&gt;

&lt;p&gt;Later I found something even worse.&lt;/p&gt;

&lt;p&gt;One parsing bug had accidentally shifted a “/100” denominator into the next reviewer’s score.&lt;/p&gt;

&lt;p&gt;Instead of noticing the parser error, the LLM happily echoed the incorrect value.&lt;/p&gt;

&lt;p&gt;A perfectly ordinary 95-point wine suddenly became a fictional 100-point masterpiece.&lt;/p&gt;

&lt;p&gt;The language was beautiful.&lt;/p&gt;

&lt;p&gt;The data was wrong.&lt;/p&gt;

&lt;p&gt;That was enough.&lt;/p&gt;

&lt;p&gt;I deleted every AI-generated numerical enrichment from the project.&lt;/p&gt;

&lt;p&gt;The second surprise&lt;/p&gt;

&lt;p&gt;I also assumed professional wine critics could be calibrated with one correction factor.&lt;/p&gt;

&lt;p&gt;The data disagreed.&lt;/p&gt;

&lt;p&gt;After analysing tens of thousands of ratings I found something unexpected.&lt;/p&gt;

&lt;p&gt;Critic generosity isn’t fixed.&lt;/p&gt;

&lt;p&gt;It depends on the combination of critic and wine region.&lt;/p&gt;

&lt;p&gt;One critic might score Burgundy significantly higher than peers while being almost perfectly aligned in Piedmont.&lt;/p&gt;

&lt;p&gt;That completely changed my scoring model.&lt;/p&gt;

&lt;p&gt;Instead of correcting by critic…&lt;/p&gt;

&lt;p&gt;I now correct by critic × region.&lt;/p&gt;

&lt;p&gt;I also discovered that generosity increases near the top of the scale, so wines rated 96–100 require larger adjustments than wines in the low 90s.&lt;/p&gt;

&lt;p&gt;The rules I eventually adopted&lt;/p&gt;

&lt;p&gt;After a few painful lessons, I ended up with a set of principles that now drive the entire pipeline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every numerical value must come from a verifiable source.&lt;/li&gt;
&lt;li&gt;LLMs may explain data, but they never invent or complete it.&lt;/li&gt;
&lt;li&gt;Missing information is preferable to guessed information.&lt;/li&gt;
&lt;li&gt;Every calculation should be reproducible.&lt;/li&gt;
&lt;li&gt;Known weaknesses should be documented rather than hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules sound obvious.&lt;/p&gt;

&lt;p&gt;I only wrote them after breaking every one of them.&lt;/p&gt;

&lt;p&gt;An unexpected lesson&lt;/p&gt;

&lt;p&gt;Building scrapers wasn’t difficult.&lt;/p&gt;

&lt;p&gt;Building the website wasn’t particularly difficult.&lt;/p&gt;

&lt;p&gt;Even the analytics weren’t the difficult part.&lt;/p&gt;

&lt;p&gt;The difficult part was resisting the temptation to make the data look complete.&lt;/p&gt;

&lt;p&gt;Users forgive missing information.&lt;/p&gt;

&lt;p&gt;They rarely forgive incorrect information.&lt;/p&gt;

&lt;p&gt;I’d rather display an empty field than a plausible lie.&lt;/p&gt;

&lt;p&gt;I’m curious&lt;/p&gt;

&lt;p&gt;Has building your own project fundamentally changed the way you think about data quality?&lt;/p&gt;

&lt;p&gt;Did you end up creating rules you never expected to need?&lt;/p&gt;

&lt;p&gt;I’d love to hear your experiences&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>ai</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
