<?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: harrisgnr</title>
    <description>The latest articles on DEV Community by harrisgnr (@harrisgnr).</description>
    <link>https://dev.to/harrisgnr</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3916465%2F48f0c25d-65aa-4f5d-a40c-e7bf7745dcde.png</url>
      <title>DEV Community: harrisgnr</title>
      <link>https://dev.to/harrisgnr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harrisgnr"/>
    <language>en</language>
    <item>
      <title>You Don't Get to Interview Your Customers: What Corporate Life Teaches You About Distribution (And What It Gets Wrong)</title>
      <dc:creator>harrisgnr</dc:creator>
      <pubDate>Sat, 16 May 2026 18:33:50 +0000</pubDate>
      <link>https://dev.to/harrisgnr/you-dont-get-to-interview-your-customers-what-corporate-life-teaches-you-about-distribution-and-pii</link>
      <guid>https://dev.to/harrisgnr/you-dont-get-to-interview-your-customers-what-corporate-life-teaches-you-about-distribution-and-pii</guid>
      <description>&lt;p&gt;I've spent 15 years inside a large tech company making infrastructure investment decisions worth billions. I've also spent the last year building side projects that struggle to reach their first hundred users.&lt;/p&gt;

&lt;p&gt;The gap between those two realities forced me to ask a question I hadn't considered before: &lt;strong&gt;is distribution actually harder outside companies, or did I just never notice how much help I was getting?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer, I think, is both. And the parallel between how careers grow inside organizations and how products grow in markets is tighter than most people realize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution is the same game everywhere. The medium just changes.
&lt;/h2&gt;

&lt;p&gt;Inside a company, distribution means getting colleagues, leadership, and stakeholders to trust you enough to act on your ideas, fund your work, or sponsor your career. Outside, it means getting strangers to trust you enough to change their behavior.&lt;/p&gt;

&lt;p&gt;Same mechanics. Different currency. And very different levels of institutional support.&lt;/p&gt;

&lt;p&gt;Here's the full map.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO → Owning a domain inside the org
&lt;/h2&gt;

&lt;p&gt;In external distribution, SEO means being the first result when someone searches for a solution to their problem. Internal SEO means being the person whose name comes up when a problem surfaces in your domain.&lt;/p&gt;

&lt;p&gt;"Who knows most about TCO?" If your name is the answer, you have internal SEO. You built it through canonical design docs, postmortems, and strategy memos that got linked in ten other documents. When someone has the problem, they find you without you having to push.&lt;/p&gt;

&lt;p&gt;The difference: &lt;strong&gt;internal SEO is a monopoly game&lt;/strong&gt;. One person owns "cost modeling" in a 10,000-person org. There's no page two. External SEO means competing with the world on every keyword, and the ranking is decided by an algorithm, not by human memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ads → Forced presence
&lt;/h2&gt;

&lt;p&gt;What does advertising look like inside a company? All-hands presentations. Org-wide emails. Volunteering for high-visibility cross-functional programs. Spending political capital to get on leadership agendas.&lt;/p&gt;

&lt;p&gt;These are all presence plays. You're buying attention — not with money, but with time and social currency.&lt;/p&gt;

&lt;p&gt;Here's the critical difference: &lt;strong&gt;your internal audience is captive&lt;/strong&gt;. They can't scroll past your all-hands slide. They can't close the tab on your org-wide email before the subject line registers. The attention floor is nonzero by default.&lt;/p&gt;

&lt;p&gt;External ads have no such floor. The default is zero. You pay to interrupt people who are actively trying not to be interrupted, and the first second of attention is the hardest to earn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Marketing → Design docs that circulate
&lt;/h2&gt;

&lt;p&gt;The internal equivalent of a content marketing flywheel is writing things that travel beyond your team. The strategy memo that gets forwarded. The postmortem that becomes the reference for how the company thinks about a problem. The design doc that gets linked for years.&lt;/p&gt;

&lt;p&gt;The difference: &lt;strong&gt;internal content has a guaranteed distribution floor&lt;/strong&gt; — your immediate team will read it because they're paid to. External content has a floor of zero. You publish and wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Influencer / Sponsorship → The VP who says your name
&lt;/h2&gt;

&lt;p&gt;This is the tightest parallel in the whole map. Externally, influencer marketing means borrowing someone else's audience. A trusted person says "this is worth your attention" and their credibility transfers to your product.&lt;/p&gt;

&lt;p&gt;Internally: a VP mentions your work in a review. Your skip-level puts your name in a room you're not in. A peer with strong internal reputation publicly credits you.&lt;/p&gt;

&lt;p&gt;Same mechanic, both directions. The person doing the endorsing is the scarce resource. Their attention is not for sale. It has to be earned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Physical Presence → Location as distribution surface
&lt;/h2&gt;

&lt;p&gt;This one is underestimated. Being on the same floor as a decision-maker, walking the same hallway, grabbing coffee in the same kitchen, this is ambient brand awareness as a distribution channel.&lt;/p&gt;

&lt;p&gt;The hallway conversation isn't small talk. It's &lt;strong&gt;unscheduled, low-friction exposure to decision-makers at the moment their guard is down&lt;/strong&gt;. You plant ideas without an agenda. You appear in someone's natural path without them having to seek you out.&lt;/p&gt;

&lt;p&gt;That's the internal equivalent of good SEO: present at the moment of need, without the other person having to know you exist first.&lt;/p&gt;

&lt;p&gt;Remote work destroyed a lot of internal distribution precisely because it &lt;strong&gt;collapsed the accidental exposure surface&lt;/strong&gt;. Every hallway conversation became a calendar invite.&lt;/p&gt;

&lt;p&gt;Externally, almost everything is remote by default. There are no hallways. The engineered equivalents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Community participation = designed hallway&lt;/li&gt;
&lt;li&gt;Conference speaking = being flown to HQ
&lt;/li&gt;
&lt;li&gt;Cold DM = scheduling the 1:1 that used to happen by accident&lt;/li&gt;
&lt;li&gt;SEO = the always-on version — you're in the hallway even while you sleep&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Promotion → Moving upmarket / third-party validation
&lt;/h2&gt;

&lt;p&gt;An internal promotion is the institution validating that your distribution has reached a new tier. You get access to bigger rooms, more budget, higher-stakes decisions.&lt;/p&gt;

&lt;p&gt;The rule everyone knows but rarely states: &lt;strong&gt;you have to already be doing the job before you get the title&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Externally, that maps almost exactly to moving upmarket. You can't land enterprise customers by claiming you're enterprise-ready. You need to already be serving someone enterprise-adjacent, then use that logo to unlock the next. The logo is the promo packet.&lt;/p&gt;

&lt;p&gt;Fundraising works the same way. VCs don't promote you into traction , they validate traction that already exists. The pitch deck is the promotion packet. The lead investor's endorsement is the manager's sponsorship.&lt;/p&gt;

&lt;p&gt;The brutal difference: &lt;strong&gt;internal promotions have a floor&lt;/strong&gt;. Your salary continues while you're being evaluated. External validation has no floor. You can be doing the job for two years and the market never gives you the title.&lt;/p&gt;

&lt;h2&gt;
  
  
  Job Transfer → The Pivot
&lt;/h2&gt;

&lt;p&gt;A transfer is a distribution reset with a safety net. You take your seniority, your comp, your institutional credibility — and walk into a new team where your internal distribution is essentially zero. You keep the floor while you rebuild.&lt;/p&gt;

&lt;p&gt;Externally, that's a pivot. Same assets (technical capability, some existing users, product infrastructure), new market. But the safety net is gone.&lt;/p&gt;

&lt;p&gt;Internal transfer worst case: you're a well-paid stranger for six months.&lt;/p&gt;

&lt;p&gt;External pivot worst case: you burn your runway during the rebuild and never recover.&lt;/p&gt;

&lt;p&gt;The mechanics are identical. The exposure is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interview: The Most Favorable Distribution Channel That Will Ever Exist For You
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable one.&lt;/p&gt;

&lt;p&gt;Think about what makes the interview extraordinary as a distribution mechanism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The buyer &lt;strong&gt;signals demand first&lt;/strong&gt;. The job posting exists before you apply. You never cold-call a company that isn't hiring.&lt;/li&gt;
&lt;li&gt;Both sides &lt;strong&gt;show up at a scheduled time&lt;/strong&gt;. The buyer is present and attentive by design.&lt;/li&gt;
&lt;li&gt;The evaluation criteria are &lt;strong&gt;roughly known in advance&lt;/strong&gt;. You can prepare.&lt;/li&gt;
&lt;li&gt;You get &lt;strong&gt;a decision&lt;/strong&gt;. Offer or rejection. Not silence. Not "circle back in Q3."&lt;/li&gt;
&lt;li&gt;The feedback loop is &lt;strong&gt;weeks&lt;/strong&gt;, not months.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The buyer is &lt;strong&gt;motivated&lt;/strong&gt;. They have a role to fill.&lt;br&gt;
Now compare that to selling your product to a stranger:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They didn't post a job. They may not know they have a problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting them on a call is itself a distribution problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The criteria for buying are invisible until you're deep in the process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most never reply. Silence is the default answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The feedback loop is infinite — was it the price, the timing, the product, the email subject line?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Their motivation to evaluate you is zero until you manufacture urgency.&lt;br&gt;
&lt;strong&gt;You don't get to interview your customers.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interview is a sales process that was designed in your favor. Both sides show up. The buyer has declared intent. The process is structured. A decision is guaranteed.&lt;/p&gt;

&lt;p&gt;Everything about external distribution is the opposite of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Reason Distribution Feels So Hard
&lt;/h2&gt;

&lt;p&gt;The reason distribution outside companies feels brutally hard isn't that people leaving corporate lack skill. It's that their entire career trained them inside a system that &lt;strong&gt;absorbed the hardest parts of distribution on their behalf&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inbound demand (headcount, job postings)&lt;/li&gt;
&lt;li&gt;Structured evaluation (known promo criteria, interview rubrics)&lt;/li&gt;
&lt;li&gt;Guaranteed decisions (offer, rejection, promotion outcome)&lt;/li&gt;
&lt;li&gt;Institutional brand amplifying personal credibility&lt;/li&gt;
&lt;li&gt;Captive audiences who can't scroll past&lt;/li&gt;
&lt;li&gt;Physical proximity creating accidental exposure
Going external doesn't mean learning distribution for the first time. It means learning it &lt;strong&gt;without the scaffolding&lt;/strong&gt; for the first time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a different diagnosis than "distribution is hard." It explains why smart, accomplished, operationally excellent people hit the wall.&lt;/p&gt;

&lt;p&gt;The skills transfer. The subsidies don't.&lt;/p&gt;

</description>
      <category>entrepreneurship</category>
      <category>career</category>
      <category>productivity</category>
      <category>startup</category>
    </item>
    <item>
      <title>How I Built an AI Hotel Review Intelligence Platform in a Weekend (Prompts Included)</title>
      <dc:creator>harrisgnr</dc:creator>
      <pubDate>Sat, 16 May 2026 16:25:56 +0000</pubDate>
      <link>https://dev.to/harrisgnr/how-i-built-an-ai-hotel-review-intelligence-platform-in-a-weekend-prompts-included-4fg4</link>
      <guid>https://dev.to/harrisgnr/how-i-built-an-ai-hotel-review-intelligence-platform-in-a-weekend-prompts-included-4fg4</guid>
      <description>&lt;p&gt;Hotel Grande Bretagne in Athens has a 9.3/10 on Booking.com.&lt;/p&gt;

&lt;p&gt;Here's what that score hides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small rooms appear in 22% of reviews across all traveler types. Guests still give 10/10. The pattern is consistent: acknowledge the room, pivot immediately to the Acropolis view to justify the score.&lt;/li&gt;
&lt;li&gt;The rooftop restaurant closed without warning for months in 2024. Zero impact on the overall rating.&lt;/li&gt;
&lt;li&gt;Three specific staff members are named positively across reviews written in English, Turkish, and Polish. One department is carrying the entire experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Star ratings are broken. Not because they're gamed (though they often are), but because averaging thousands of opinions into one number destroys the signal.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://wrongstay.com" rel="noopener noreferrer"&gt;WrongStay&lt;/a&gt; — an AI platform that extracts what's actually in hotel reviews. Persona scores, buried complaints, staff patterns, temporal signals. Athens and Zurich live now, 39 hotels analyzed.&lt;/p&gt;

&lt;p&gt;Here's exactly how I built it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replit&lt;/strong&gt; — development and deployment (Reserved VM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express + TypeScript&lt;/strong&gt; — backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + Vite&lt;/strong&gt; — frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL via Neon&lt;/strong&gt; — database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude API (Sonnet 4)&lt;/strong&gt; — AI synthesis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outscraper&lt;/strong&gt; — Booking.com review collection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serper.dev&lt;/strong&gt; — hotel cover photos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chart.js + react-chartjs-2&lt;/strong&gt; — radar charts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plausible&lt;/strong&gt; — privacy-friendly analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total infrastructure cost to run: under $50/month.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting the Data
&lt;/h2&gt;

&lt;p&gt;The first problem: where do you get hotel reviews without building a scraper?&lt;/p&gt;

&lt;p&gt;I chose Booking.com over TripAdvisor for one specific reason: &lt;strong&gt;verified guests only&lt;/strong&gt;. You can't review a hotel on Booking.com unless you actually booked and stayed there. Less noise.&lt;/p&gt;

&lt;p&gt;Booking.com also pre-labels reviewer type which feeds directly into persona scoring without any NLP.&lt;/p&gt;

&lt;p&gt;And also: they split pros and cons into separate fields. The &lt;code&gt;review_disliked_text&lt;/code&gt; field alone is where most of the real signal lives.&lt;/p&gt;

&lt;p&gt;For data collection I used the Outscraper API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.outscraper.cloud/booking-reviews&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hotelUrls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// up to 1000 in one request&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;f_recent_desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;paramsSerializer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;arrayFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repeat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OUTSCRAPER_API_KEY&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key insight: Outscraper supports batching up to 1000 queries in one request. I submitted all 40 hotel URLs simultaneously, got one job ID, and polled for completion. Total cost for 40 hotels × 200 reviews: &lt;strong&gt;$16&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Database Schema
&lt;/h2&gt;

&lt;p&gt;Five tables: hotels, reviews, synthesis, persona_scores, translations.&lt;/p&gt;

&lt;p&gt;The reviews table captures everything from Outscraper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reviews&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;hotel_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;hotels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;review_liked_text&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;review_disliked_text&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;review_score&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;author_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;author_country&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;review_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;review_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Synthesis Prompt
&lt;/h2&gt;

&lt;p&gt;This is the core of the product. The prompt took multiple iterations to get right. The key insight: &lt;strong&gt;don't ask the AI to summarize. Ask it to find what standard review systems miss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the actual prompt used in production:&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="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hotel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;intelligence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;analyst.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;signals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;guest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reviews&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;standard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;review&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;platforms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;miss.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;respond&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;only.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;preamble,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;explanation,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;markdown.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Analyze&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;these&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hotel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reviews&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ONLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;exact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structure:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"traveler_mismatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Which specific traveler type 
    consistently loves this hotel and which consistently 
    doesn't — and the precise reason why. Be specific, 
    not generic."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"buried_complaint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The complaint that appears in 
    15-40% of disliked fields but that positive 
    reviewers always rationalize or excuse. Name the 
    exact pattern, not just the complaint."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"star_agreement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One specific thing that both 
    low-scoring AND high-scoring reviewers mention, 
    despite disagreeing on everything else."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"temporal_signal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Do reviews from the last 6 
    months differ meaningfully from older ones? What 
    specifically changed and approximately when? If 
    no meaningful change, say so."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"marketing_gap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Something the hotel implies or 
    that positive reviewers praise, that negative 
    reviewers experienced very differently."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"staff_pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Any specific staff role, 
    department, or named individual mentioned 
    repeatedly — positively or negatively."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"verdict_book_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Complete this: Book this hotel 
    if you are [specific traveler type and reason]. 
    Max 20 words."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"verdict_avoid_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Complete this: Avoid this 
    hotel if you are [specific traveler type and 
    reason]. Max 20 words."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"persona_scores"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"persona"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Couples"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A|B|C|D|F"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One specific sentence based on 
        actual review patterns."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"review_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;N&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Couples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Families&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Solo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Travelers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Business&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Foodies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Active/Outdoors&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;persona&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fewer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;than&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reviews,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;score&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reasoning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Insufficient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data'.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Reviews:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;INSERT_REVIEWS_JSON_HERE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instructions that made the biggest difference:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Not just the complaint — the exact pattern"&lt;/strong&gt; forces specificity instead of generalities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"15-40% of disliked fields"&lt;/strong&gt; gives the AI a statistical frame rather than anecdotal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Positive reviewers always rationalize"&lt;/strong&gt; is the mechanism that makes the buried complaint actually useful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letter grades (A/B/C/D/F)&lt;/strong&gt; instead of numbers — they communicate judgment, not false precision&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What the AI Actually Found
&lt;/h2&gt;

&lt;p&gt;Here's a real example from Athens Was Hotel (10/10 on Booking.com):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buried complaint:&lt;/strong&gt; &lt;em&gt;"The split-mattress double bed complaint appears in roughly 20-25% of couple reviews with lower scores, where one mattress sits higher than the other making shared sleeping uncomfortable. Positive reviewers either don't mention it or describe the bed as 'very comfortable' — suggesting room assignment variance. Negative reviewers name it explicitly as a physical discomfort issue, while positive reviewers who got proper beds praise comfort without knowing the problem exists for others."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staff pattern:&lt;/strong&gt; &lt;em&gt;"Anna (breakfast service) is the most frequently and specifically named individual, praised by name across multiple reviews in multiple languages (English, Turkish, Polish) for warmth, language skills, and making the à la carte breakfast easy to navigate. Themis (front desk) and Katerina (restaurant) are also named positively in multiple reviews."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Neither of these appear in the star rating. Both are immediately useful to someone deciding whether to book.&lt;/p&gt;




&lt;h2&gt;
  
  
  Translation Pipeline
&lt;/h2&gt;

&lt;p&gt;After synthesis I translate all 8 text fields into German, French, Spanish, and Italian — one Claude API call returning all four languages simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
Translate all fields below into German (de), 
French (fr), Spanish (es), and Italian (it).

Preserve the specific, analytical tone. Do not 
soften or generalize. Keep named people and 
places in their original form.

Return ONLY this JSON structure, nothing else:
{
  "de": { "traveler_mismatch": "...", ... },
  "fr": { ... },
  "es": { ... },
  "it": { ... }
}

English source:
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;englishFields&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four languages in one API call. Stored in a translations table, fetched via &lt;code&gt;?lang=de&lt;/code&gt; param on the frontend.&lt;/p&gt;




&lt;h2&gt;
  
  
  pSEO Architecture
&lt;/h2&gt;

&lt;p&gt;40 hotels generates a large URL surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/athens                               → city page
/athens/hotel-grande-bretagne         → hotel page
/athens/hotel-grande-bretagne/couples → persona page
/athens/best-hotels-for-couples       → aggregate page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sitemap is fully DB-driven — new hotels appear automatically within 1 hour (cache TTL) of having synthesis run. No manual sitemap edits ever.&lt;/p&gt;

&lt;p&gt;The critical SEO problem with React SPAs: every URL returns the same &lt;code&gt;index.html&lt;/code&gt; to Googlebot, which means every hotel page has identical meta tags.&lt;/p&gt;

&lt;p&gt;Fix: bot detection middleware that returns server-rendered HTML with correct meta tags for crawlers, while regular users still get the React SPA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BOT_AGENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googlebot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bingbot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitterbot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linkedinbot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;facebookexternalhit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;BOT_AGENTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hotel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getHotelWithSynthesis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;renderBotHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REACT_INDEX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each bot response includes correct title, meta description, Hotel schema, and FAQ schema as JSON-LD. Unknown slugs return HTTP 404 with &lt;code&gt;noindex&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Design
&lt;/h2&gt;

&lt;p&gt;I wanted editorial authority, not another AI dashboard.&lt;/p&gt;

&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Playfair Display&lt;/strong&gt; serif for headings — signals trusted publication, not tech startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amber accent (#BA7517)&lt;/strong&gt; — warmer than the typical blue/green SaaS palette&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two dark inverted sections&lt;/strong&gt;: verdict cards and buried complaint spotlight — creates visual hierarchy and makes the intelligence feel premium&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radar chart&lt;/strong&gt; for persona scores — the polygon shape communicates the hotel's personality at a glance before you read anything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bento grid&lt;/strong&gt; for insights — varied card sizes encode importance without explicit ranking&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Admin Pipeline
&lt;/h2&gt;

&lt;p&gt;A single admin page runs the full pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Import → Synthesize → Translate → Photo fetch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the Outscraper API integration, the full pipeline for 40 hotels runs unattended. Server-Sent Events stream progress to the admin UI so I can watch it without the HTTP connection timing out. When done, cache is automatically invalidated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Distribution Strategy
&lt;/h2&gt;

&lt;p&gt;Building was the easy part. Distribution is where most projects die.&lt;/p&gt;

&lt;p&gt;The approach that worked: &lt;strong&gt;lead with the insight, not the product&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The LinkedIn post that drove the first wave of traffic started with the buried complaint finding — not "I built a thing." Nobody clicks "I built a thing." People click specific, surprising data.&lt;/p&gt;

&lt;p&gt;The long game is pSEO. "Is [Hotel Name] good for couples?" has real search volume and almost no competition for AI-synthesized answers. That's 240 pages from 40 hotels, each targeting a specific high-intent query.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the prompt.&lt;/strong&gt; The synthesis prompt is the product. Everything else is infrastructure. Test the AI output quality on day one before building anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the data source immediately.&lt;/strong&gt; I built the full pipeline before verifying the API worked. Backwards. One test call on day one would have saved hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build pSEO from the first commit.&lt;/strong&gt; Adding the prerender middleware after launch is harder than including it from the start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Live
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wrongstay.com" rel="noopener noreferrer"&gt;wrongstay.com&lt;/a&gt; — 39 hotels, Athens and Zurich, free to use.&lt;/p&gt;

&lt;p&gt;The full synthesis prompt is above — adapt it, use it, let me know what patterns you find in your own data.&lt;/p&gt;

&lt;p&gt;What cities should I add next?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Built an Autonomous AI News Platform That Publishes 20 Articles/Day</title>
      <dc:creator>harrisgnr</dc:creator>
      <pubDate>Wed, 06 May 2026 17:49:51 +0000</pubDate>
      <link>https://dev.to/harrisgnr/how-i-built-an-autonomous-ai-news-platform-that-publishes-20-articlesday-nmp</link>
      <guid>https://dev.to/harrisgnr/how-i-built-an-autonomous-ai-news-platform-that-publishes-20-articlesday-nmp</guid>
      <description>&lt;p&gt;I'm a solo developer who built &lt;a href="https://agentnews.gr/en" rel="noopener noreferrer"&gt;AgentNews.gr&lt;/a&gt; — a fully autonomous news platform covering the Greek economy. It publishes ~20 articles per day with zero human intervention. No editors, no manual curation, no daily maintenance.&lt;/p&gt;

&lt;p&gt;In this post I'll walk through the architecture, the agent pipeline, and the hard lessons from building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Greece has massive public economic data — 16,000 government decisions published daily on Diavgeia (the national transparency portal), tax authority announcements on AADE, statistical releases from ELSTAT, stock exchange data from ATHEX. All of it is public. None of it is readable.&lt;/p&gt;

&lt;p&gt;These datasets exist in raw bureaucratic form. Nobody aggregates them. Nobody explains them in plain language. Traditional Greek media covers the big headlines but ignores the long tail of government spending, regulatory changes, and economic data.&lt;/p&gt;

&lt;p&gt;I wanted to build a system that could monitor all of this and turn it into readable news articles — autonomously, 24/7.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The system is a pipeline of specialized AI agents, each handling one step. Think of it as a virtual newsroom where every role is an agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Express + React + PostgreSQL + Google Gemini, running on a single $7/month server.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Scout Agent
&lt;/h3&gt;

&lt;p&gt;The Scout monitors 30+ RSS feeds from Greek financial media, plus direct connections to government databases. Every 2 hours it fetches new content, deduplicates against existing articles, and scores each story for newsworthiness on a 1-10 scale. Only stories scoring 7+ pass through.&lt;/p&gt;

&lt;p&gt;The key design decision: the Scout doesn't write anything. It just produces "leads" — structured objects with the source URL, a topic summary, and a relevance score. This separation means I can swap out scoring logic without touching the rest of the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Social Signal Scouts
&lt;/h3&gt;

&lt;p&gt;Beyond RSS, three signal detectors monitor social platforms for trending economic topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reddit&lt;/strong&gt; — monitors Greek finance subreddits via their free RSS feeds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Trends Greece&lt;/strong&gt; — catches search spikes in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YouTube&lt;/strong&gt; — monitors Greek finance channels and transcribes videos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical rule: social posts are &lt;em&gt;signals&lt;/em&gt;, never &lt;em&gt;sources&lt;/em&gt;. When the Reddit scout detects a trending topic, it finds the actual primary source — the government announcement, the press release, the data release — and writes the article from that. The Reddit post is never mentioned or linked.&lt;/p&gt;

&lt;p&gt;For YouTube, the system fetches auto-generated transcripts and uses them to identify topics. For English-language channels, it translates the key insights into Greek articles with clear attribution to the original creator.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Writer Agent
&lt;/h3&gt;

&lt;p&gt;Takes a lead and produces a full Greek article — 400-800 words, journalistic tone, structured with proper paragraphs. The system prompt is specific: write like a senior Greek financial journalist, cite sources by name, don't editorialize, don't hallucinate.&lt;/p&gt;

&lt;p&gt;Each article gets a transliterated Greek slug, category assignment, and source attribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fact-Checker Agent
&lt;/h3&gt;

&lt;p&gt;Reviews the draft against the original source material. Flags any claims that don't appear in the source. This doesn't catch everything, but it catches the obvious hallucinations — made-up statistics, wrong company names, fabricated quotes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SEO Agent
&lt;/h3&gt;

&lt;p&gt;Generates meta titles, descriptions, keywords, and Open Graph tags. For articles triggered by Google Trends, it optimizes specifically for the trending search term — this is how a new site with low domain authority can compete for timely queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Image Agent
&lt;/h3&gt;

&lt;p&gt;Three-tier system: (1) try to extract the OG image from the source article, (2) search Pexels for a relevant photo, (3) generate with AI. Falls back to a branded gradient if all three fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Publisher Agent
&lt;/h3&gt;

&lt;p&gt;Takes the finished article, transliterates the title into a URL-friendly Greek slug, saves everything to PostgreSQL, and it's live. No human approval step.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Translator Agent
&lt;/h3&gt;

&lt;p&gt;Every 3 hours, picks the top articles and translates them to English at &lt;code&gt;/en/&lt;/code&gt;. This turned out to be more valuable than I expected — the English section gets more organic traffic than Greek because there's less competition for "Greek economy news in English."&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deep Research Agent
&lt;/h3&gt;

&lt;p&gt;Every Sunday, a separate agent synthesizes a full week of government spending data from Diavgeia into a long-form analysis piece with charts. This is the content that's hardest to replicate and most valuable for SEO — nobody else covers weekly patterns in Greek government spending.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Pipeline
&lt;/h2&gt;

&lt;p&gt;The most unique part of the system is the Diavgeia pipeline. Diavgeia is Greece's government transparency portal — every public spending decision, contract, and regulatory act gets published there. That's ~16,000 decisions per day.&lt;/p&gt;

&lt;p&gt;My agent fetches these decisions, scores them for public interest, and produces plain-language Greek articles explaining what happened. "The Ministry of Health awarded a €2.3M contract to Company X for IT systems" is boring as a raw database entry. As a news article with context about the ministry's spending patterns, it becomes interesting.&lt;/p&gt;

&lt;p&gt;This is the data moat. The raw data is public, but the structured intelligence layer on top of it is unique.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerendering for SEO
&lt;/h2&gt;

&lt;p&gt;The frontend is a React SPA. When I launched, Google was seeing empty &lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; pages and barely indexing anything — 57 pages after a month.&lt;/p&gt;

&lt;p&gt;I added a prerender middleware that detects crawler user agents (Googlebot, Bingbot, social media crawlers) and serves fully-rendered HTML with all meta tags, JSON-LD structured data, and article content. Regular users still get the SPA.&lt;/p&gt;

&lt;p&gt;After deploying the prerender, indexed pages jumped from 57 to 1,000+ within weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Distribution is harder than building.&lt;/strong&gt; The platform runs itself. Getting humans to discover it is the actual hard problem. Zero backlinks for the first two months meant Google had no reason to rank me above established competitors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;English content grows faster than Greek.&lt;/strong&gt; Counter-intuitive for a Greek news site, but the English section gets more organic traffic because there are fewer competitors for "Greek economy news in English" than for "ελληνικά οικονομικά νέα."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality threshold matters.&lt;/strong&gt; I initially set the Scout's minimum quality score too low and was publishing 35+ thin articles per day. Raising the threshold to only pass high-scoring stories improved the average quality and Google's willingness to index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent separation principle pays off.&lt;/strong&gt; Every agent does one job. When image generation was causing server hangs, I fixed the Image Agent without touching anything else. When SEO needed improvement, I updated the SEO Agent's prompts. This modularity is the main reason a solo developer can maintain a system this complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;~1,000 pages indexed by Google&lt;/li&gt;
&lt;li&gt;~20 articles/day published autonomously&lt;/li&gt;
&lt;li&gt;30+ RSS sources + Diavgeia + ELSTAT + ATHEX + ECB&lt;/li&gt;
&lt;li&gt;3 social signal sources (Reddit, Google Trends, YouTube)&lt;/li&gt;
&lt;li&gt;Greek + English, fully automated&lt;/li&gt;
&lt;li&gt;Running on a single server, $7/month&lt;/li&gt;
&lt;li&gt;Zero revenue (yet)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Newsletter with weekly digest. B2B licensing of the agent platform for other publishers. More signal sources. And honestly — just letting SEO compound while the system runs itself.&lt;/p&gt;

&lt;p&gt;If you're building with AI agents, I'd love to hear about your architecture. The multi-agent pipeline pattern has worked well for this use case but I'm curious what others are doing differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://agentnews.gr/en" rel="noopener noreferrer"&gt;agentnews.gr&lt;/a&gt; (English) | &lt;a href="https://agentnews.gr" rel="noopener noreferrer"&gt;agentnews.gr&lt;/a&gt; (Greek)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
