<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged analytics</title>
<link>https://coderlegion.com/tag/analytics</link>
<description>Powered by Question2Answer</description>
<item>
<title>What Is a Good Conversion Rate? (And Why the Average Can Fool You)</title>
<link>https://coderlegion.com/33227/what-is-a-good-conversion-rate-and-why-the-average-can-fool-you</link>
<description>&lt;p&gt;We build Opti-Behavior, the WordPress plugin mentioned in this guide.&lt;/p&gt;
&lt;p&gt;Imagine Nadia. She sells online yoga classes. Last month, 3,000 people visited her sales page and 45 bought a class. She typed &quot;what is a good conversion rate&quot; into Google and found an article that said &quot;the average is 2.35%&quot;. Hers was 1.5%. She felt terrible.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Nadia is an example: her site and figures are made up to show the method step by step.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then she found another article that said &quot;a good conversion rate is 10%&quot;. Now she felt even worse.&lt;/p&gt;
&lt;p&gt;Here is the truth: both articles were talking about other people’s websites. A &lt;strong&gt;good conversion rate&lt;/strong&gt; depends on what you sell, who visits, and what you count as a conversion. In this guide we will work out a good rate for Nadia’s site, step by step, and then find ways to raise it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; A good conversion rate is one that is better than your own rate last month, measured the same way. Industry averages are rough hints, not goals. Measure one clear action, per visitor, for each type of page, and improve the weakest step first.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-conversion-exactly-&quot;&gt;What is a conversion, exactly?&lt;/h3&gt;
&lt;p&gt;A conversion is when a visitor does the thing you want. It can be big or small:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Type &lt;/th&gt;&lt;th&gt;Examples&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Big (macro) conversion &lt;/td&gt;&lt;td&gt;A purchase, a paid sign-up, a booked call, a quote request&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Small (micro) conversion &lt;/td&gt;&lt;td&gt;Adding to cart, starting a form, signing up to a newsletter, watching a demo&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Small conversions matter because they lead to big ones. If nobody adds to cart, nobody buys.&lt;/p&gt;
&lt;h3 id=&quot;how-to-calculate-your-conversion-rate&quot;&gt;How to calculate your conversion rate&lt;/h3&gt;
&lt;p&gt;The formula is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conversion rate = conversions ÷ visitors × 100&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Nadia: 45 purchases ÷ 3,000 visitors × 100 = &lt;strong&gt;1.5%&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But watch out. Tools do not all divide by the same thing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per visitor:&lt;/strong&gt; 45 buyers out of 3,000 people. This is the honest one for sales.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per session:&lt;/strong&gt; 45 buyers out of 4,200 visits. The rate looks lower, because many people visit twice before buying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per page:&lt;/strong&gt; conversions on one page divided by that page’s visitors. Best for comparing pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you compare numbers, always use the same method. Many &quot;benchmark&quot; articles do not say which one they used.&lt;/p&gt;
&lt;h3 id=&quot;so-what-is-a-good-conversion-rate-&quot;&gt;So what is a good conversion rate?&lt;/h3&gt;
&lt;p&gt;Here are ranges you often see quoted in marketing reports. They come from different studies and change every year, so treat them as rough hints:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Type of page or site &lt;/th&gt;&lt;th&gt;Often quoted range&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Online shop (all visitors) &lt;/td&gt;&lt;td&gt;1% to 3%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Shop, visitors who add to cart &lt;/td&gt;&lt;td&gt;30% to 60% reach checkout&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Lead form on a service site &lt;/td&gt;&lt;td&gt;2% to 5%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dedicated landing page from an ad &lt;/td&gt;&lt;td&gt;5% to 15%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Newsletter sign-up box on a blog &lt;/td&gt;&lt;td&gt;1% to 3%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Free trial sign-up (software) &lt;/td&gt;&lt;td&gt;3% to 10%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Why are the ranges so wide? Because a &quot;good&quot; rate depends on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Price.&lt;/strong&gt; A $5 ebook converts more than a $900 course.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traffic source.&lt;/strong&gt; Visitors from your newsletter already trust you. Visitors from a cold ad do not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device.&lt;/strong&gt; Mobile visitors often browse and buy later on a computer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intent.&lt;/strong&gt; Someone searching &quot;buy yoga class online&quot; is ready. Someone searching &quot;is yoga good for back pain&quot; is learning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So Nadia’s 1.5% was not bad for a paid course with mostly cold traffic. The real question was: &lt;strong&gt;which part of her journey was weak?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;split-the-rate-into-steps&quot;&gt;Split the rate into steps&lt;/h3&gt;
&lt;p&gt;One big conversion rate hides where the problem is. Split the path into steps. This is called a &lt;strong&gt;funnel&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Nadia’s path was: Sales page, Pricing, Checkout, Thank-you page. In Opti-Behavior, you can create this funnel in a minute: go to &lt;strong&gt;Opti-Behavior &amp;gt; Funnels&lt;/strong&gt;, click &lt;strong&gt;Build New Funnel&lt;/strong&gt;, and add each step with a URL rule, like &quot;URL Contains /pricing/&quot;. Opti-Behavior also suggests funnels built from the pages of your site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://optiuser.com/wp-content/uploads/2026/09/docs-funnels-page.webp&quot; alt=&quot;Good conversion rate: funnel showing step-by-step conversion rate&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Each step shows how many visitors continue and how many leave.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here is what Nadia’s funnel showed:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Step &lt;/th&gt;&lt;th&gt;Visitors &lt;/th&gt;&lt;th&gt;Continue to next step&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Sales page &lt;/td&gt;&lt;td&gt;3,000 &lt;/td&gt;&lt;td&gt;28%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pricing &lt;/td&gt;&lt;td&gt;840 &lt;/td&gt;&lt;td&gt;22%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Checkout &lt;/td&gt;&lt;td&gt;185 &lt;/td&gt;&lt;td&gt;24%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Thank-you &lt;/td&gt;&lt;td&gt;45 &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The big leak was at checkout: 185 people started to pay, only 45 finished. That is 76% lost at the very last step. Her sales page was fine. Her checkout was not.&lt;/p&gt;
&lt;p&gt;With Pro, each funnel step links to the recordings of the people who left right there. Nadia watched eight of them. On phones, her checkout asked people to create an account with a password before paying. Most gave up at the password field.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://optiuser.com/wp-content/uploads/2026/06/docs-funnel-dropoff-replays.webp&quot; alt=&quot;Watch recordings of visitors who left a funnel step&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In Pro, each step links to the recordings of the visitors who dropped there.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;measure-what-counts-as-a-win&quot;&gt;Measure what counts as a win&lt;/h3&gt;
&lt;p&gt;When you change something, you want to know if it worked. That is what A/B tests are for: half your visitors see the old version, half see the new one, at the same time.&lt;/p&gt;
&lt;p&gt;In Opti-Behavior, a test has goals. The first goal is the one that picks the winner.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Goal &lt;/th&gt;&lt;th&gt;Counts a conversion when &lt;/th&gt;&lt;th&gt;Plan&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Page visit &lt;/td&gt;&lt;td&gt;The visitor reaches a page, like a thank-you page &lt;/td&gt;&lt;td&gt;Free&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Click &lt;/td&gt;&lt;td&gt;The visitor clicks an element you pick on the page &lt;/td&gt;&lt;td&gt;Free&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Form submit &lt;/td&gt;&lt;td&gt;A form on the page is sent &lt;/td&gt;&lt;td&gt;Free&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Scroll depth &lt;/td&gt;&lt;td&gt;The visitor scrolls to a depth you set &lt;/td&gt;&lt;td&gt;Pro&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Time on page &lt;/td&gt;&lt;td&gt;The visit lasts longer than a threshold &lt;/td&gt;&lt;td&gt;Pro&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Revenue &lt;/td&gt;&lt;td&gt;WooCommerce orders from the test &lt;/td&gt;&lt;td&gt;Pro&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://optiuser.com/wp-content/uploads/2026/06/docs-ab-wizard-goals.webp&quot; alt=&quot;Choosing a conversion goal for an A/B test&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pick the action that counts as a conversion. The first goal decides the winner.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The free version runs up to 3 tests at the same time, with up to 3 versions each.&lt;/p&gt;
&lt;h3 id=&quot;careful-a-higher-conversion-rate-is-not-always-better&quot;&gt;Careful: a higher conversion rate is not always better&lt;/h3&gt;
&lt;p&gt;This surprises a lot of people. Look at this real-style example from our A/B testing docs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Version &lt;/th&gt;&lt;th&gt;Visitors &lt;/th&gt;&lt;th&gt;Orders &lt;/th&gt;&lt;th&gt;Conversion rate &lt;/th&gt;&lt;th&gt;Average order &lt;/th&gt;&lt;th&gt;Revenue per visitor&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;A (current) &lt;/td&gt;&lt;td&gt;1,000 &lt;/td&gt;&lt;td&gt;30 &lt;/td&gt;&lt;td&gt;3.0% &lt;/td&gt;&lt;td&gt;60 € &lt;/td&gt;&lt;td&gt;1.80 €&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;B (free shipping over 50 €) &lt;/td&gt;&lt;td&gt;1,000 &lt;/td&gt;&lt;td&gt;27 &lt;/td&gt;&lt;td&gt;2.7% &lt;/td&gt;&lt;td&gt;78 € &lt;/td&gt;&lt;td&gt;2.11 €&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Version B has a &lt;strong&gt;lower&lt;/strong&gt; conversion rate, but people spend more, so it earns 17% more per visitor. If you only looked at the conversion rate, you would pick the wrong version. For shops, watch &lt;strong&gt;revenue per visitor&lt;/strong&gt; too. Pro can measure it with a Revenue goal.&lt;/p&gt;
&lt;h3 id=&quot;know-what-one-conversion-is-worth&quot;&gt;Know what one conversion is worth&lt;/h3&gt;
&lt;p&gt;Once you know the value of one conversion, every leak has a price. In Smart Insights settings, you can type the value of one conversion (Free and Pro). Smart Insights then estimates what a problem costs you. With Pro, WooCommerce orders set that value for you.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://optiuser.com/wp-content/uploads/2026/09/docs-si-settings-conversion-value.webp&quot; alt=&quot;Setting the value of one conversion in Smart Insights&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Type the value of one conversion, or, with Pro, let WooCommerce orders price the losses.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For Nadia, one class was worth 60 €. Losing 140 people at checkout every month was not &quot;a bad conversion rate&quot;. It was up to 8,400 € a month walking away. That made the fix very easy to prioritize.&lt;/p&gt;
&lt;h3 id=&quot;8-ways-to-raise-your-conversion-rate&quot;&gt;8 ways to raise your conversion rate&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fix the weakest step first.&lt;/strong&gt; A 10% gain on your worst step beats a 10% gain on your best one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove forced account creation.&lt;/strong&gt; Let people buy as guests. Offer an account after payment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Show the price and what is included early.&lt;/strong&gt; Surprises at checkout kill sales.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make the main button obvious.&lt;/strong&gt; One clear action per page, visible without scrolling on mobile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Answer doubts next to the button.&lt;/strong&gt; Refunds, delivery time, support. One line each.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed up mobile.&lt;/strong&gt; A slow checkout on a phone loses people fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check for broken things.&lt;/strong&gt; Rage clicks and JavaScript errors on a checkout page are pure lost money. Read our guide on &lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/blog/rage-click-dead-click-detection-wordpress/&quot;&gt;rage clicks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test, do not guess.&lt;/strong&gt; Run an A/B test on one change at a time. Our WordPress A/B testing guide shows how.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;nadia-s-new-conversion-rate&quot;&gt;Nadia’s new conversion rate&lt;/h3&gt;
&lt;p&gt;Nadia allowed guest checkout and moved the account creation to the thank-you page. Six weeks later, 44% of people who reached checkout finished paying, instead of 24%. Her overall conversion rate went from 1.5% to 2.7%, with the same traffic.&lt;/p&gt;
&lt;p&gt;She still does not know if 2.7% is &quot;good&quot; for the whole yoga industry. She does not care anymore. It is &lt;strong&gt;better than last month&lt;/strong&gt;, and she knows exactly which step to work on next.&lt;/p&gt;
&lt;h4 id=&quot;find-your-weakest-conversion-step&quot;&gt;Find your weakest conversion step&lt;/h4&gt;
&lt;p&gt;Opti-Behavior is free: funnels, click heatmaps and A/B tests on WordPress. Pro adds drop-off recordings and revenue goals, with a 6-month free trial.&lt;/p&gt;
&lt;h3 id=&quot;good-conversion-rate-faq&quot;&gt;Good conversion rate FAQ&lt;/h3&gt;
&lt;h4 id=&quot;what-is-a-good-conversion-rate-&quot;&gt;What is a good conversion rate?&lt;/h4&gt;
&lt;p&gt;It depends on your site, price and traffic. Rough ranges often quoted are 1% to 3% for online shops, 2% to 5% for lead forms and 5% to 15% for focused landing pages. The best benchmark is your own rate last month, measured the same way.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-calculate-my-conversion-rate-&quot;&gt;How do I calculate my conversion rate?&lt;/h4&gt;
&lt;p&gt;Divide the number of conversions by the number of visitors and multiply by 100. For example, 45 sales from 3,000 visitors is a 1.5% conversion rate.&lt;/p&gt;
&lt;h4 id=&quot;is-a-2-conversion-rate-good-&quot;&gt;Is a 2% conversion rate good?&lt;/h4&gt;
&lt;p&gt;For an online shop with mixed traffic, 2% is often considered solid. For a landing page that only receives ad visitors, it may be low.&lt;/p&gt;
&lt;h4 id=&quot;why-is-my-conversion-rate-low-&quot;&gt;Why is my conversion rate low?&lt;/h4&gt;
&lt;p&gt;Usually one step loses most people: an unclear offer, a hidden button, a surprise cost, a long form or a broken checkout. A funnel shows which step, and recordings show why.&lt;/p&gt;
&lt;h4 id=&quot;should-i-trust-average-conversion-rate-benchmarks-&quot;&gt;Should I trust average conversion rate benchmarks?&lt;/h4&gt;
&lt;p&gt;Only as a rough hint. Studies measure different sites in different ways. Track your own rate per page and per step, and try to beat it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Opti-Behavior Team&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Full guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/go/coderlegion-conversion-rate&quot;&gt;https://optiuser.com/go/coderlegion-conversion-rate&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Drafted with AI assistance, reviewed by the Opti-Behavior Team.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33227/what-is-a-good-conversion-rate-and-why-the-average-can-fool-you</guid>
<pubDate>Sat, 31 Oct 2026 13:43:31 +0000</pubDate>
</item>
<item>
<title>LifeOps Project (Part 6): 360° Analytics Dashboard with Multi-Dimensional Filtering, Recharts and Co</title>
<link>https://coderlegion.com/32785/lifeops-project-part-360-analytics-dashboard-with-multi-dimensional-filtering-recharts</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.datalaria.com/en/posts/app-lifeops_part5_deploy_mobile_pwa/&quot;&gt;Part 5 of our engineering series&lt;/a&gt;, we achieved a major milestone: deploying the entire &lt;strong&gt;LifeOps architecture 24/7 in production at zero cost ($0/month)&lt;/strong&gt; by combining Netlify, Render, and Supabase, optimizing mobile ergonomics for smartphones, and installing the app as a native PWA.&lt;/p&gt;
&lt;p&gt;However, once you start logging dozens of daily activities (workouts, bike rides, books read, movies watched, and Kanban tasks), a critical analytical challenge emerges: &lt;strong&gt;data without analysis lacks operational value&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Having 40 or 50 records in a PostgreSQL database is of little use if opening the dashboard doesn't immediately answer operational questions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;“How many kilometers did I run this month compared to last month?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“What percentage of my training went into sprint intervals versus long endurance runs?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“How has my daily consistency evolved, and what is my current active day streak?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“How can I filter exclusively by Running to inspect average pace and calories burned without mixing in gym workouts or movies?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this sixth installment, we tackle the full evolution of the &lt;strong&gt;360° Analytics Dashboard&lt;/strong&gt;, addressing both the deterministic client-side aggregation design and the most notorious UX challenge of zero-cost serverless infrastructure: &lt;strong&gt;cloud container sleep periods (cold-starts)&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Try the analytics live&lt;/strong&gt;: You can test the new 360° Dashboard with interactive data in Sandbox mode at &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/#/?mock=true&amp;amp;lang=en&quot;&gt;https://datalaria.com/apps/lifeops/#/?mock=true&amp;amp;lang=en&lt;/a&gt; or log in with your Supabase account at &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/#/?lang=en&quot;&gt;https://datalaria.com/apps/lifeops/#/?lang=en&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;1-architecture-decision-external-bi-grafana-metabase-vs-native-react-engine-adr-013-&quot;&gt;1. Architecture Decision: External BI (Grafana / Metabase) vs. Native React Engine (ADR-013) ⚖️&lt;/h4&gt;
&lt;p&gt;When planning advanced charts and timeframe filters, the first engineering instinct is often to reach for established Business Intelligence tools like &lt;strong&gt;Grafana&lt;/strong&gt;, &lt;strong&gt;Metabase&lt;/strong&gt;, or &lt;strong&gt;Apache Superset&lt;/strong&gt;. However, evaluating our requirements against our project constraints led to architectural decision &lt;strong&gt;ADR-013&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Evaluation Factor &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;External BI Tool (Grafana / Metabase) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Native React 19 + Recharts 3.10 Engine (Our Choice)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Multi-Tenant Security (RLS)&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;❌ Requires static direct database connection. Passing individual Supabase Auth JWTs requires expensive Enterprise licenses or custom proxy tokens. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ &lt;strong&gt;Perfect Isolation&lt;/strong&gt;: consumes secure user-scoped REST endpoints protected by Row-Level Security (&lt;code&gt;auth.uid() = user_id&lt;/code&gt;).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Infrastructure Cost&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;❌ Requires a separate container running 24/7 with dedicated RAM, or paid cloud plans. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ &lt;strong&gt;$0.00/month&lt;/strong&gt;: compiles directly into the Netlify static bundle.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sandbox Demo Mode (&lt;code&gt;?mock=true&lt;/code&gt;)&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;❌ Impossible to feed an external iframe using in-memory mock state. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ &lt;strong&gt;100% Compatibility&lt;/strong&gt;: runs instantly with deterministic synthetic data.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Filter Latency&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;⚠️ Every click triggers a remote SQL round-trip to the cloud. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ &lt;strong&gt;0 ms&lt;/strong&gt;: the timeframe dataset filters in client memory using pure &lt;code&gt;useMemo&lt;/code&gt; hooks.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Mobile UX &amp;amp; Aesthetics&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;⚠️ Visual disconnect from &lt;em&gt;Glassmorphism Dark Mode&lt;/em&gt; and nested iframe scroll friction on mobile. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;✅ &lt;strong&gt;Unified Design&lt;/strong&gt;: seamless responsive layout tailored to our design system.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h5 id=&quot;data-flow-architecture&quot;&gt;Data Flow Architecture&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API Layer&lt;/strong&gt;: FastAPI REST API / Supabase PostgreSQL (&lt;code&gt;GET /api/v1/activities/details?limit=200&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local Cache&lt;/strong&gt;: Stale-While-Revalidate (SWR) cache in &lt;code&gt;localStorage&lt;/code&gt; (&lt;code&gt;lifeops_swr_v1:&amp;lt;user_id&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client Engine&lt;/strong&gt;: Deterministic Client Analytics Engine (&lt;code&gt;lib/analyticsEngine.js&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control Bar&lt;/strong&gt;: Timeframes (7D, 30D, Month, 90D, Year), period comparisons (&lt;code&gt;vs. prev.&lt;/code&gt;), and category/subcategory drill-downs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual Suite&lt;/strong&gt;: 4 dynamic KPI cards with &lt;code&gt;Δ%&lt;/code&gt;, multi-metric &lt;code&gt;ComposedChart&lt;/code&gt; time-series, &lt;code&gt;PieChart&lt;/code&gt; donut breakdowns, and a 16-week consistency heatmap.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-multi-dimensional-cascading-filters-amp-period-over-period-deltas-&quot;&gt;2. Multi-Dimensional Cascading Filters &amp;amp; Period-over-Period Deltas ⏱️📊&lt;/h4&gt;
&lt;p&gt;The cornerstone of the new experience is the &lt;strong&gt;Analytics Control Bar&lt;/strong&gt;, enabling three levels of analytical exploration without page reloads:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.datalaria.com/en/posts/app-lifeops_part6_analytics_dashboard_360/lifeops_analytics_filters_drilldown_en.png&quot; alt=&quot;Analytics Control Bar in LifeOps&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;a-timeframe-resolution&quot;&gt;A. Timeframe Resolution&lt;/h5&gt;
&lt;p&gt;The engine supports five time horizons powered by the deterministic &lt;code&gt;resolveTimeWindows()&lt;/code&gt; function:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;7 Days&lt;/code&gt; (&lt;code&gt;week&lt;/code&gt;)&lt;/strong&gt;: Rolling last 7 days vs. immediately preceding 7 days.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;30 Days&lt;/code&gt; (&lt;code&gt;month&lt;/code&gt;)&lt;/strong&gt;: Rolling last 30 days vs. immediately preceding 30 days.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;By Month&lt;/code&gt; (&lt;code&gt;calendar_month&lt;/code&gt;)&lt;/strong&gt;: Dynamic month selector (&lt;code&gt;January&lt;/code&gt; to &lt;code&gt;December&lt;/code&gt;) and year, compared against the preceding calendar month.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;90 Days&lt;/code&gt; (&lt;code&gt;quarter&lt;/code&gt;)&lt;/strong&gt;: Rolling last 90 days aggregated into weekly buckets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;By Year&lt;/code&gt; (&lt;code&gt;year&lt;/code&gt;)&lt;/strong&gt;: Full annual overview with 12 monthly buckets vs. prior year.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;b-invariant-inv-8-safe-percentage-delta-calculation-&quot;&gt;B. Invariant INV-8: Safe Percentage Delta Calculation (Δ%)&lt;/h5&gt;
&lt;p&gt;To compare current performance (&lt;code&gt;V_curr&lt;/code&gt;) against the previous period (&lt;code&gt;V_prev&lt;/code&gt;) without division-by-zero errors when starting a new discipline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;V_prev = 0&lt;/code&gt; and &lt;code&gt;V_curr = 0&lt;/code&gt; → &lt;code&gt;Δ% = 0%&lt;/code&gt; (Stable).&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;V_prev = 0&lt;/code&gt; and &lt;code&gt;V_curr &amp;gt; 0&lt;/code&gt; → &lt;code&gt;Δ% = +100%&lt;/code&gt; (Baseline creation).&lt;/li&gt;
&lt;li&gt;In any other case → &lt;code&gt;Δ% = round(((V_curr - V_prev) / V_prev) * 100)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function computeDelta(currentVal, prevVal, unit = '', language = 'en') {
  const curr = Number(currentVal) || 0;
  const prev = Number(prevVal) || 0;
  const vsLabel = language === 'es' ? 'vs. ant.' : 'vs. prev.';

  if (prev === 0 &amp;amp;&amp;amp; curr === 0) {
    return { positive: true, deltaPct: 0, text: 'Stable (0% vs. prev.)' };
  }
  if (prev === 0 &amp;amp;&amp;amp; curr &amp;gt; 0) {
    return { positive: true, deltaPct: 100, text: `+100% (+${curr}${unit} ${vsLabel})` };
  }

  const diff = curr - prev;
  const pct = Math.round((diff / prev) * 100);
  const sign = diff &amp;gt;= 0 ? '+' : '';
  return {
    positive: diff &amp;gt;= 0,
    deltaPct: pct,
    diff,
    text: `${sign}${pct}% (${sign}${diff.toFixed(1)}${unit} ${vsLabel})`,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-the-visual-suite-with-recharts-contextual-kpis-time-series-amp-donut-charts-&quot;&gt;3. The Visual Suite with Recharts: Contextual KPIs, Time-Series &amp;amp; Donut Charts 📈🍩&lt;/h4&gt;
&lt;p&gt;Selecting a category or drilling down into a subcategory (for example, filtering by &lt;strong&gt;&lt;code&gt;Sport&lt;/code&gt; ➔ &lt;code&gt;Running&lt;/code&gt;&lt;/strong&gt;) dynamically updates the entire dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.datalaria.com/en/posts/app-lifeops_part6_analytics_dashboard_360/lifeops_dashboard_360_analytics_en.png&quot; alt=&quot;360 Analytics Dashboard View&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;4 Contextual KPI Cards&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;Sport&lt;/strong&gt;: adapt metrics to the selected discipline:&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Sessions&lt;/em&gt;: workout count with &lt;code&gt;Δ%&lt;/code&gt; badge and personal bests (PB).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Distance&lt;/em&gt;: cumulative mileage and average km per session.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Time &amp;amp; Pace&lt;/em&gt;: active duration and average pace (&lt;code&gt;min/km&lt;/code&gt; in running or &lt;code&gt;km/h&lt;/code&gt; in cycling).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Calories &amp;amp; Effort&lt;/em&gt;: calories burned, average heart rate (&lt;code&gt;bpm&lt;/code&gt;), and elevation gain (&lt;code&gt;+m&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-Metric Time-Series Chart (&lt;code&gt;ComposedChart&lt;/code&gt;)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;Renders current period bars color-coded by category and overlays a &lt;strong&gt;dashed trendline of the previous period&lt;/strong&gt; when the &lt;code&gt;⇄ Compare vs. prev.&lt;/code&gt; toggle is active.&lt;/li&gt;
&lt;li&gt;Built-in metric switcher: toggle on the fly between &lt;strong&gt;Sessions&lt;/strong&gt;, &lt;strong&gt;Distance (km)&lt;/strong&gt;, &lt;strong&gt;Calories (kcal)&lt;/strong&gt;, &lt;strong&gt;Duration (h)&lt;/strong&gt;, or &lt;strong&gt;Elevation Gain (m)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subcategory Distribution (&lt;code&gt;PieChart&lt;/code&gt; Donut)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;Displays percentage and absolute shares for each activity type. Clicking any slice or legend item applies an automatic &lt;em&gt;drill-down&lt;/em&gt; into that subcategory.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-16-week-consistency-daily-heatmap-strava-github-style-&quot;&gt;4. 16-Week Consistency Daily Heatmap (Strava / GitHub Style) 🔥🗓️&lt;/h4&gt;
&lt;p&gt;Habit formation and athletic progression rely on consistency. To provide clear visual accountability, we built a 112-day grid (16 weeks × 7 days, Monday to Sunday):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.datalaria.com/en/posts/app-lifeops_part6_analytics_dashboard_360/lifeops_consistency_heatmap_streaks_en.png&quot; alt=&quot;Daily Consistency Heatmap&quot;&gt;&lt;/p&gt;
&lt;p&gt;The algorithm evaluates daily activity volume across 5 intensity tiers (&lt;code&gt;level-0&lt;/code&gt; to &lt;code&gt;level-4&lt;/code&gt;) and computes in real time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Active days&lt;/strong&gt; during the evaluated window.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Current active streak&lt;/strong&gt;: counting consecutive active days backwards from today or yesterday.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All-time best streak&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-overcoming-the-free-tier-cloud-ux-challenge-cold-start-mitigation-&quot;&gt;5. Overcoming the Free-Tier Cloud UX Challenge: Cold-Start Mitigation ⚡🛡️&lt;/h4&gt;
&lt;p&gt;Deploying full-stack web applications on free cloud tiers (such as Render.com's Free Web Service) introduces an unavoidable reality: &lt;strong&gt;the container sleeps after 15 minutes of inactivity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When a user visits after several hours, the initial request takes &lt;strong&gt;20 to 40 seconds&lt;/strong&gt; while the virtual machine boots. In early versions, this lag produced a terrible UX: users saw blank charts and KPI cards showing &lt;code&gt;0&lt;/code&gt;, leading them to believe &lt;strong&gt;their data was lost or deleted&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To eliminate this friction while staying strictly within our &lt;strong&gt;$0.00/month budget&lt;/strong&gt;, we engineered a 3-layer mitigation system:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.datalaria.com/en/posts/app-lifeops_part6_analytics_dashboard_360/lifeops_cloud_sync_banner_coldstart_en.png&quot; alt=&quot;Cloud Synchronization Banner &amp;amp; Cold Start Feedback&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;layer-1-silent-pre-warming-wakeupserver-&quot;&gt;Layer 1: Silent Pre-Warming (&lt;code&gt;wakeUpServer&lt;/code&gt;)&lt;/h5&gt;
&lt;p&gt;The moment the browser loads the SPA inside &lt;code&gt;AuthContext.jsx&lt;/code&gt;—before the user even finishes entering credentials or navigating to the Dashboard—a non-blocking background &lt;code&gt;fetch&lt;/code&gt; pings &lt;code&gt;/health&lt;/code&gt;. This gives the container a 5-to-10 second head start to spin up.&lt;/p&gt;
&lt;h5 id=&quot;layer-2-user-scoped-stale-while-revalidate-swr-local-cache&quot;&gt;Layer 2: User-Scoped Stale-While-Revalidate (SWR) Local Cache&lt;/h5&gt;
&lt;p&gt;Every read request (&lt;code&gt;GET /api/v1/activities/details&lt;/code&gt;, &lt;code&gt;/tasks&lt;/code&gt;, &lt;code&gt;/stats/dashboard&lt;/code&gt;) is cached in &lt;code&gt;localStorage&lt;/code&gt; under a strict user-prefixed key (&lt;code&gt;lifeops_swr_v1:&amp;lt;user_id&amp;gt;:&amp;lt;endpoint&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On subsequent visits, cards and charts hydrate &lt;strong&gt;in under 10 milliseconds (effectively 0 ms)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The user sees their data immediately while background network calls fetch fresh updates.&lt;/li&gt;
&lt;li&gt;Any write operation (&lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;) automatically invalidates the local cache to guarantee consistency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;layer-3-foreground-synchronization-banner-and-skeletons&quot;&gt;Layer 3: Foreground Synchronization Banner and Skeletons&lt;/h5&gt;
&lt;p&gt;If a user accesses the app from a clean browser and must wait through a cold start:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Never show misleading zeros&lt;/strong&gt;: KPI cards display waiting indicators (&lt;code&gt;···&lt;/code&gt;) instead of &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transparent Status Banner&lt;/strong&gt;: displays an animated spinner, live elapsed seconds counter (&lt;code&gt;0s, 1s, 2s...&lt;/code&gt;), visual progress bar, and active phase badges:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;✓ Authenticated Session (Supabase Auth)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;⟳ Waking API Engine on Render Cloud&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;⏳ Hydrating Activities &amp;amp; 360° Analytics&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If the wait exceeds 4 seconds, the banner explicitly explains that the free cloud container is spinning up and that &lt;strong&gt;all data is 100% safe&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Timeframe Fallback&lt;/strong&gt;: if the query returns activities but none fall within the last 30 days (e.g. after a vacation), the dashboard automatically switches to &lt;code&gt;90 Days&lt;/code&gt; or &lt;code&gt;By Year&lt;/code&gt; so charts appear populated immediately.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-conclusions-amp-links-&quot;&gt;6. Conclusions &amp;amp; Links 🚀&lt;/h4&gt;
&lt;p&gt;With this update, LifeOps evolves from a plain activity logger into a &lt;strong&gt;full personal operations intelligence system&lt;/strong&gt;. Combining deterministic client-side analytics with a resilient cold-start mitigation architecture proves that it is entirely possible to build fast, secure, and visually stunning software on a &lt;strong&gt;$0.00/month infrastructure bill&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🎮 &lt;strong&gt;Deployed Web App&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/&quot;&gt;https://datalaria.com/apps/lifeops/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🕹️ &lt;strong&gt;Interactive Sandbox Mode (No Registration)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/lifeops/#/?mock=true&amp;amp;lang=en&quot;&gt;https://datalaria.com/apps/lifeops/#/?mock=true&amp;amp;lang=en&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📋 &lt;strong&gt;Official LifeOps Technical Overview&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/apps/lifeops/&quot;&gt;https://datalaria.com/en/apps/lifeops/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If you enjoyed this technical breakdown, explore more open engineering articles and quantitative apps at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.datalaria.com&quot;&gt;www.datalaria.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32785/lifeops-project-part-360-analytics-dashboard-with-multi-dimensional-filtering-recharts</guid>
<pubDate>Wed, 28 Oct 2026 08:18:20 +0000</pubDate>
</item>
<item>
<title>What Is a Good Bounce Rate? Ranges by Page Type and When to Worry</title>
<link>https://coderlegion.com/32690/what-is-a-good-bounce-rate-ranges-by-page-type-and-when-to-worry</link>
<description>&lt;p&gt;We build Opti-Behavior, the WordPress plugin mentioned in this guide.&lt;/p&gt;
&lt;p&gt;You open your analytics. Bounce rate: 68%. Is that a good bounce rate, and how do you reduce bounce rate when it is not?&lt;/p&gt;
&lt;p&gt;The honest answer: &lt;strong&gt;it depends on the page&lt;/strong&gt;. A blog post and a checkout page have very different jobs. A bounce rate that is great for one is terrible for the other.&lt;/p&gt;
&lt;p&gt;This guide explains &lt;strong&gt;what a good bounce rate is&lt;/strong&gt; for each kind of page, why a high number is sometimes fine, and how to find the real reason people leave.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-bounce-exactly-&quot;&gt;What is a bounce, exactly?&lt;/h3&gt;
&lt;p&gt;In simple words, a bounce is a visit where the person arrives and leaves without doing much.&lt;/p&gt;
&lt;p&gt;But &quot;without doing much&quot; means different things in different tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Old Google Analytics (Universal Analytics):&lt;/strong&gt; a bounce was a visit with only one page view. Someone who read your article for 10 minutes and left still counted as a bounce.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GA4:&lt;/strong&gt; a bounce is a visit that is &lt;strong&gt;not engaged&lt;/strong&gt;. A visit counts as engaged if it lasts more than 10 seconds, or has two or more page views, or includes a conversion. So GA4 bounce rate is simply 100% minus the engagement rate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is why your bounce rate probably dropped when you moved to GA4. The visitors did not change. The definition did.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; When you compare your bounce rate with any &quot;average&quot;, first check which definition it uses.&lt;/p&gt;
&lt;h3 id=&quot;good-bounce-rate-ranges-by-page-type&quot;&gt;Good bounce rate ranges by page type&lt;/h3&gt;
&lt;p&gt;These are broad ranges often seen across websites, using the &quot;one page, short visit&quot; idea. Treat them as a starting point, not a rule.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Page type &lt;/th&gt;&lt;th&gt;Typical range &lt;/th&gt;&lt;th&gt;Why&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Blog post or article &lt;/td&gt;&lt;td&gt;65% to 90% &lt;/td&gt;&lt;td&gt;People read one article and leave. That is normal.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Landing page (one offer) &lt;/td&gt;&lt;td&gt;60% to 90% &lt;/td&gt;&lt;td&gt;One page, one action. No reason to click deeper.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Home page &lt;/td&gt;&lt;td&gt;40% to 60% &lt;/td&gt;&lt;td&gt;It should send people to other pages.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Product page (e-commerce) &lt;/td&gt;&lt;td&gt;30% to 55% &lt;/td&gt;&lt;td&gt;People compare, add to cart, look at other products.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Service or lead-gen page &lt;/td&gt;&lt;td&gt;35% to 60% &lt;/td&gt;&lt;td&gt;Visitors check prices, reviews, contact.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Checkout or cart &lt;/td&gt;&lt;td&gt;Under 30% &lt;/td&gt;&lt;td&gt;Leaving here usually means a problem.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;when-a-high-bounce-rate-is-fine&quot;&gt;When a high bounce rate is fine&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The page answered the question.&lt;/strong&gt; Someone searched &quot;store opening hours&quot;, found them, left happy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It is a single-page site or landing page.&lt;/strong&gt; There is nowhere else to go.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The goal is off-site.&lt;/strong&gt; A click to call, to an app store, or to a partner.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long reads.&lt;/strong&gt; In old-style analytics, a 10-minute reader who leaves is still a bounce.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;when-a-high-bounce-rate-is-a-problem&quot;&gt;When a high bounce rate is a problem&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Visitors leave fast&lt;/strong&gt; (under 10 seconds). They did not find what they expected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paid traffic bounces.&lt;/strong&gt; You pay for each visit, so every quick exit costs money.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pages that should lead somewhere&lt;/strong&gt; (home, category, pricing) have a very high rate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile is much worse than desktop.&lt;/strong&gt; Something is broken or slow on phones.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-to-find-out-why-people-bounce&quot;&gt;How to find out why people bounce&lt;/h3&gt;
&lt;p&gt;The bounce rate tells you &lt;strong&gt;that&lt;/strong&gt; people leave. It never tells you &lt;strong&gt;why&lt;/strong&gt;. For that you need to look at behavior.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Scroll heatmap.&lt;/strong&gt; Do people even reach your main content? If 70% stop at the hero, the top of the page is the problem. (Pro in Opti-Behavior)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Click heatmap.&lt;/strong&gt; Are people clicking on something that is not a link, then giving up?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/opti-behavior/ob-feature-recordings/&quot;&gt;Session recordings&lt;/a&gt;.&lt;/strong&gt; Watch 5 to 10 visits that ended quickly. You will often spot the reason in minutes. (Pro in Opti-Behavior)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed on real visitors.&lt;/strong&gt; A slow page on mobile is one of the most common causes of quick exits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Opti-Behavior, the Traffic dashboard shows your bounce rate and average scroll depth. Click heatmaps are free; &lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/opti-behavior/ob-feature-heatmaps/&quot;&gt;scroll heatmaps&lt;/a&gt; and session recordings are in Pro. &lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/opti-behavior/ob-feature-smart-insights/&quot;&gt;Smart Insights&lt;/a&gt; (Free) then turns your traffic, click and funnel data into one card per problem (Pro adds signals from recordings, scroll heatmaps, errors and forms): what is wrong, where it happens, the evidence behind it and what to check next.&lt;/p&gt;
&lt;h3 id=&quot;7-ways-to-lower-a-bad-bounce-rate&quot;&gt;7 ways to lower a bad bounce rate&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Match the promise.&lt;/strong&gt; The page must deliver what the ad or search result said.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Say what you offer in the first screen.&lt;/strong&gt; One clear headline, one sentence, one button.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed up mobile.&lt;/strong&gt; Compress the hero image, remove heavy widgets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove pop-ups that fire right away.&lt;/strong&gt; Let people read first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add clear next steps.&lt;/strong&gt; Related articles, &quot;see pricing&quot;, a visible button.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make text easy to scan.&lt;/strong&gt; Short paragraphs, headings, bullet lists.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test your changes.&lt;/strong&gt; An A/B test tells you if the new version really keeps people longer.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;a-good-bounce-rate-the-bottom-line&quot;&gt;A good bounce rate: the bottom line&lt;/h3&gt;
&lt;p&gt;A good bounce rate is the one that fits the page’s job. Compare each page with itself over time, not with a random average. When a page that should lead somewhere starts to bounce more, look at its heatmap and a few recordings before you change anything. Our guides on &lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/blog/wordpress-heatmap-plugin-guide/&quot;&gt;adding a heatmap to WordPress&lt;/a&gt; and form abandonment show the next steps.&lt;/p&gt;
&lt;h3 id=&quot;good-bounce-rate-faq&quot;&gt;Good bounce rate FAQ&lt;/h3&gt;
&lt;h4 id=&quot;what-is-a-good-bounce-rate-&quot;&gt;What is a good bounce rate?&lt;/h4&gt;
&lt;p&gt;It depends on the page. Very roughly, 30% to 55% is common for product pages, 40% to 60% for home pages, and 65% to 90% is normal for blog posts and landing pages.&lt;/p&gt;
&lt;h4 id=&quot;is-a-70-bounce-rate-bad-&quot;&gt;Is a 70% bounce rate bad?&lt;/h4&gt;
&lt;p&gt;Not for a blog post or a landing page. It can be a warning sign for a home page, a product page or a checkout.&lt;/p&gt;
&lt;h4 id=&quot;why-did-my-bounce-rate-drop-in-ga4-&quot;&gt;Why did my bounce rate drop in GA4?&lt;/h4&gt;
&lt;p&gt;GA4 counts a visit as a bounce only if it is not engaged (under 10 seconds, one page, no conversion). Universal Analytics counted every one-page visit, so GA4 numbers are usually lower.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-lower-my-bounce-rate-&quot;&gt;How do I lower my bounce rate?&lt;/h4&gt;
&lt;p&gt;Find out why people leave with click heatmaps (free), scroll heatmaps and recordings (Pro), then fix the first screen, the speed on mobile and the next step you offer. Test the change with an A/B test.&lt;/p&gt;
&lt;p&gt;The Opti-Behavior Team&lt;/p&gt;
&lt;p&gt;Full guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/blog/what-is-a-good-bounce-rate/?utm_source=coderlegion&amp;amp;utm_medium=referral&amp;amp;utm_campaign=oct26-problem-keyword&amp;amp;utm_content=good-bounce-rate&quot;&gt;https://optiuser.com/blog/what-is-a-good-bounce-rate/?utm_source=coderlegion&amp;amp;utm_medium=referral&amp;amp;utm_campaign=oct26-problem-keyword&amp;amp;utm_content=good-bounce-rate&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Drafted with AI assistance, reviewed by the Opti-Behavior Team.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32690/what-is-a-good-bounce-rate-ranges-by-page-type-and-when-to-worry</guid>
<pubDate>Tue, 27 Oct 2026 13:38:17 +0000</pubDate>
</item>
<item>
<title>I let every AI crawler in. A month later I cannot tell you how many people read me.</title>
<link>https://coderlegion.com/32440/i-let-every-ai-crawler-in-a-month-later-i-cannot-tell-you-how-many-people-read-me</link>
<description>&lt;p&gt;In early August Cloudflare sent me a congratulations email. The site had passed ten thousand pageviews in its first month: 33,561, on a domain that went live on 5 July.&lt;/p&gt;
&lt;p&gt;For about ten seconds it was good news.&lt;/p&gt;
&lt;p&gt;Then I remembered what I had done in July, which was open &lt;code&gt;robots.txt&lt;/code&gt; to every AI crawler in existence. Training included, on purpose. I have no brand to protect and my problem is not that someone copies me: it is that nobody finds me. So everyone gets in.&lt;/p&gt;
&lt;p&gt;Which makes that number, taken as it is, good for nothing.&lt;/p&gt;
&lt;h3 id=&quot;why-does-a-pageview-count-not-tell-you-how-many-people-read-you-&quot;&gt;Why does a pageview count not tell you how many people read you?&lt;/h3&gt;
&lt;p&gt;Because Cloudflare's free plan counts requests, not people, and it does not separate humans from bots. Inside those 33,561 are the crawlers I invited myself, and I have no way to know in what proportion.&lt;/p&gt;
&lt;p&gt;This is a practical problem, not a philosophical one. If a client asks me tomorrow how much traffic the site gets, I do not have an answer I can defend. &quot;Thirty-three thousand&quot; would be a true sentence and false information, because whoever hears it understands &quot;thirty-three thousand people&quot;. I would rather say I do not know.&lt;/p&gt;
&lt;p&gt;And there is the opposite question, which matters more to me: is opening the gates working? If GPTBot comes through and re-reads the site every week, I did the right thing. If it never comes, I am giving away bandwidth for nothing, and I would like to know.&lt;/p&gt;
&lt;h3 id=&quot;is-letting-ai-crawlers-in-actually-worth-it-&quot;&gt;Is letting AI crawlers in actually worth it?&lt;/h3&gt;
&lt;p&gt;On half the question I have a measured answer, and the two halves look nothing alike. Perplexity cites me in first position for &quot;who is Marco Bellingeri, cloud and security engineer&quot;. ChatGPT, asked the same thing, does not cite me at all.&lt;/p&gt;
&lt;p&gt;The monitor that measures this runs every Monday and writes the history to a database. So far, routine. The interesting part is what the model did when I asked it that question.&lt;/p&gt;
&lt;p&gt;It did not look for me. It rewrote the question into a search query, &quot;Marco Bellingeri AI security&quot;, and read twelve pages about the topic: the European regulation, the AI Act, the Bletchley declaration, a couple of newspapers. None of them was about a person. It answered about the subject because it never found the person.&lt;/p&gt;
&lt;p&gt;That is a different kind of failure from &quot;your content is not good enough&quot;. It is the engine failing to connect the question to the page, and the difference changes what is worth doing next. If the crawler never comes, rewriting articles is wasted time and the problem is access. If it comes and does not cite, the problem is that the content is not extractable, and then rewriting is the work.&lt;/p&gt;
&lt;p&gt;To know which, I needed to count who comes through.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-count-crawlers-without-tracking-people-&quot;&gt;How do you count crawlers without tracking people?&lt;/h3&gt;
&lt;p&gt;By looking at the &lt;code&gt;User-Agent&lt;/code&gt; of every page request and recording only the family it belongs to, with no IP address, no cookie, no session, nothing that ties two requests to the same person.&lt;/p&gt;
&lt;p&gt;The site is static and runs on Cloudflare Workers, so the Worker was the obvious place. I started from an assumption that turned out to be false: I thought every request went through it. It did not. The Worker was configured to run on five routes only, the root and four APIs. Every page of the site was served straight from static assets, and my code never saw a thing.&lt;/p&gt;
&lt;p&gt;The fix was not &quot;send everything through&quot;. Requests to static assets are free and unlimited; requests that invoke the Worker consume the free plan's quota of a hundred thousand a day. Sending everything through would have put my code in front of every font and every stylesheet, which is most of the volume and all of the latency that matters, in order to count things that are not pageviews.&lt;/p&gt;
&lt;p&gt;So only HTML goes through. A pageview &lt;em&gt;is&lt;/em&gt; an HTML request. The rest is what that page drags along with it.&lt;/p&gt;
&lt;h3 id=&quot;what-not-to-keep-which-is-the-hard-part&quot;&gt;What NOT to keep, which is the hard part&lt;/h3&gt;
&lt;p&gt;The classification is trivial: a list of names, GPTBot, ClaudeBot, PerplexityBot, Googlebot, and a couple of rules to spot a real browser. Twenty lines. What took me time was deciding what to throw away.&lt;/p&gt;
&lt;p&gt;A browser's &lt;code&gt;User-Agent&lt;/code&gt; identifies a person far more than it looks. Combined with other signals it becomes a fingerprint. So for a person I keep one word, &lt;code&gt;human&lt;/code&gt;, and drop the string.&lt;/p&gt;
&lt;p&gt;For a crawler I already know I keep the family name and nothing else: I know who it is, the rest adds nothing.&lt;/p&gt;
&lt;p&gt;That leaves the awkward case, the one that made me rewrite the code twice. If I do not recognise the client, what do I do? The first version called it a bot and kept its full &lt;code&gt;User-Agent&lt;/code&gt;. Wrong on two counts. The first is that a person can be in there: a text browser, a webview inside an app, an accessibility tool, a &lt;code&gt;User-Agent&lt;/code&gt; stripped by a privacy extension. Treating them as a bot and keeping their string is exactly the thing I said I would not do.&lt;/p&gt;
&lt;p&gt;The second reason is that the number lies. If I put &quot;a crawler I do not recognise&quot; and &quot;I could not tell what this was&quot; in the same bucket, I then read that forty per cent of the traffic is automated and believe it, when part of that forty is my own uncertainty wearing a certainty's clothes. And that number is the one that ends up in front of a client.&lt;/p&gt;
&lt;p&gt;Now there are two labels. A client that declares itself automated in its own &lt;code&gt;User-Agent&lt;/code&gt;, by writing &lt;code&gt;bot&lt;/code&gt; or &lt;code&gt;crawler&lt;/code&gt; or &lt;code&gt;curl&lt;/code&gt; into it, gets kept as its short product name: that is what lets me notice a new family. A client that simply does not place gets recorded as &lt;code&gt;unclassified&lt;/code&gt; and nothing else.&lt;/p&gt;
&lt;h3 id=&quot;what-does-counting-cost-&quot;&gt;What does counting cost?&lt;/h3&gt;
&lt;p&gt;Little, but not nothing, and the point is knowing beforehand rather than finding out. Every page that goes through the Worker consumes one request of the free quota. At today's traffic that is around eleven hundred a day against a hundred thousand: a margin of about ninety, which is comfortable but not infinite, and there is no rate limit on those routes.&lt;/p&gt;
&lt;p&gt;I wrote it into the project README, along with what I will do if the number gets close, in order: watch the counter, narrow the routes and accept counting less, and only then pay. A declared ceiling with an exit path next to it is acceptable debt. A ceiling discovered by accident one night is an incident.&lt;/p&gt;
&lt;h3 id=&quot;the-line-that-nearly-made-me-publish-a-lie&quot;&gt;The line that nearly made me publish a lie&lt;/h3&gt;
&lt;p&gt;My site's privacy policy said, in Italian and in English, that it &quot;uses no tracking or analytics&quot;.&lt;/p&gt;
&lt;p&gt;That was true until the day before. If I had shipped the counter without touching that page, the site that sells transparency would have published a false statement about itself, and no test would have caught it: tests look at code, not at promises.&lt;/p&gt;
&lt;p&gt;I rewrote it before shipping. It now says what it does: it counts requests one at a time, with no identifiers, no profiling, and keeps the data for three months because that is when the platform deletes it. I also removed the word &quot;aggregate&quot; from a first draft, because it was not true: what I write is one event per request, not a running total, and calling it aggregate would have been more reassuring and less accurate.&lt;/p&gt;
&lt;h3 id=&quot;what-i-still-do-not-know&quot;&gt;What I still do not know&lt;/h3&gt;
&lt;p&gt;The counter has been on for a few hours. I generated the first data points myself, sending three test requests with fake &lt;code&gt;User-Agent&lt;/code&gt; strings to check that it worked, and honestly I could not tell them apart from real traffic.&lt;/p&gt;
&lt;p&gt;So I have nothing to tell you about crawlers yet. In a week I will know: which families come through, how often, on which pages, and above all whether GPTBot shows up at all. That answer decides whether it is worth continuing to write to be found by a model, or whether I have been writing for an audience that never came.&lt;/p&gt;
&lt;p&gt;In the meantime I have lost the ability to say thirty-three thousand. It looks like a step backwards and it is a real step forward: before, I had a big number and no idea what was inside it. Now I have a measurement on its way, and in the meantime an honest answer.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32440/i-let-every-ai-crawler-in-a-month-later-i-cannot-tell-you-how-many-people-read-me</guid>
<pubDate>Mon, 26 Oct 2026 01:32:09 +0000</pubDate>
</item>
<item>
<title>What 1.23M Edge Requests Taught Us About AI Crawlers (and Our Own Analytics)</title>
<link>https://coderlegion.com/31840/what-1-23m-edge-requests-taught-us-about-ai-crawlers-and-our-own-analytics</link>
<description>&lt;p&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.seosiri.com/2026/10/edge-telemetry-audit-ai-crawlers.html&quot;&gt;SEOSiri&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We read 90 days of Cloudflare edge logs through Microsoft Clarity's bot analytics (~1.23M requests) and compared them with GA4 and Blogger stats. Here's what stood out, with the caveats.&lt;/p&gt;
&lt;h3 id=&quot;the-split&quot;&gt;The split&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;About 70% of requests were likely human, about 30% automated.&lt;/li&gt;
&lt;li&gt;Clarity classifies roughly 200K requests as AI crawler, AI search or AI assistant activity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;selected-ai-operators-requests-in-90-days-&quot;&gt;Selected AI operators (requests in 90 days)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Operator &lt;/th&gt;&lt;th&gt;Requests&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Microsoft &lt;/td&gt;&lt;td&gt;43,380&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Google &lt;/td&gt;&lt;td&gt;42,050&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Meta &lt;/td&gt;&lt;td&gt;35,690&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Anthropic &lt;/td&gt;&lt;td&gt;19,730&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;OpenAI &lt;/td&gt;&lt;td&gt;14,900&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Clarity also lists Huawei and Amazon above OpenAI. Logs show who asked for a page, not how the data is used afterward.&lt;/p&gt;
&lt;h3 id=&quot;robots-txt-compliance&quot;&gt;robots.txt compliance&lt;/h3&gt;
&lt;p&gt;Clarity's Violations card showed 0.98%: requests to URLs our robots.txt disallows. That's a compliance metric, not a security one. If you set per-operator rules, they look like this (choose Allow or Disallow for your own policy):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;status-codes&quot;&gt;Status codes&lt;/h3&gt;
&lt;p&gt;Around 48% of requests showed as unsuccessful in Clarity. We're breaking them down by path and status code next.&lt;/p&gt;
&lt;h3 id=&quot;don-t-mix-units&quot;&gt;Don't mix units&lt;/h3&gt;
&lt;p&gt;Blogger showed 64.7K pageviews from the Netherlands. GA4 showed 102 active users there. Same site, same 90 days, different measures.&lt;/p&gt;
&lt;p&gt;How do you separate bots from humans in your analytics?&lt;/p&gt;
&lt;p&gt;Full write-up with tables and methodology: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.seosiri.com/2026/10/edge-telemetry-audit-ai-crawlers.html?utm_source=coderlegion&amp;amp;utm_medium=social&amp;amp;utm_campaign=edge-audit&quot;&gt;SEOSiri edge telemetry audit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SEOSiri is my own property&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31840/what-1-23m-edge-requests-taught-us-about-ai-crawlers-and-our-own-analytics</guid>
<pubDate>Tue, 20 Oct 2026 11:36:46 +0000</pubDate>
</item>
<item>
<title>Heatmap Empty After Installing a Cache Plugin? 3 Checks in 60 Seconds</title>
<link>https://coderlegion.com/30993/heatmap-empty-after-installing-a-cache-plugin-3-checks-in-60-seconds</link>
<description>&lt;p&gt;Your heatmap is empty, the session counter says zero, and nothing errors. On WordPress, the usual cause is not the analytics tool. It is the cache or performance plugin: it merges, defers or delays JavaScript, and the tracker stops running without a single warning. This applies to every tool in the category (Hotjar, Microsoft Clarity, Matomo, Opti-Behavior or your own beacon).&lt;/p&gt;
&lt;p&gt;Open your site in a private window, logged out, and run these three checks in order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Is the script in the HTML?&lt;/strong&gt;&lt;br&gt;
View source (Ctrl+U, not the Elements tab) and search for your tracker's file name.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not there: the script was never added. Check the plugin is active and that you are not excluded as a logged-in admin.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; points to something like &lt;code&gt;/wp-content/cache/min/...&lt;/code&gt;: the tracker was merged into a bundle.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; is not &lt;code&gt;text/javascript&lt;/code&gt; (for example &lt;code&gt;rocketlazyloadscript&lt;/code&gt;): the script is delayed until the first interaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Does the request leave the browser?&lt;/strong&gt;&lt;br&gt;
DevTools &amp;gt; Network &amp;gt; Fetch/XHR, then click and scroll for ten seconds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No request: the tracker never started.&lt;/li&gt;
&lt;li&gt;4xx: often an expired nonce on a page served from cache for too long.&lt;/li&gt;
&lt;li&gt;200: tracking works; look at filters or admin exclusion instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Is the config object defined?&lt;/strong&gt;&lt;br&gt;
In the console, type your tracker's global config name. &lt;code&gt;undefined&lt;/code&gt; while the file is present means the small inline config block was optimised away. Excluding the file is only half the job: the inline block needs its own exclusion rule.&lt;/p&gt;
&lt;p&gt;The full guide has the exclusion code for WP Rocket, LiteSpeed Cache, SiteGround Optimizer, Autoptimize, Perfmatters, WP-Optimize, W3 Total Cache, Breeze and Jetpack Boost, plus a clean way to verify the fix:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://optiuser.com/blog/heatmap-not-working-cache-plugin/?utm_source=coderlegion&amp;amp;utm_campaign=traffic-oct26&quot;&gt;https://optiuser.com/blog/heatmap-not-working-cache-plugin/?utm_source=coderlegion&amp;amp;utm_campaign=traffic-oct26&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclosure: written by The Opti-Behavior Team, the publisher of the Opti-Behavior plugin, with the help of AI tools, and reviewed by the team.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30993/heatmap-empty-after-installing-a-cache-plugin-3-checks-in-60-seconds</guid>
<pubDate>Tue, 13 Oct 2026 22:27:08 +0000</pubDate>
</item>
<item>
<title>Telemetry: Shared Analytics and Crash Reporting in Kotlin Multiplatform</title>
<link>https://coderlegion.com/30479/telemetry-shared-analytics-and-crash-reporting-in-kotlin-multiplatform</link>
<description>&lt;p&gt;In a modern endurance car, every sensor talks to the same box. Tyre pressure, brake temperature, fuel flow, throttle trace. They all end up in one data stream that goes to the pit wall over a single radio link. The engineer on the wall doesn't care which supplier made the brake sensor. He cares that the channel is called &lt;code&gt;BRAKE_TEMP_FL&lt;/code&gt; and that the number arrives every 100 milliseconds.&lt;/p&gt;
&lt;p&gt;Swap a sensor, the channel name stays. That's the whole point of the standard.&lt;/p&gt;
&lt;p&gt;Analytics and crash reporting in a KMP app should work the same way. The sensors are the SDKs, one per platform, made by different teams with different APIs. The channel names are yours.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-had-before&quot;&gt;What I had before&lt;/h3&gt;
&lt;p&gt;My first KMP app logged analytics twice. Once in Kotlin for Android, once in Swift for iOS. Same events, same names, in theory.&lt;/p&gt;
&lt;p&gt;In practice, the Android side sent &lt;code&gt;screen_view&lt;/code&gt; with a &lt;code&gt;screen_name&lt;/code&gt; parameter. The iOS side sent &lt;code&gt;screenView&lt;/code&gt; with &lt;code&gt;screenName&lt;/code&gt;. Nobody noticed for six weeks, until I tried to build a funnel in the dashboard and half the users were missing from it. The numbers weren't wrong. They were split across two event names that meant the same thing.&lt;/p&gt;
&lt;p&gt;That's the real cost of leaving telemetry in the platform layer. Not the duplicated code, which is small. The drift. Two implementations of the same contract, with no compiler checking that they agree.&lt;/p&gt;
&lt;p&gt;If you read &lt;a rel=&quot;nofollow&quot; href=&quot;/posts/feature-flags-kmp&quot;&gt;Change the Map&lt;/a&gt;, this is the same move applied to a different problem: the contract lives in &lt;code&gt;commonMain&lt;/code&gt;, the SDKs sit underneath. It also pairs with flags directly, because the first event worth logging is which variant a user was actually shown.&lt;/p&gt;
&lt;p&gt;Crash reporting has a quieter version of the same problem. A Kotlin exception that crashes the Android app shows up in Crashlytics with a readable stack trace. The same exception on iOS arrives as a native crash with frames that look like &lt;code&gt;kfun:...&lt;/code&gt;, and the Kotlin type is buried in the message. I'll get to that.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-interface-lives-in-commonmain&quot;&gt;The interface lives in commonMain&lt;/h3&gt;
&lt;p&gt;The fix is to stop treating analytics as a platform feature and start treating it as a domain contract.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// commonMain
interface Analytics {
    fun track(event: AnalyticsEvent)
    fun setUserId(id: String?)
}

sealed interface AnalyticsEvent {
    val name: String
    val params: Map&amp;lt;String, String&amp;gt;

    data class ScreenView(val screen: String) : AnalyticsEvent {
        override val name = &quot;screen_view&quot;
        override val params = mapOf(&quot;screen_name&quot; to screen)
    }

    data class Purchase(val sku: String, val amountCents: Long) : AnalyticsEvent {
        override val name = &quot;purchase&quot;
        override val params = mapOf(
            &quot;sku&quot; to sku,
            &quot;amount_cents&quot; to amountCents.toString(),
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A sealed interface for events is the part that pays for itself. The event name and the parameter keys exist in exactly one place. A new event is a new class, and the compiler tells every &lt;code&gt;when&lt;/code&gt; that cares about it. A typo in &lt;code&gt;&quot;screen_name&quot;&lt;/code&gt; is no longer possible in two files, because it's only written once.&lt;/p&gt;
&lt;p&gt;I keep params as &lt;code&gt;Map&amp;lt;String, String&amp;gt;&lt;/code&gt; on purpose. Firebase, Mixpanel, and Amplitude all accept richer types, but they disagree about which ones. Strings are the lowest common denominator, and the conversion cost is trivial.&lt;/p&gt;
&lt;p&gt;The crash side is a second, separate interface.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// commonMain
interface CrashReporter {
    fun log(message: String)
    fun recordNonFatal(throwable: Throwable)
    fun setUserId(id: String?)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two interfaces, not one. Analytics is about what users did. Crash reporting is about what broke. They often go to different vendors, and you'll want to swap one without touching the other.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;android-the-easy-side&quot;&gt;Android: the easy side&lt;/h3&gt;
&lt;p&gt;On Android the implementation is a thin wrapper. Firebase is the obvious choice, though nothing here is Firebase-specific.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// androidMain
class FirebaseAnalyticsImpl(
    private val firebase: FirebaseAnalytics,
) : Analytics {

    override fun track(event: AnalyticsEvent) {
        val bundle = Bundle().apply {
            event.params.forEach { (k, v) -&amp;gt; putString(k, v) }
        }
        firebase.logEvent(event.name, bundle)
    }

    override fun setUserId(id: String?) {
        firebase.setUserId(id)
    }
}

class CrashlyticsReporter(
    private val crashlytics: FirebaseCrashlytics,
) : CrashReporter {

    override fun log(message: String) = crashlytics.log(message)

    override fun recordNonFatal(throwable: Throwable) =
        crashlytics.recordException(throwable)

    override fun setUserId(id: String?) {
        crashlytics.setUserId(id.orEmpty())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing clever. If the Android side is the only one you ever shipped, this is the code you already had.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ios-the-swift-bridge&quot;&gt;iOS: the Swift bridge&lt;/h3&gt;
&lt;p&gt;The simplest iOS implementation doesn't call the Firebase iOS SDK from Kotlin at all. No &lt;code&gt;expect&lt;/code&gt;/&lt;code&gt;actual&lt;/code&gt; in this layer, no cinterop bindings, no CocoaPods dependency in the shared module.&lt;/p&gt;
&lt;p&gt;The shared module defines the interface, and the iOS app passes in a Swift implementation at startup.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// iosApp
final class FirebaseAnalyticsBridge: Analytics {

    func track(event: AnalyticsEvent) {
        FirebaseAnalytics.Analytics.logEvent(event.name, parameters: event.params)
    }

    func setUserId(id: String?) {
        FirebaseAnalytics.Analytics.setUserID(id)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Swift sees the Kotlin interface as a protocol, so conforming to it is ordinary Swift. The one snag is the name: Firebase's Swift class is also called &lt;code&gt;Analytics&lt;/code&gt;, so inside this file the bare name is ambiguous. I qualify the Firebase one with its module. Renaming the Kotlin interface would also work, but I'd rather not let a vendor pick my names. The app registers it with the DI container before the first screen renders.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// commonMain/Telemetry.kt
fun setUpTelemetry(analytics: Analytics, crashReporter: CrashReporter) {
    // register both in your DI graph of choice
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// iosApp
@main
struct IosApp: App {
    init() {
        TelemetryKt.setUpTelemetry(
            analytics: FirebaseAnalyticsBridge(),
            crashReporter: CrashlyticsBridge()
        )
    }
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don't start that function name with &lt;code&gt;init&lt;/code&gt;. The Objective-C export reserves &lt;code&gt;init&lt;/code&gt; for initializers, so a Kotlin function called &lt;code&gt;initTelemetry&lt;/code&gt; shows up in Swift as &lt;code&gt;doInitTelemetry&lt;/code&gt;. It works, but it's the kind of thing that costs ten minutes the first time you see it.&lt;/p&gt;
&lt;p&gt;The Firebase SDK stays where Xcode manages it. The shared module never learns it exists.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;two-other-ways-to-bring-the-sdk-in&quot;&gt;Two other ways to bring the SDK in&lt;/h3&gt;
&lt;p&gt;That Swift bridge is how I wrote this in my first KMP apps, and it's the version I'd explain to anyone starting out because every moving part is visible. It isn't what I use today.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitLive's Firebase Kotlin SDK.&lt;/strong&gt; &lt;code&gt;firebase-kotlin-sdk&lt;/code&gt; from GitLive wraps Firebase so you can call it from &lt;code&gt;commonMain&lt;/code&gt;. I haven't shipped it myself. If you use it, your &lt;code&gt;Analytics&lt;/code&gt; interface still sits in front of it, otherwise you've just moved the vendor lock-in into shared code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CocoaPods through Gradle, with the call in &lt;code&gt;iosMain&lt;/code&gt;.&lt;/strong&gt; This is what I use now. The Kotlin CocoaPods plugin lets the shared module declare the Firebase pod itself, and Kotlin generates the bindings so &lt;code&gt;iosMain&lt;/code&gt; can call the iOS SDK directly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// build.gradle.kts (shared module)
plugins {
    kotlin(&quot;multiplatform&quot;)
    kotlin(&quot;native.cocoapods&quot;)
}

kotlin {
    cocoapods {
        version = &quot;1.0&quot;
        ios.deploymentTarget = &quot;16.0&quot;
        pod(&quot;FirebaseAnalytics&quot;)
        pod(&quot;FirebaseCrashlytics&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can pin a version with &lt;code&gt;pod(&quot;FirebaseAnalytics&quot;, &quot;~&amp;gt; 12.0&quot;)&lt;/code&gt;, and &lt;code&gt;linkOnly = true&lt;/code&gt; exists for pods you only need to link and not bind. The Kotlin docs cover both.&lt;/p&gt;
&lt;p&gt;Then the iOS implementation is Kotlin, in the same module as the interface.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// iosMain
import cocoapods.FirebaseAnalytics.FIRAnalytics
import kotlinx.cinterop.ExperimentalForeignApi

@OptIn(ExperimentalForeignApi::class)
class IosAnalytics : Analytics {

    @Suppress(&quot;UNCHECKED_CAST&quot;)
    override fun track(event: AnalyticsEvent) {
        FIRAnalytics.logEventWithName(event.name, event.params as Map&amp;lt;Any?, *&amp;gt;)
    }

    override fun setUserId(id: String?) {
        FIRAnalytics.setUserID(id)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bindings come from the Objective-C headers, not the Swift API, so the class is &lt;code&gt;FIRAnalytics&lt;/code&gt; and the method is &lt;code&gt;logEventWithName&lt;/code&gt;. The &lt;code&gt;parameters&lt;/code&gt; dictionary arrives as &lt;code&gt;Map&amp;lt;Any?, *&amp;gt;?&lt;/code&gt;. A &lt;code&gt;Map&amp;lt;String, String&amp;gt;&lt;/code&gt; doesn't fit that type as-is, because map keys are invariant in Kotlin, hence the cast.&lt;/p&gt;
&lt;p&gt;I switched because the Swift bridge had a cost I only noticed over time: two files per interface, in two languages, in two repos' worth of mental context. With the pod declared in Gradle, the iOS implementation sits next to the Android one, in Kotlin, and a new method on &lt;code&gt;Analytics&lt;/code&gt; fails the build in the same module.&lt;/p&gt;
&lt;p&gt;It has its own cost. Pod dependencies and cinterop bindings are now part of your shared module's build, so Gradle sync and iOS builds feel it, and a Firebase SDK bump can break the generated bindings. It's also the option with an expiry date: the Kotlin docs now carry a CocoaPods-to-SwiftPM migration guide, and the new &lt;code&gt;swiftPMDependencies { }&lt;/code&gt; block replaces &lt;code&gt;cocoapods { }&lt;/code&gt;. The docs I read put the minimum Kotlin Gradle plugin at 2.4.20-RC3 for it, so check your version before planning the move. That's the trade I accept for keeping everything telemetry-related in one language.&lt;/p&gt;
&lt;p&gt;I'm showing three options on purpose, and Firebase is only the example. A KMP project has more than one way to bring a native dependency in: a bridge the app injects, a KMP wrapper library, or a native dependency declared in Gradle. Which one fits depends on how much of the native SDK surface you need, and who maintains the glue.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;kotlin-exceptions-on-ios-are-not-kotlin-exceptions&quot;&gt;Kotlin exceptions on iOS are not Kotlin exceptions&lt;/h3&gt;
&lt;p&gt;This is the part nobody mentions until you're staring at a dashboard.&lt;/p&gt;
&lt;p&gt;When an uncaught Kotlin exception terminates the app on iOS, the crash is reported by the native crash handler. The stack trace is the native one. The Kotlin frames are there, but unsymbolicated Kotlin frames read as noise, and the exception type is flattened. Touchlab's CrashKiOS exists for exactly this: it sends symbolicated Kotlin crashes and handled exceptions to Crashlytics, with breadcrumbs and custom keys. Its docs page was last updated in January 2024, so check the repo for recent activity before you rely on it.&lt;/p&gt;
&lt;p&gt;For non-fatal errors you have a better option. Catch the exception at a boundary you control, and hand it to &lt;code&gt;recordNonFatal&lt;/code&gt;. On iOS that means converting it to an &lt;code&gt;NSError&lt;/code&gt;, and that's where it gets fiddly.&lt;/p&gt;
&lt;p&gt;Swift can't get much out of a &lt;code&gt;KotlinThrowable&lt;/code&gt; by itself. The Kotlin stack trace accessor on Native, &lt;code&gt;getStackTrace()&lt;/code&gt;, is a stdlib extension marked &lt;code&gt;@ExperimentalNativeApi&lt;/code&gt;, so I don't count on it being reachable from Swift. I let Kotlin do the work instead, with two small extensions in &lt;code&gt;iosMain&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// iosMain
fun Throwable.kotlinTypeName(): String =
    this::class.qualifiedName ?: &quot;UnknownKotlinThrowable&quot;

fun Throwable.kotlinStackTrace(): String = stackTraceToString()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They extend a Kotlin class, so the export turns them into methods on &lt;code&gt;KotlinThrowable&lt;/code&gt; and Swift calls them like any other method.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// iosApp
final class CrashlyticsBridge: CrashReporter {

    func recordNonFatal(throwable: KotlinThrowable) {
        let error = NSError(
            domain: throwable.kotlinTypeName(),
            code: 0,
            userInfo: [
                NSLocalizedDescriptionKey: throwable.message ?? &quot;unknown&quot;,
                &quot;kotlin_stacktrace&quot;: throwable.kotlinStackTrace(),
            ]
        )
        Crashlytics.crashlytics().record(error: error)
    }

    func log(message: String) {
        Crashlytics.crashlytics().log(message)
    }

    func setUserId(id: String?) {
        Crashlytics.crashlytics().setUserID(id ?? &quot;&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Crashlytics turns &lt;code&gt;userInfo&lt;/code&gt; into key-value pairs in the keys section of the issue, so the full Kotlin trace sits right there. I put it there on purpose rather than hoping the dashboard parses the message, because it won't.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;domain&lt;/code&gt; choice matters more than it looks. For fatal crashes Crashlytics groups by stack trace, but the Firebase docs say logged errors are grouped by &lt;code&gt;domain&lt;/code&gt; and &lt;code&gt;code&lt;/code&gt;. A fixed domain like &lt;code&gt;&quot;kotlin&quot;&lt;/code&gt; with code &lt;code&gt;0&lt;/code&gt; would collapse every non-fatal into one issue. With the Kotlin type there, an &lt;code&gt;IllegalStateException&lt;/code&gt; and a network timeout stay separate. Keep unique values like user IDs or timestamps out of both fields, or you'll fragment the dashboard the other way. Crashlytics also only stores the most recent eight exceptions in a session, so &lt;code&gt;recordNonFatal&lt;/code&gt; is not a log.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-the-calls-go&quot;&gt;Where the calls go&lt;/h3&gt;
&lt;p&gt;The interface is only worth having if people use it in the same place. I let ViewModels own analytics events and nothing else.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;// commonMain
class CheckoutViewModel(
    private val analytics: Analytics,
    private val crashReporter: CrashReporter,
    private val repository: CheckoutRepository,
) : ViewModel() {

    fun onPay(sku: String, amountCents: Long) {
        viewModelScope.launch {
            runCatching { repository.pay(sku, amountCents) }
                .onSuccess {
                    analytics.track(AnalyticsEvent.Purchase(sku, amountCents))
                }
                .onFailure {
                    if (it is CancellationException) throw it
                    crashReporter.recordNonFatal(it)
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CancellationException&lt;/code&gt; check is there because &lt;code&gt;runCatching&lt;/code&gt; catches it too. Without it, every user who leaves the screen mid-payment becomes a non-fatal in Crashlytics.&lt;/p&gt;
&lt;p&gt;Composables and SwiftUI views don't call &lt;code&gt;track&lt;/code&gt;. Screen views are the exception, and I fire them from a single navigation observer, so there's one line of code that produces every &lt;code&gt;screen_view&lt;/code&gt; in the app.&lt;/p&gt;
&lt;p&gt;The other benefit shows up in tests. A &lt;code&gt;FakeAnalytics&lt;/code&gt; that stores events in a list takes ten lines, and asserting on it in &lt;code&gt;commonTest&lt;/code&gt; is trivial. I used to skip testing analytics because it meant mocking an SDK. Now I assert that tapping Pay produces exactly one &lt;code&gt;Purchase&lt;/code&gt; with the right amount, and it runs on every target.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;gotchas&quot;&gt;Gotchas&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Don't init the SDKs inside commonMain, whichever option you pick.&lt;/strong&gt; Firebase on Android wants a &lt;code&gt;Context&lt;/code&gt;. On iOS it wants &lt;code&gt;FirebaseApp.configure()&lt;/code&gt; before anything else touches it. Those calls belong in &lt;code&gt;Application.onCreate&lt;/code&gt; and the iOS &lt;code&gt;App.init&lt;/code&gt;, not behind an abstraction that pretends the platforms are the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User IDs are a privacy decision, not a plumbing one.&lt;/strong&gt; &lt;code&gt;setUserId&lt;/code&gt; is easy to wire and easy to get wrong. I pass an internal opaque ID and never an email. Deciding that once, in the interface docs, beats hoping every caller remembers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consent has to be a gate, not a flag.&lt;/strong&gt; If your users can opt out, wrap the implementation, not the call sites. A &lt;code&gt;ConsentAwareAnalytics&lt;/code&gt; decorator that drops events when consent is off keeps the rule in one class. Crash reporting is different. &lt;code&gt;setCrashlyticsCollectionEnabled&lt;/code&gt; can be called at runtime, but the Firebase docs say the override applies from the next launch, so a user who opts out mid-session is still reporting until the app restarts. Say that in your consent screen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A debug build should not talk to production.&lt;/strong&gt; I give the debug variant a &lt;code&gt;LoggingAnalytics&lt;/code&gt; that prints to the console. It's another implementation of the same interface, which is the quiet argument for having the interface at all.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Treat telemetry as a contract you own and a set of sensors you rent. The event names, the parameter keys, and the rule for what counts as a non-fatal live in &lt;code&gt;commonMain&lt;/code&gt;, in code your compiler can check. The SDKs sit underneath, one per platform, and nobody outside the platform layer should know which ones.&lt;/p&gt;
&lt;p&gt;I would still start with the interface even if the app only ships on one platform today. Adding iOS later then costs one Swift file instead of an audit of every event.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Same channel names, whatever the sensor. 🏁&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The KMP Bits app is available on &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/pt/app/bitsreader/id6755438235&quot;&gt;App Store&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://play.google.com/store/apps/details?id=com.joel.bitsreader&quot;&gt;Google Play&lt;/a&gt; — built entirely with KMP.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30479/telemetry-shared-analytics-and-crash-reporting-in-kotlin-multiplatform</guid>
<pubDate>Fri, 09 Oct 2026 14:00:00 +0000</pubDate>
</item>
<item>
<title>Testing on production without polluting your data (and the day my tests voted 8 times)</title>
<link>https://coderlegion.com/30010/testing-on-production-without-polluting-your-data-and-the-day-my-tests-voted-8-times</link>
<description>&lt;p&gt;Every release of &lt;a rel=&quot;nofollow&quot; href=&quot;https://cavienchien.net/en/game/&quot;&gt;Cá Viên Chiên&lt;/a&gt; ends with the same step: walk every changed flow &lt;strong&gt;on production&lt;/strong&gt;, in Chromium and WebKit, at phone and desktop sizes, in both languages. Co-op rooms run on the real room server, share codes come from the real API, rewards are written to the real database.&lt;/p&gt;
&lt;p&gt;That's a lot of fake players. Early on they all showed up in Google Analytics and in my own player count, so I wrote down one rule:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Test traffic never goes to Google Analytics. Our own API and database are used for real, with a test tag, and the tagged data is deleted when the tests are done.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here is how that works on Cloudflare Pages + D1, and where it broke.&lt;/p&gt;
&lt;h3 id=&quot;1-nothing-reaches-google-analytics&quot;&gt;1. Nothing reaches Google Analytics&lt;/h3&gt;
&lt;p&gt;Every Playwright context goes through one helper before any page opens:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export async function guardContext(context, { presence = 'tag' } = {}) {
  // GA4's own opt-out flag, so gtag sends nothing even where its script loads
  await context.addInitScript({ content: `window['ga-disable-${GA_ID}'] = true;` });
  // Silence: the game's Web Audio graph runs into a gain of 0
  await context.addInitScript({ content: MUTE_INIT });
  // Drop analytics and ad hosts before the request leaves the browser
  for (const pattern of BLOCKED) await context.route(pattern, (r) =&amp;gt; r.abort('blockedbyclient'));
  // Our own player count goes through, tagged as a test browser
  if (presence === 'tag') {
    await context.route('**/api/presence**', (r) =&amp;gt;
      r.continue({ headers: { ...r.request().headers(), 'x-cvc-test': '1' } }));
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;BLOCKED&lt;/code&gt; covers Tag Manager, &lt;code&gt;google-analytics.com&lt;/code&gt;, &lt;code&gt;analytics.google.com&lt;/code&gt;, DoubleClick and AdSense. A local &lt;code&gt;astro build&lt;/code&gt; loads GA4 too, so the guard applies to localhost as well.&lt;/p&gt;
&lt;p&gt;There is a test for the guard itself: it scans every script in &lt;code&gt;scripts/&lt;/code&gt; that opens the site and fails if one doesn't import &lt;code&gt;guardContext&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Chrome DevTools tabs driven over MCP can't block requests, so they get the same thing as an init script: the GA opt-out flag, the mute, and a &lt;code&gt;fetch&lt;/code&gt; wrapper that adds the header.&lt;/p&gt;
&lt;h3 id=&quot;2-the-real-api-with-a-tag&quot;&gt;2. The real API, with a tag&lt;/h3&gt;
&lt;p&gt;I don't fake our own endpoints. A faked &lt;code&gt;/api/presence&lt;/code&gt; means no play record, which means no share code, which means the referral reward flow can't be tested at all. I found that out the hard way, by skipping the reward test on production once.&lt;/p&gt;
&lt;p&gt;So the server stores test browsers like anyone else, with a flag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// functions/api/presence.ts
const test = request.headers.get('X-CVC-Test') === '1' ? 1 : null;
await db.prepare(
  'INSERT OR IGNORE INTO installs (id, …, test) VALUES (?, …, ?)'
).bind(id, …, test).run();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The statistics page and &lt;code&gt;pnpm stats&lt;/code&gt; skip &lt;code&gt;installs.test = 1&lt;/code&gt;. Everything that hangs off an install (play days, first-session steps, replays, share codes, referral rewards, poster events) can be traced back to a tagged record.&lt;/p&gt;
&lt;h3 id=&quot;3-delete-it-when-you-re-done&quot;&gt;3. Delete it when you're done&lt;/h3&gt;
&lt;p&gt;After every run, local or production:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;node scripts/clean-test-data.mjs --remote        # delete
node scripts/clean-test-data.mjs --remote --dry  # count only: must be 0 everywhere
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script selects by the tag only, deletes dependents first, then the records, all in one call, then counts again and exits non-zero if anything is left:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const T = 'SELECT id FROM installs WHERE test = 1';
const TABLES = [
  ['referral_rewards', `newcomer_install_id IN (${T}) OR code IN (SELECT code FROM share_codes WHERE install_id IN (${T}))`],
  ['share_codes',   `install_id IN (${T})`],
  ['replays',       `install_id IN (${T})`],
  ['install_steps', `install_id IN (${T})`],
  ['poster_events', `install_id IN (${T})`],
  ['install_days',  `install_id IN (${T})`],
  ['installs',      'test = 1'],
];
run(TABLES.map(([t, w]) =&amp;gt; `DELETE FROM ${t} WHERE ${w};`).join(' '));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The release checklist has &lt;code&gt;test-rules&lt;/code&gt; before the deploy and &lt;code&gt;test-cleanup&lt;/code&gt; after it. The deploy script refuses to run until the before items are answered, and the after items must pass before the release record is committed.&lt;/p&gt;
&lt;p&gt;After the 1.37.0 release walk it deleted 129 test installs and everything attached to them. A real player's row is never selected.&lt;/p&gt;
&lt;h3 id=&quot;4-the-table-that-had-no-tag&quot;&gt;4. The table that had no tag&lt;/h3&gt;
&lt;p&gt;Then I tested the new rating stars.&lt;/p&gt;
&lt;p&gt;The game page has a &quot;rate this game&quot; card. My check opened it, hovered, and voted 4 stars, at two widths, in two languages, in two engines. Eight votes. Locally that was fine. On production, the log told the story on its own:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4 stars saved | 4.42/5 · 57 player ratings → 4.41/5 · 58 player ratings
…
4 stars saved | 4.38/5 · 64 player ratings → 4.37/5 · 65 player ratings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ratings&lt;/code&gt; table has a voter id, a score, a hashed IP and timestamps, but &lt;strong&gt;no install id and no test flag&lt;/strong&gt;. My cleanup script had no way to find these rows, and every vote moved the public average shown on every page and in the structured data.&lt;/p&gt;
&lt;p&gt;Cleaning it up carefully:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;List the newest rows, read-only. Eight rows from the same IP hash, alternating vi / en, all 4 stars, inside a three-minute window, matching the four Chromium and four WebKit runs exactly. The vote just before them came from a different address and stayed.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Count before deleting&lt;/strong&gt;, with exactly the conditions the delete would use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT COUNT(*) FROM ratings
WHERE ip_hash LIKE 'a2c680bee7%' AND score = 4
  AND created_at BETWEEN strftime('%s','2026-10-03 13:50:00')*1000
                     AND strftime('%s','2026-10-03 13:53:30')*1000;
-- 8
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the delete with the same &lt;code&gt;WHERE&lt;/code&gt;: &lt;code&gt;changes: 8&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Check the result: back to 57 votes and 4.42. The public summary is cached at the edge for 5 minutes, so the API caught up a few minutes later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then I made sure it can't happen the same way twice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The rating check now stubs the &lt;code&gt;POST&lt;/code&gt; when it runs on production and reads the live summary for its assertions. The real vote is tested only on the local stack.&lt;/li&gt;
&lt;li&gt;The testing rules say it in one line: &lt;em&gt;a test never votes on production&lt;/em&gt;, because a rating can't be tagged or found afterwards.&lt;/li&gt;
&lt;li&gt;The script was run once more against production with the stub: 24 / 24 checks, still 57 votes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-i-d-tell-myself-a-month-ago&quot;&gt;What I'd tell myself a month ago&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block analytics in the browser, not in a dashboard filter.&lt;/strong&gt; GA4's internal-traffic filters only apply from the day you turn them on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tag at the source.&lt;/strong&gt; One request header, one column, and every derived row can be traced back to it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make cleanup a script with a count, not a habit.&lt;/strong&gt; Exit non-zero if anything is left.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List every table a test can write to.&lt;/strong&gt; Any table that can't be traced back to a tagged record needs its writes stubbed on production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Count before you delete on production&lt;/strong&gt;, with exactly the &lt;code&gt;WHERE&lt;/code&gt; you're going to use.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The game is free in the browser at &lt;a rel=&quot;nofollow&quot; href=&quot;https://cavienchien.net/en/game/&quot;&gt;cavienchien.net&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30010/testing-on-production-without-polluting-your-data-and-the-day-my-tests-voted-8-times</guid>
<pubDate>Tue, 06 Oct 2026 13:36:20 +0000</pubDate>
</item>
<item>
<title>Unstructured Data Analytics Tools Unlock Hidden Business Insights</title>
<link>https://coderlegion.com/27385/unstructured-data-analytics-tools-unlock-hidden-business-insights</link>
<description>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Businesses generate enormous amounts of information outside traditional databases. Customer emails, PDFs, social media conversations, images, videos, call recordings, documents, reviews, and machine-generated content can all contain valuable information, yet much of it cannot be analyzed easily using conventional reporting systems.&lt;/p&gt;
&lt;p&gt;This is where unstructured data analytics tools become increasingly important. Instead of treating documents, text, images, and other non-tabular information as isolated files, modern analytics technologies can process these sources and turn them into useful business intelligence.&lt;/p&gt;
&lt;p&gt;The growing interest in unstructured data analytics tools reflects a broader change in how organizations approach data. Companies are no longer relying only on structured databases for decision-making. They are looking for ways to combine structured and unstructured information to understand customers, operations, risks, and market trends more completely.&lt;/p&gt;
&lt;h2&gt;Understanding Unstructured Data&lt;/h2&gt;
&lt;p&gt;Structured data typically follows a predefined format, such as rows and columns in a relational database. Unstructured data does not follow the same predictable structure. It can include emails, contracts, presentations, photographs, videos, audio recordings, support tickets, web content, and social media posts.&lt;/p&gt;
&lt;p&gt;The challenge is not simply storing these files. The real challenge is extracting useful meaning from them.&lt;/p&gt;
&lt;p&gt;Modern &lt;a rel=&quot;nofollow&quot; href=&quot;https://pentaho.com/insights/blogs/unstructured-data-management-strategies-tools-and-enterprise-solutions/&quot;&gt;unstructured data analytics tools&lt;/a&gt; use technologies such as natural language processing, machine learning, artificial intelligence, search, text mining, and computer vision to identify information within different formats. These capabilities can help organizations find relationships and patterns that would otherwise remain hidden.&lt;/p&gt;
&lt;p&gt;For example, a retailer could analyze customer reviews and support conversations to identify recurring complaints. A financial organization could examine documents and communications to identify potential compliance concerns. A manufacturer could combine equipment records, maintenance documents, and sensor information to improve operational analysis.&lt;/p&gt;
&lt;h2&gt;How Unstructured Data Analytics Tools Create Business Value&lt;/h2&gt;
&lt;p&gt;The value of unstructured data analytics tools comes from their ability to convert difficult-to-use information into actionable insights.&lt;/p&gt;
&lt;p&gt;Consider a company receiving thousands of customer messages every month. Reading every message manually is slow and inconsistent. An analytics platform can process the text, identify recurring subjects, classify sentiment, and reveal common issues.&lt;/p&gt;
&lt;p&gt;The same principle applies to documents. Instead of searching through thousands of PDFs individually, unstructured data analytics tools can extract relevant fields, categorize documents, identify keywords, and make information easier to retrieve.&lt;/p&gt;
&lt;p&gt;This creates a connection between raw information and business decisions. Analysts can spend less time locating information and more time interpreting what it means.&lt;/p&gt;
&lt;h2&gt;The Role of AI and Machine Learning&lt;/h2&gt;
&lt;p&gt;Artificial intelligence has significantly expanded the capabilities of unstructured data analytics tools. Traditional keyword searches can locate specific terms, but AI-based systems can understand relationships, context, classifications, and patterns across large collections of information.&lt;/p&gt;
&lt;p&gt;Natural language processing can help interpret written content, while machine learning can identify recurring patterns. Computer vision can analyze visual information, and speech-processing technologies can convert conversations into searchable text.&lt;/p&gt;
&lt;p&gt;Generative AI is also influencing this area. Once unstructured information has been properly ingested, cleaned, classified, and enriched with useful metadata, AI systems can use it for summarization, question answering, retrieval, and knowledge discovery.&lt;/p&gt;
&lt;p&gt;However, AI does not eliminate the need for sound data management. Poor-quality source information can still produce unreliable results. Effective unstructured data analytics tools therefore need to work as part of a broader data pipeline rather than operate as isolated applications.&lt;/p&gt;
&lt;h2&gt;Connecting Different Data Sources&lt;/h2&gt;
&lt;p&gt;One major advantage of unstructured data analytics tools is their ability to bring information from multiple sources into an analytical workflow.&lt;/p&gt;
&lt;p&gt;An enterprise might have customer information in a &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.salesforce.com/crm/what-is-crm/&quot;&gt;CRM platform&lt;/a&gt;, transaction records in databases, contracts stored as PDFs, conversations in communication platforms, and product feedback across websites and social networks. Looking at each source independently creates information silos.&lt;/p&gt;
&lt;p&gt;Data integration can help bring these sources together. Once information is accessible through a common analytical environment, teams can compare structured records with unstructured content and develop a more complete understanding of a business problem.&lt;/p&gt;
&lt;p&gt;For example, transaction data might show that sales declined in a particular region. Customer feedback could explain why. Combining both sources gives decision-makers context that a traditional sales report alone may not provide.&lt;/p&gt;
&lt;h2&gt;Choosing the Right Analytics Approach&lt;/h2&gt;
&lt;p&gt;Not every organization needs the same capabilities from unstructured data analytics tools. The right choice depends on the type of information being analyzed, data volume, technical requirements, security expectations, and business objectives.&lt;/p&gt;
&lt;p&gt;A company primarily working with text may prioritize natural language processing and semantic search. An organization handling large document collections may need document extraction, classification, indexing, and metadata management. Businesses processing multimedia information may require image, video, or audio analysis.&lt;/p&gt;
&lt;p&gt;Scalability is another important consideration. A tool that works well for a small collection of documents may not perform efficiently when data volumes increase significantly. Integration capabilities also matter because analytics rarely happens in isolation from existing databases, cloud platforms, business applications, and data pipelines.&lt;/p&gt;
&lt;p&gt;Security and governance should be evaluated from the beginning. Sensitive documents and communications may contain confidential business information or personally identifiable information. Strong access controls, data protection, auditing, and appropriate retention policies are therefore essential considerations when evaluating unstructured data analytics tools.&lt;/p&gt;
&lt;h2&gt;Preparing Data for Reliable Analysis&lt;/h2&gt;
&lt;p&gt;The quality of analytical results depends heavily on the quality of the underlying information. Simply placing thousands of documents into an analytics platform does not automatically make them useful.&lt;/p&gt;
&lt;p&gt;Organizations should consider how data is collected, classified, cleaned, enriched, indexed, and governed. Duplicate files, incomplete records, outdated documents, inconsistent metadata, and irrelevant content can reduce analytical accuracy.&lt;/p&gt;
&lt;p&gt;Effective unstructured data analytics tools should therefore support a broader workflow that prepares information before analysis. This preparation can include extracting text from documents, identifying entities, assigning metadata, removing unnecessary content, and creating searchable representations.&lt;/p&gt;
&lt;p&gt;Data quality becomes especially important when unstructured information is used for artificial intelligence applications. Reliable inputs provide a stronger foundation for analytics, machine learning, retrieval systems, and AI-generated outputs.&lt;/p&gt;
&lt;h2&gt;Moving Toward More Complete Business Intelligence&lt;/h2&gt;
&lt;p&gt;The growing adoption of unstructured data analytics tools represents a shift from traditional reporting toward broader information intelligence. Businesses are beginning to recognize that valuable insights are not limited to structured tables and &lt;a rel=&quot;nofollow&quot; href=&quot;https://help.deltek.com/Product/Vantagepoint/7.2/st_settings_dashdesigner_predefined_dashboards.html&quot;&gt;predefined dashboards&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Emails can reveal customer concerns. Documents can contain contractual information. Reviews can highlight product weaknesses. Images and videos can provide operational evidence. Conversations can reveal changing customer expectations.&lt;/p&gt;
&lt;p&gt;When these sources are analyzed alongside structured business data, organizations can develop a richer understanding of what is happening and why.&lt;/p&gt;
&lt;p&gt;The future of enterprise analytics will increasingly depend on the ability to work across different data formats. Unstructured data analytics tools can help close the gap between information that is easy to query and information that contains valuable but difficult-to-access context.&lt;/p&gt;
&lt;p&gt;Conclusion&lt;/p&gt;
&lt;p&gt;Unstructured information is no longer simply an untapped collection of files. With the right technology, it can become an important source of business intelligence.&lt;/p&gt;
&lt;p&gt;Modern unstructured data analytics tools help organizations process text, documents, images, audio, video, and other complex information while connecting these sources to broader analytical workflows. Their effectiveness depends not only on advanced AI capabilities but also on data quality, integration, governance, security, and a clear business objective.&lt;/p&gt;
&lt;p&gt;Organizations that build a thoughtful approach to unstructured information can uncover insights that conventional analytics may overlook. As data volumes continue to grow and AI becomes more deeply integrated into enterprise workflows, unstructured data analytics tools will play an increasingly important role in turning hidden information into practical business knowledge.&lt;/p&gt;
&amp;lt;!-- Comments are visible in the HTML source only --&amp;gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27385/unstructured-data-analytics-tools-unlock-hidden-business-insights</guid>
<pubDate>Wed, 16 Sep 2026 10:30:31 +0000</pubDate>
</item>
<item>
<title>Your AI Visibility Score May Be Wrong: A Reproducible Testing Protocol</title>
<link>https://coderlegion.com/26904/your-ai-visibility-score-may-be-wrong-a-reproducible-testing-protocol</link>
<description>&lt;p&gt;&lt;strong&gt;By Jarno Saarimies, Founder of AEOvara.&lt;/strong&gt; This is Part 3 of the series &lt;em&gt;How to Build a Repeatable AI Search Visibility Benchmark&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Ask ChatGPT the same commercial question twice and you may get two different shortlists. Change “best” to “most reliable,” and the shortlist may change again. Run the prompt in a continuing conversation, in another country, or after a model update, and you may no longer be measuring the same system.&lt;/p&gt;
&lt;p&gt;That creates an awkward problem for AI visibility reporting: a dashboard can show a precise score even when the experiment behind it is not precise.&lt;/p&gt;
&lt;p&gt;This does not make AI visibility measurement useless. It means the score must be treated as an estimate produced by a documented test—not as an objective ranking retrieved from a fixed index.&lt;/p&gt;
&lt;p&gt;In Parts 1 and 2 of this series, I explained how to build a prompt corpus and track mentions, citations, competitors, and answer quality. This article addresses the harder question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When is a change in AI visibility evidence, and when is it only measurement noise?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The answer requires more than repeating prompts. You need to control the test environment, preserve the raw evidence, quantify uncertainty, and separate effects that dashboards often collapse into one number.&lt;/p&gt;
&lt;h3 id=&quot;first-identify-what-actually-changed&quot;&gt;First, identify what actually changed&lt;/h3&gt;
&lt;p&gt;Suppose your mention rate rises from 30% to 40%. At least three different things could have produced that result.&lt;/p&gt;
&lt;h4 id=&quot;1-the-brand-changed&quot;&gt;1. The brand changed&lt;/h4&gt;
&lt;p&gt;You published new evidence, earned independent coverage, corrected an entity description, improved a relevant page, or became more widely discussed. This is the effect an AEO or GEO programme is trying to measure.&lt;/p&gt;
&lt;h4 id=&quot;2-the-engine-changed&quot;&gt;2. The engine changed&lt;/h4&gt;
&lt;p&gt;The provider released a new model snapshot, changed retrieval behaviour, refreshed an index, altered citation rules, or modified the product. OpenAI explicitly states that model outputs are variable and that prompting behaviour can change between model snapshots. For API applications, it recommends pinned model versions and evals when consistency matters.&lt;/p&gt;
&lt;p&gt;Consumer AI products rarely give researchers complete control over those variables. A visible product name such as “ChatGPT” is not a permanent experimental condition.&lt;/p&gt;
&lt;h4 id=&quot;3-the-sample-changed&quot;&gt;3. The sample changed&lt;/h4&gt;
&lt;p&gt;The wording, session history, location, language, login state, retrieval mode, time, or number of repetitions changed. Even if the website and model stayed untouched, the measured score could move.&lt;/p&gt;
&lt;p&gt;If your log cannot distinguish these explanations, it cannot support a causal claim such as “our optimization increased AI visibility by 10%.” It can only say that the observed answers changed.&lt;/p&gt;
&lt;p&gt;That distinction sounds cautious. It is also what makes the result credible.&lt;/p&gt;
&lt;h3 id=&quot;a-prompt-is-not-a-keyword&quot;&gt;A prompt is not a keyword&lt;/h3&gt;
&lt;p&gt;Traditional rank tracking usually observes a defined query, location, device, and search engine. AI prompts carry more semantic degrees of freedom.&lt;/p&gt;
&lt;p&gt;Consider these questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What are the best technical SEO consultancies for a Finnish SME?&lt;/li&gt;
&lt;li&gt;Which technical SEO consultancy is most reliable for a small Finnish company?&lt;/li&gt;
&lt;li&gt;Who can help a Finnish business become visible in AI-generated answers?&lt;/li&gt;
&lt;li&gt;Recommend an affordable Finnish specialist in SEO and AI search optimization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They occupy the same commercial neighbourhood, but they are not interchangeable samples. “Best,” “reliable,” “affordable,” and “AI search optimization” introduce different selection criteria.&lt;/p&gt;
&lt;p&gt;A 2026 controlled study by Jan Ehrlinspiel, Malte Landwehr, and Tomek Rudzki examined 37,804 responses across five AI answer engines. It found that prompt format and archetype can shift visibility baselines, with the clearest sensitivity appearing in unbranded, middle-funnel commercial prompts. The authors are affiliated with Peec AI, so that commercial relationship should be disclosed, but the methodological lesson is useful: one wording cannot represent an entire buyer intent.&lt;/p&gt;
&lt;p&gt;Freezing a prompt list is therefore necessary, but not sufficient. A frozen list gives you repeatability for those exact prompts. It does not prove that the list represents the full market.&lt;/p&gt;
&lt;h3 id=&quot;use-prompt-families-not-a-bag-of-convenient-questions&quot;&gt;Use prompt families, not a bag of convenient questions&lt;/h3&gt;
&lt;p&gt;A stronger benchmark separates two layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Core prompts&lt;/strong&gt; remain unchanged so trends can be compared over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt variants&lt;/strong&gt; test whether the result survives realistic changes in wording.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For each buyer intent, write one core prompt and two or three variants before checking which version favours your brand. This prevents an easy form of unconscious cherry-picking: keeping the wording that produces the most flattering result.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Buyer intent &lt;/th&gt;&lt;th&gt;Core prompt &lt;/th&gt;&lt;th&gt;Controlled variants&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Category discovery &lt;/td&gt;&lt;td&gt;What are the best AEO agencies in Finland? &lt;/td&gt;&lt;td&gt;most reliable; established; suitable for SMEs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Problem solving &lt;/td&gt;&lt;td&gt;How can a Finnish SME improve visibility in AI answers? &lt;/td&gt;&lt;td&gt;get cited by ChatGPT; appear in AI search; improve generative search visibility&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Supplier selection &lt;/td&gt;&lt;td&gt;Who should I hire for an AI visibility audit in Finland? &lt;/td&gt;&lt;td&gt;independent specialist; consultancy; local expert&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Do not average branded questions such as “What is AEOvara?” into the same score as unbranded discovery questions. A branded prompt measures recognition and description accuracy. An unbranded prompt measures discovery. Combining them can make a weak discovery result look healthy.&lt;/p&gt;
&lt;h3 id=&quot;record-the-test-environment-like-an-experiment&quot;&gt;Record the test environment like an experiment&lt;/h3&gt;
&lt;p&gt;Every observation should include enough metadata for another person to understand what was tested:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exact prompt text;&lt;/li&gt;
&lt;li&gt;platform and visible product mode;&lt;/li&gt;
&lt;li&gt;model identifier, when exposed;&lt;/li&gt;
&lt;li&gt;web search or grounding status, when exposed;&lt;/li&gt;
&lt;li&gt;date, time, language, and country;&lt;/li&gt;
&lt;li&gt;signed-in or signed-out state;&lt;/li&gt;
&lt;li&gt;clean session or continuing conversation;&lt;/li&gt;
&lt;li&gt;personalization or memory state, when known;&lt;/li&gt;
&lt;li&gt;repetition number;&lt;/li&gt;
&lt;li&gt;full answer and cited URLs;&lt;/li&gt;
&lt;li&gt;scorer name or scoring-rule version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“ChatGPT, September” is not adequate provenance. Neither is a cropped screenshot that hides the prompt, date, citations, and product mode.&lt;/p&gt;
&lt;p&gt;API-based monitoring improves control, but it creates a different test. An API model with a pinned snapshot and explicit search configuration is not necessarily equivalent to the consumer interface used by customers. Report API observations as API observations. Do not quietly label them “what users see in ChatGPT.”&lt;/p&gt;
&lt;h3 id=&quot;measure-a-rate-and-show-its-uncertainty&quot;&gt;Measure a rate—and show its uncertainty&lt;/h3&gt;
&lt;p&gt;If a brand appears in 3 of 5 runs, the observed mention rate is 60%. That does not mean the true probability of a mention is known to be 60%.&lt;/p&gt;
&lt;p&gt;With only five binary observations, a 95% Wilson confidence interval is approximately &lt;strong&gt;23% to 88%&lt;/strong&gt;. The interval is wide because the sample is small.&lt;/p&gt;
&lt;p&gt;If the brand appears in 30 of 50 comparable runs, the observed rate is still 60%, but the corresponding interval narrows to approximately &lt;strong&gt;46% to 72%&lt;/strong&gt;. More observations have not changed the headline percentage; they have changed how much confidence we should place in it.&lt;/p&gt;
&lt;p&gt;For a binary mention metric:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mention rate = mentions / comparable runs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Report the numerator and denominator—not just the percentage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ChatGPT web search: 12/20 mentions, 60%
95% Wilson interval: approximately 39%–78%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interval is not decoration. It helps prevent claims that the evidence cannot support.&lt;/p&gt;
&lt;p&gt;If period A is 6/20 and period B is 8/20, the dashboard has moved from 30% to 40%. But with samples that small, the difference may be ordinary variation. Call it an observed increase, not a confirmed improvement.&lt;/p&gt;
&lt;h3 id=&quot;do-not-use-one-noise-budget-for-every-metric&quot;&gt;Do not use one noise budget for every metric&lt;/h3&gt;
&lt;p&gt;Mention, citation, prominence, sentiment, and factual accuracy behave differently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mention&lt;/strong&gt; is binary and relatively easy to score.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Citation&lt;/strong&gt; requires a link or source reference and should be stored at URL level.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prominence&lt;/strong&gt; depends on an explicit rule: first named, top three, recommended, or merely included.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sentiment&lt;/strong&gt; is a classification judgement and is usually noisier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accuracy&lt;/strong&gt; must be checked against a dated source of truth.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A 2026 production-data paper on GEO measurement reported that sentiment was 6.7 times noisier than mention in its dataset. That is one vendor’s observational result, not a universal constant, but it illustrates why a single blended “AI visibility score” can hide more than it reveals.&lt;/p&gt;
&lt;p&gt;Publish the components separately. A brand can have a rising mention rate while its citation rate falls or its description becomes inaccurate. Those are materially different outcomes.&lt;/p&gt;
&lt;h3 id=&quot;establish-a-noise-floor-before-claiming-a-win&quot;&gt;Establish a noise floor before claiming a win&lt;/h3&gt;
&lt;p&gt;Before changing the site, run the unchanged benchmark over several measurement windows. This creates a baseline for natural movement.&lt;/p&gt;
&lt;p&gt;Then document the intervention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;page or entity changed;&lt;/li&gt;
&lt;li&gt;exact publication time;&lt;/li&gt;
&lt;li&gt;URLs affected;&lt;/li&gt;
&lt;li&gt;intended prompt family;&lt;/li&gt;
&lt;li&gt;no other known campaign, migration, or major PR event;&lt;/li&gt;
&lt;li&gt;first date the changed page was observed or cited.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Continue measuring with the same core prompts and conditions. Compare post-change movement with pre-change variation.&lt;/p&gt;
&lt;p&gt;This is still not a perfect causal experiment. AI engines can update during the same period, and third-party sources can change independently. But it is much stronger than editing ten pages and attributing the next dashboard increase to whichever tactic sounds most impressive.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Evidence &lt;/th&gt;&lt;th&gt;Defensible wording&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;One answer changed &lt;/td&gt;&lt;td&gt;“The brand appeared in this observation.”&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Repeated rate increased &lt;/td&gt;&lt;td&gt;“Observed mention frequency increased in this prompt set.”&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Increase exceeds baseline variation &lt;/td&gt;&lt;td&gt;“The change is larger than the noise previously observed in this test.”&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Controlled intervention with comparison group &lt;/td&gt;&lt;td&gt;“The intervention is a plausible cause of the measured difference.”&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Repeated controlled result across engines and periods &lt;/td&gt;&lt;td&gt;“The evidence supports a robust effect under the tested conditions.”&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Avoid jumping from the first row to the last.&lt;/p&gt;
&lt;h3 id=&quot;build-a-holdout-set-to-catch-overfitting&quot;&gt;Build a holdout set to catch overfitting&lt;/h3&gt;
&lt;p&gt;If you optimize pages against the exact prompts used for reporting, you may improve performance on the test without improving broader buyer discovery.&lt;/p&gt;
&lt;p&gt;Reserve 20–30% of your prompt families as a holdout set. Do not use those prompts to decide what to write or edit. Run them only at defined validation points.&lt;/p&gt;
&lt;p&gt;If the core tracking prompts improve but the holdout prompts do not, you may have optimized to the benchmark rather than the market. This is the AI-search equivalent of teaching to the test.&lt;/p&gt;
&lt;p&gt;The holdout set should remain commercially relevant. Random prompts do not create scientific rigour; they only create irrelevant data.&lt;/p&gt;
&lt;h3 id=&quot;publish-the-method-including-its-limitations&quot;&gt;Publish the method, including its limitations&lt;/h3&gt;
&lt;p&gt;E-E-A-T is not a numeric score that can be added with an author box or a block of schema. For readers—and for systems trying to evaluate a source—trust is easier to establish when claims have visible provenance.&lt;/p&gt;
&lt;p&gt;A credible public benchmark should state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;who ran the test;&lt;/li&gt;
&lt;li&gt;what was measured;&lt;/li&gt;
&lt;li&gt;the exact period and platforms;&lt;/li&gt;
&lt;li&gt;how prompts were selected;&lt;/li&gt;
&lt;li&gt;how many observations were collected;&lt;/li&gt;
&lt;li&gt;how answers were scored;&lt;/li&gt;
&lt;li&gt;whether the evaluator had a commercial interest;&lt;/li&gt;
&lt;li&gt;what changed since the previous version;&lt;/li&gt;
&lt;li&gt;what the data cannot prove.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is the approach used in the continuously updated &lt;a rel=&quot;nofollow&quot; href=&quot;https://aeovara.fi/ai-visibility-benchmark-suomi-2026/&quot;&gt;AEOvara AI Visibility Benchmark Suomi 2026&lt;/a&gt;: the platform-level results, prompt scope, cadence, and limitations are part of the evidence, not hidden behind a proprietary score.&lt;/p&gt;
&lt;p&gt;Publishing an unfavourable or inconclusive result can strengthen the work. A benchmark that only produces success stories looks like marketing. A benchmark that preserves negative findings begins to look like a useful dataset.&lt;/p&gt;
&lt;h3 id=&quot;a-minimum-viable-reproducibility-checklist&quot;&gt;A minimum viable reproducibility checklist&lt;/h3&gt;
&lt;p&gt;Before publishing the next percentage, ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are branded and unbranded prompts reported separately?&lt;/li&gt;
&lt;li&gt;Were prompts written before seeing which ones favoured the brand?&lt;/li&gt;
&lt;li&gt;Are the exact wording and realistic variants preserved?&lt;/li&gt;
&lt;li&gt;Were runs made in clean, comparable sessions?&lt;/li&gt;
&lt;li&gt;Are platform, model, retrieval mode, location, and date recorded?&lt;/li&gt;
&lt;li&gt;Does every percentage show its numerator and denominator?&lt;/li&gt;
&lt;li&gt;Is uncertainty visible when the sample is small?&lt;/li&gt;
&lt;li&gt;Are mentions, citations, prominence, sentiment, and accuracy separate?&lt;/li&gt;
&lt;li&gt;Was a pre-change noise floor established?&lt;/li&gt;
&lt;li&gt;Is there a holdout prompt set?&lt;/li&gt;
&lt;li&gt;Are raw answers and cited URLs archived?&lt;/li&gt;
&lt;li&gt;Are limitations and commercial relationships disclosed?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If several answers are “no,” the result may still be useful as an observation. It should not be presented as a stable market measurement.&lt;/p&gt;
&lt;h3 id=&quot;the-bottom-line&quot;&gt;The bottom line&lt;/h3&gt;
&lt;p&gt;AI visibility is measurable, but not as a single universal rank.&lt;/p&gt;
&lt;p&gt;What you can measure is a defined brand’s behaviour across a documented sample of prompts, engines, configurations, places, and times. Repeat that protocol and you can build evidence. Hide those conditions behind a polished score and you create false precision.&lt;/p&gt;
&lt;p&gt;The strongest AI-search report is not the one with the most decimal places. It is the one another practitioner could challenge, reproduce, and learn from.&lt;/p&gt;
&lt;p&gt;That is also the kind of source worth citing.&lt;/p&gt;
&lt;h3 id=&quot;faq&quot;&gt;FAQ&lt;/h3&gt;
&lt;h4 id=&quot;how-many-times-should-i-run-each-ai-search-prompt-&quot;&gt;How many times should I run each AI-search prompt?&lt;/h4&gt;
&lt;p&gt;There is no universal number. Three to five repetitions can expose obvious variability and provide a directional baseline, but a small sample does not produce a precise probability. Report the raw count and an uncertainty interval, then increase repetitions for important or volatile prompt families.&lt;/p&gt;
&lt;h4 id=&quot;should-i-freeze-my-prompts-or-update-them-&quot;&gt;Should I freeze my prompts or update them?&lt;/h4&gt;
&lt;p&gt;Do both in separate layers. Keep a stable core set for trend comparison, and maintain a versioned discovery set that reflects new buyer language, products, and competitors. Never silently replace old prompts and continue the same trend line.&lt;/p&gt;
&lt;h4 id=&quot;can-i-compare-an-api-result-with-the-consumer-ai-product-&quot;&gt;Can I compare an API result with the consumer AI product?&lt;/h4&gt;
&lt;p&gt;Only with an explicit caveat. The API may use a different model snapshot, system configuration, search policy, location input, or citation interface. Treat it as a separate measurement surface unless equivalence has been demonstrated.&lt;/p&gt;
&lt;h4 id=&quot;is-a-higher-mention-rate-proof-that-my-content-change-worked-&quot;&gt;Is a higher mention rate proof that my content change worked?&lt;/h4&gt;
&lt;p&gt;No. It is evidence that the observed rate changed. A causal claim requires stronger controls: stable test conditions, a pre-change noise floor, documented timing, enough observations, and preferably a comparison or holdout group.&lt;/p&gt;
&lt;h4 id=&quot;does-a-citation-mean-the-ai-answer-used-my-page-correctly-&quot;&gt;Does a citation mean the AI answer used my page correctly?&lt;/h4&gt;
&lt;p&gt;Not necessarily. Store and inspect the cited URL, the claim it appears to support, and whether the answer accurately represents the source. Citation presence and citation correctness are separate metrics.&lt;/p&gt;
&lt;h3 id=&quot;sources-and-further-reading&quot;&gt;Sources and further reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;OpenAI, &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.openai.com/api/reference/overview#backwards-compatibility&quot;&gt;API backwards compatibility and model variability&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Ehrlinspiel, Landwehr &amp;amp; Rudzki, &lt;a rel=&quot;nofollow&quot; href=&quot;https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6914539&quot;&gt;Prompt Tracking Works, But Not as a One-Prompt Measurement System&lt;/a&gt;, 2026.&lt;/li&gt;
&lt;li&gt;Blago, &lt;a rel=&quot;nofollow&quot; href=&quot;https://arxiv.org/abs/2606.20065&quot;&gt;Generative Engine Optimization at Scale: Measuring Brand Visibility Across AI Search Engines&lt;/a&gt;, 2026. Treat the vendor-operated production dataset and its generalizability accordingly.&lt;/li&gt;
&lt;li&gt;Aggarwal et al., &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.1145/3637528.3671900&quot;&gt;GEO: Generative Engine Optimization&lt;/a&gt;, KDD 2024.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;About the author:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://aeovara.fi/asiantuntija/&quot;&gt;Jarno Saarimies&lt;/a&gt; is the founder of AEOvara, a Finnish consultancy focused on technical SEO, Answer Engine Optimization, Generative Engine Optimization, and reproducible AI-search visibility measurement. His work emphasizes documented methods, source verification, and claims that remain useful after the hype cycle moves on.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26904/your-ai-visibility-score-may-be-wrong-a-reproducible-testing-protocol</guid>
<pubDate>Fri, 11 Sep 2026 20:10:27 +0000</pubDate>
</item>
<item>
<title>How to Detect Airdrop Farmers on Polymarket Leaderboards</title>
<link>https://coderlegion.com/25756/how-to-detect-airdrop-farmers-on-polymarket-leaderboards</link>
<description>&lt;p&gt;A 90% win rate looks like proof of skill. On Polymarket it usually isn't. Of the 70,711 wallets currently showing a 90% or higher win rate, about 61%, or 43,179 of them, are airdrop farmers. They never took a real position. They bought outcomes that were already all but settled, collected a few cents, and repeated it thousands of times to farm volume and a spotless record.&lt;/p&gt;
&lt;p&gt;If you pick wallets to follow off a raw leaderboard, the math says you are probably copying one of them. Here is how to tell a farmer apart from a real trader.&lt;/p&gt;
&lt;h3 id=&quot;how-can-i-detect-airdrop-farmers-on-polymarket-leaderboards-&quot;&gt;How can I detect airdrop farmers on Polymarket leaderboards?&lt;/h3&gt;
&lt;p&gt;An airdrop farmer is a wallet with an average buy price above 95 cents. That single number is the fingerprint. Real traders take uncertain positions at a range of prices, so their average entry lands somewhere in the middle. A farmer's average sits near the top of the book, because the whole strategy is buying near-certain outcomes for the last sliver of profit. Sort a leaderboard by win rate, check the average entry price of the wallets at the top, and the farmers separate themselves.&lt;/p&gt;
&lt;p&gt;OrcaLayer does this automatically. Every ranking is farmer-filtered by default, so the wallets you see have taken real risk. The exact rules are on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://orcalayer.com/farmer-filter&quot;&gt;Farmer Filter&lt;/a&gt; page and in the full &lt;a rel=&quot;nofollow&quot; href=&quot;https://orcalayer.com/methodology&quot;&gt;methodology&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-an-airdrop-farmer-actually-does&quot;&gt;What an airdrop farmer actually does&lt;/h3&gt;
&lt;p&gt;The play is simple and low risk. Find a market that will clearly resolve one way. Buy the winning side for almost its full value. Wait for settlement.&lt;/p&gt;
&lt;p&gt;Say a market is going to resolve Yes. The farmer buys Yes at 96 cents. It settles at 1 dollar. That is 4 cents of profit on the dollar, near guaranteed. Do it once and it is pocket change. Do it across thousands of markets and you farm two things at the same time: a near perfect win rate, and the trading volume that qualifies for airdrop and rewards programs. The skill involved is close to zero.&lt;/p&gt;
&lt;p&gt;This is why raw leaderboards mislead. Sort any unfiltered Polymarket ranking by win rate and the top is a wall of 95 to 100 percent records. It reads like a roster of geniuses to copy. Most of them are farmers, and a 96 cent entry is not something you can profitably copy anyway.&lt;/p&gt;
&lt;h3 id=&quot;the-three-signals-in-detail&quot;&gt;The three signals, in detail&lt;/h3&gt;
&lt;p&gt;Farmers are one of three kinds of leaderboard noise. OrcaLayer separates each one on its own, so any of them can be filtered independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Farmers.&lt;/strong&gt; Average buy price above 95 cents. You do not accumulate an average that high by taking genuine, uncertain positions. Flagged and excluded from Smart Money.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bots.&lt;/strong&gt; More than 500,000 trades and an average trade size under 20 dollars. That is the signature of an automated market-making or micro-arbitrage program, not a person taking real positions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Router contracts.&lt;/strong&gt; Polymarket's own protocol contracts, the CTF and NegRisk exchange routers, sit on both sides of ordinary trades. Left in, a router can look like a single hyper-active insider wallet. Excluded platform-wide.&lt;/p&gt;
&lt;p&gt;Across every trader OrcaLayer has indexed, about 114,000 are farmers, roughly 1 in 12, and they cluster exactly where they do the most damage: at the top of an unfiltered win-rate ranking.&lt;/p&gt;
&lt;h3 id=&quot;raw-leaderboard-vs-farmer-filtered&quot;&gt;Raw leaderboard vs farmer-filtered&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Raw Polymarket leaderboard &lt;/th&gt;&lt;th&gt;OrcaLayer, farmer-filtered&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Top sorted by win rate &lt;/td&gt;&lt;td&gt;Wall of 95 to 100 percent records &lt;/td&gt;&lt;td&gt;Wallets that took real positions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Farmers &lt;/td&gt;&lt;td&gt;Dominate the top &lt;/td&gt;&lt;td&gt;Flagged and removed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bots and routers &lt;/td&gt;&lt;td&gt;Counted as traders &lt;/td&gt;&lt;td&gt;Detected and excluded&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Can you copy the entries &lt;/td&gt;&lt;td&gt;Rarely, most sit near 1 dollar &lt;/td&gt;&lt;td&gt;Yes, real entry prices&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Once the farmers come out, the &quot;flawless&quot; leaderboard mostly disappears. What is left is the smaller, more honest set OrcaLayer calls Smart Money: a wallet with a 55 percent or higher market win rate, positive total profit, and no farmer flag. Just under 198,000 wallets qualify.&lt;/p&gt;
&lt;h3 id=&quot;how-to-check-a-wallet-yourself&quot;&gt;How to check a wallet yourself&lt;/h3&gt;
&lt;p&gt;If you want to vet a wallet by hand:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Look at its average entry price. Above 95 cents is a farmer tell.&lt;/li&gt;
&lt;li&gt;Look at trade count and average size. Hundreds of thousands of trades at a few dollars each is a bot.&lt;/li&gt;
&lt;li&gt;Ask whether the wins came from real, uncertain positions or from near-settled ones.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or open the &lt;a rel=&quot;nofollow&quot; href=&quot;https://orcalayer.com/leaderboard&quot;&gt;OrcaLayer leaderboard&lt;/a&gt;, which applies all three checks by default and shows you which wallets took real positions. The full data story behind the filter is in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://orcalayer.com/blog/470m-trades-polymarket-farmers&quot;&gt;470M-trade farmer analysis&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently asked questions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;How can I detect airdrop farmers on Polymarket leaderboards?&lt;/strong&gt; Check a wallet's average buy price. Farmers sit above 95 cents, because they buy near-settled outcomes for a few cents of guaranteed profit. OrcaLayer flags every such wallet automatically and removes it from the rankings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What percentage of high win-rate wallets are farmers?&lt;/strong&gt; Of the 70,711 Polymarket wallets with a 90 percent or higher win rate, about 61 percent, or 43,179, are farmers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why do airdrop farmers have such high win rates?&lt;/strong&gt; Because they only buy outcomes that are already almost certain, often at 96 cents or more. It produces a near perfect record with almost no risk and almost no skill.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How does OrcaLayer remove farmers?&lt;/strong&gt; By three independent signals: an average buy price above 95 cents for farmers, over 500,000 trades with an average size under 20 dollars for bots, and Polymarket's own router contracts, which are excluded platform-wide. The thresholds are published in the methodology.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is left after farmers are removed?&lt;/strong&gt; Just under 198,000 wallets that OrcaLayer classifies as Smart Money: a 55 percent or higher market win rate, positive total profit, and no farmer flag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I copy a farmer's trades to make money?&lt;/strong&gt; No. Their edge is buying at 96 cents or more on near-settled markets, which leaves almost nothing for a follower and disappears once the market resolves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What average entry price signals a real Polymarket trader?&lt;/strong&gt; A genuine trader's average buy price tends to land in the middle of the book, because real positions are taken while the outcome is still uncertain. An average above 95 cents is the farmer tell, not the mark of a skilled trader.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://orcalayer.com/blog/how-to-detect-airdrop-farmers-polymarket&quot;&gt;orcalayer.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25756/how-to-detect-airdrop-farmers-on-polymarket-leaderboards</guid>
<pubDate>Sat, 29 Aug 2026 21:49:23 +0000</pubDate>
</item>
<item>
<title>Data Analytics Services: Transform Raw Data into Actionable Business Insights</title>
<link>https://coderlegion.com/23744/data-analytics-services-transform-raw-data-into-actionable-business-insights</link>
<description>&lt;p&gt;Turn data into a competitive advantage with end-to-end &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.damcogroup.com/data-analytics-services&quot;&gt;data analytics services&lt;/a&gt;&lt;/strong&gt;. From data strategy and integration to AI-powered analytics, reporting, visualization, and DataOps, organizations can uncover actionable insights that improve decision-making, optimize operations, and accelerate business growth. This infographic highlights the core capabilities, business benefits, and analytics workflow that help enterprises build a trusted, data-driven foundation for long-term success.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23744/data-analytics-services-transform-raw-data-into-actionable-business-insights</guid>
<pubDate>Wed, 29 Jul 2026 14:21:27 +0000</pubDate>
</item>
<item>
<title>Your API has visitors GA4 will never see</title>
<link>https://coderlegion.com/21271/your-api-has-visitors-ga4-will-never-see</link>
<description>&lt;p&gt;Are you building an API for AI agents? If so, you might be flying blind.&lt;/p&gt;
&lt;p&gt;Google Analytics 4 (GA4) is great for tracking human users with browsers, but it is completely blind to AI agents, bots, and server-to-server API calls.&lt;/p&gt;
&lt;p&gt;When an AI agent (like OpenClaw or a custom script) calls your MCP endpoint or REST API, it does not execute JavaScript. There is no &lt;code&gt;gtag.js&lt;/code&gt; running. You see nothing in GA4.&lt;/p&gt;
&lt;p&gt;You are effectively serving a silent, invisible army of customers that you cannot track, understand, or optimize for.&lt;/p&gt;
&lt;h4 id=&quot;the-problem-ga4-is-designed-for-humans-not-bots&quot;&gt;The Problem: GA4 is designed for humans, not bots&lt;/h4&gt;
&lt;p&gt;GA4 relies on client-side JavaScript to send tracking data to Google. This works well for websites.&lt;/p&gt;
&lt;p&gt;It does not work for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API calls from AI agents.&lt;/li&gt;
&lt;li&gt;Requests from curl, Python, or Node.js scripts.&lt;/li&gt;
&lt;li&gt;MCP (Streamable HTTP) traffic.&lt;/li&gt;
&lt;li&gt;Webhook calls.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your most important customers (the agents and bots that pay you) are invisible.&lt;/p&gt;
&lt;h4 id=&quot;the-solution-track-at-the-server-level&quot;&gt;The Solution: Track at the server level&lt;/h4&gt;
&lt;p&gt;Instead of relying on GA4, you can track everything from your own server. Every incoming request has headers and metadata that you can log and analyze.&lt;/p&gt;
&lt;p&gt;You can build a simple logging middleware that records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Endpoint path.&lt;/li&gt;
&lt;li&gt;User-Agent string.&lt;/li&gt;
&lt;li&gt;IP address.&lt;/li&gt;
&lt;li&gt;Response time.&lt;/li&gt;
&lt;li&gt;Status code.&lt;/li&gt;
&lt;li&gt;API key or auth identifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, you can parse the logs to understand who is calling your API and how they are using it.&lt;/p&gt;
&lt;h4 id=&quot;what-you-can-learn&quot;&gt;What you can learn&lt;/h4&gt;
&lt;p&gt;By analyzing server logs, you can answer questions that GA4 never can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many AI agents are using your API?&lt;/li&gt;
&lt;li&gt;What are the most popular endpoints?&lt;/li&gt;
&lt;li&gt;Which user-agents are making the most requests?&lt;/li&gt;
&lt;li&gt;Are there any abusive or unusual patterns?&lt;/li&gt;
&lt;li&gt;Where are your agent users coming from? (IP geolocation)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;a-practical-example&quot;&gt;A practical example&lt;/h4&gt;
&lt;p&gt;I built a project called &lt;strong&gt;AgentShare&lt;/strong&gt; (&lt;a rel=&quot;nofollow&quot; href=&quot;https://agentshare.dev&quot;&gt;agentshare.dev&lt;/a&gt;), a platform that provides structured price data for AI agents via REST and MCP.&lt;/p&gt;
&lt;p&gt;Instead of relying on GA4, I built a log analysis system on the server. I now get daily reports on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The number of agent requests.&lt;/li&gt;
&lt;li&gt;The top endpoints.&lt;/li&gt;
&lt;li&gt;The most active user-agents.&lt;/li&gt;
&lt;li&gt;Anomalies and error rates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This data helps me make product decisions and understand my real customers.&lt;/p&gt;
&lt;p&gt;I wrote about this journey in detail here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/anhmtk/your-api-has-visitors-ga4-will-never-see-5hjj&quot;&gt;&quot;Your API has visitors GA4 will never see&quot;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;try-it-yourself&quot;&gt;Try it yourself&lt;/h4&gt;
&lt;p&gt;If you are building an API for AI agents, do yourself a favor: stop relying on GA4 and start tracking at the server level.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add a logging middleware.&lt;/li&gt;
&lt;li&gt;Store logs in a database or a file.&lt;/li&gt;
&lt;li&gt;Write a simple script to analyze the logs.&lt;/li&gt;
&lt;li&gt;Set up a daily report.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You will be surprised by what you discover.&lt;/p&gt;
&lt;p&gt;Have you ever tracked AI agent traffic on your API? What did you learn?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21271/your-api-has-visitors-ga4-will-never-see</guid>
<pubDate>Wed, 24 Jun 2026 09:10:54 +0000</pubDate>
</item>
<item>
<title>Causality Engine</title>
<link>https://coderlegion.com/21053/causality-engine</link>
<description>&lt;p&gt;Causality Engine is a marketing attribution platform that uses causal inference, not correlation, to show e-commerce brands which marketing channels actually drive sales.&lt;/p&gt;
&lt;p&gt;Traditional tools like Google Analytics credit the last click. If a customer saw an Instagram ad, opened an email, then Googled your brand name, Google gets 100% of the credit. The ad and the email get zero. This causes marketers to systematically overspend on bottom-funnel channels and underfund the ones that create demand.&lt;/p&gt;
&lt;p&gt;Causality Engine fixes this. Upload a Google Analytics CSV and get a full causal attribution analysis in minutes. No pixels, no SDKs, no developer needed. The platform reveals 15-25% of revenue that traditional attribution structurally cannot see, and quantifies it as an exact euro amount per channel.&lt;/p&gt;
&lt;p&gt;Built for e-commerce brands doing €5K+ monthly revenue. All data is EU-hosted. GDPR-compliant from the ground up with right-to-erasure, data anonymisation, and fraud detection built in.&lt;/p&gt;
&lt;p&gt;Pricing starts at €99 for a single analysis. Pro subscribers (€299/mo) get unlimited analyses, AI chat, and full historical lookback.&lt;/p&gt;
&lt;p&gt;Get started now: &lt;a rel=&quot;nofollow&quot; href=&quot;https://causalityengine.ai&quot;&gt;https://causalityengine.ai&lt;/a&gt;&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/21053/causality-engine</guid>
<pubDate>Sun, 21 Jun 2026 03:58:15 +0000</pubDate>
</item>
<item>
<title>The Data Science Behind Marketing Attribution: Moving Beyond Client-Side Correlation</title>
<link>https://coderlegion.com/21054/the-data-science-behind-marketing-attribution-moving-beyond-client-side-correlation</link>
<description>&lt;p&gt;As browser privacy controls and ad-blockers tighten globally, developers and data engineers face a growing bottleneck: client-side event tracking is becoming increasingly unreliable. &lt;/p&gt;
&lt;p&gt;The traditional way to measure marketing performance relies on third-party tracking scripts, front-end pixels, or heavy SDKs embedded into the browser. When a conversion occurs, the client-side script parses the current session source and credits it with the sale. This structure usually implements a &quot;last-click&quot; attribution framework.&lt;/p&gt;
&lt;h4 id=&quot;the-engineering-flaw-of-last-click-data&quot;&gt;The Engineering Flaw of Last-Click Data&lt;/h4&gt;
&lt;p&gt;From an architectural standpoint, last-click tracking reduces a multi-touchpoint user journey down to a single relational database entry. &lt;/p&gt;
&lt;p&gt;Consider this standard pipeline sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User interacts with a top-of-funnel ad via a social media platform.&lt;/li&gt;
&lt;li&gt;User enters an email marketing loop and clicks a mid-funnel link.&lt;/li&gt;
&lt;li&gt;User runs a quick direct brand search on Google to execute the transaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In a last-click data model, the brand search capturing the transaction gets 100% of the conversion credit. The discovery ad and email nurture nodes get zero. This structural blind spot causes a massive misallocation of budget toward bottom-funnel channels while starving the systems that generate baseline demand.&lt;/p&gt;
&lt;h4 id=&quot;solving-inaccuracies-with-backend-causal-inference&quot;&gt;Solving Inaccuracies with Backend Causal Inference&lt;/h4&gt;
&lt;p&gt;The alternative to flawed client-side correlation is server-side causal inference. Instead of evaluating isolated browser events as they execute, the logic shifts entirely to the backend, analyzing complete historical data records.&lt;/p&gt;
&lt;p&gt;By processing standardized exports (such as a standard Google Analytics CSV export), server-side algorithms can run independent multi-touchpoint analysis without client-side execution overhead. &lt;/p&gt;
&lt;p&gt;Engineered platforms in this space, like &lt;a rel=&quot;nofollow&quot; href=&quot;https://causalityengine.ai&quot;&gt;Causality Engine&lt;/a&gt;, approach this mathematically rather than using arbitrary linear rules. By moving processing to a secure backend, it bypasses browser script blockers entirely. This architecture successfully surfaces 15% to 25% of hidden conversion revenue that standard tracking scripts fail to capture, outputting the exact value metrics per channel.&lt;/p&gt;
&lt;h4 id=&quot;architectural-benefits-performance-amp-compliance&quot;&gt;Architectural Benefits: Performance &amp;amp; Compliance&lt;/h4&gt;
&lt;p&gt;Shifting your analytics stack from front-end pixels to server-side file processing tackles two core infrastructure requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Site Reliability Engineering:&lt;/strong&gt; Eliminating external script execution on the frontend improves core web vitals and eliminates page latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compliance by Design:&lt;/strong&gt; Processing data purely via historical logs enables deterministic data privacy. Platforms can maintain fully EU-hosted environments with native data anonymization, strict right-to-erasure workflows, and integrated fraud detection layers without complicating front-end consent scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How is your engineering team adapting data architectures to handle multi-touch attribution as browser cookie restrictions expand? Let's talk in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21054/the-data-science-behind-marketing-attribution-moving-beyond-client-side-correlation</guid>
<pubDate>Sun, 21 Jun 2026 03:58:06 +0000</pubDate>
</item>
<item>
<title>Why Token Analysis Isn't Enough Anymore</title>
<link>https://coderlegion.com/20355/why-token-analysis-isnt-enough-anymore</link>
<description>&lt;h3 id=&quot;the-industry-is-looking-at-the-wrong-thing&quot;&gt;The Industry Is Looking at the Wrong Thing&lt;/h3&gt;
&lt;p&gt;For years, crypto security has revolved around a single assumption:&lt;/p&gt;
&lt;p&gt;If we analyze a token thoroughly enough, we can understand its risk.&lt;/p&gt;
&lt;p&gt;This assumption gave rise to an entire ecosystem of tools.&lt;/p&gt;
&lt;p&gt;Liquidity checkers.&lt;/p&gt;
&lt;p&gt;Contract scanners.&lt;/p&gt;
&lt;p&gt;Holder analyzers.&lt;/p&gt;
&lt;p&gt;Rug pull detectors.&lt;/p&gt;
&lt;p&gt;Risk scores.&lt;/p&gt;
&lt;p&gt;Audits.&lt;/p&gt;
&lt;p&gt;Dashboards.&lt;/p&gt;
&lt;p&gt;Alerts.&lt;/p&gt;
&lt;p&gt;Every day, thousands of traders open these tools and ask the same question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is this token safe?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At first glance, it seems like a reasonable question.&lt;/p&gt;
&lt;p&gt;A token is visible.&lt;/p&gt;
&lt;p&gt;It has a contract.&lt;/p&gt;
&lt;p&gt;It has holders.&lt;/p&gt;
&lt;p&gt;It has liquidity.&lt;/p&gt;
&lt;p&gt;It produces data.&lt;/p&gt;
&lt;p&gt;Data can be analyzed.&lt;/p&gt;
&lt;p&gt;Analysis can produce a score.&lt;/p&gt;
&lt;p&gt;The score can be used to make decisions.&lt;/p&gt;
&lt;p&gt;The logic appears sound.&lt;/p&gt;
&lt;p&gt;The problem is that reality refuses to cooperate.&lt;/p&gt;
&lt;p&gt;Despite years of increasingly sophisticated token analysis, the same scams continue to appear.&lt;/p&gt;
&lt;p&gt;The same manipulation strategies continue to work.&lt;/p&gt;
&lt;p&gt;The same outcomes continue to repeat.&lt;/p&gt;
&lt;p&gt;The industry keeps getting better at analyzing tokens.&lt;/p&gt;
&lt;p&gt;Yet it remains surprisingly ineffective at understanding the people behind them.&lt;/p&gt;
&lt;p&gt;And that may be because we are analyzing the wrong entity.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-strange-pattern&quot;&gt;A Strange Pattern&lt;/h3&gt;
&lt;p&gt;When we first started building NexusVeritas, our goal was not particularly ambitious.&lt;/p&gt;
&lt;p&gt;We wanted to build a better risk engine.&lt;/p&gt;
&lt;p&gt;Like everyone else, we looked at the usual signals:&lt;/p&gt;
&lt;p&gt;Liquidity.&lt;/p&gt;
&lt;p&gt;Holder concentration.&lt;/p&gt;
&lt;p&gt;Funding sources.&lt;/p&gt;
&lt;p&gt;Wallet activity.&lt;/p&gt;
&lt;p&gt;Transaction flows.&lt;/p&gt;
&lt;p&gt;The idea seemed straightforward.&lt;/p&gt;
&lt;p&gt;Collect enough information about a token and eventually risk becomes measurable.&lt;/p&gt;
&lt;p&gt;Then something unexpected happened.&lt;/p&gt;
&lt;p&gt;The more cases we analyzed, the less important the tokens themselves seemed.&lt;/p&gt;
&lt;p&gt;Different names.&lt;/p&gt;
&lt;p&gt;Different branding.&lt;/p&gt;
&lt;p&gt;Different communities.&lt;/p&gt;
&lt;p&gt;Different narratives.&lt;/p&gt;
&lt;p&gt;Different websites.&lt;/p&gt;
&lt;p&gt;Different contracts.&lt;/p&gt;
&lt;p&gt;Yet beneath the surface, certain behavioral patterns kept reappearing.&lt;/p&gt;
&lt;p&gt;The same launch structures.&lt;/p&gt;
&lt;p&gt;The same wallet behaviors.&lt;/p&gt;
&lt;p&gt;The same funding routes.&lt;/p&gt;
&lt;p&gt;The same distribution patterns.&lt;/p&gt;
&lt;p&gt;The same operational mistakes.&lt;/p&gt;
&lt;p&gt;Again and again.&lt;/p&gt;
&lt;p&gt;At first it felt like coincidence.&lt;/p&gt;
&lt;p&gt;Then it became difficult to ignore.&lt;/p&gt;
&lt;p&gt;Eventually a different hypothesis emerged:&lt;/p&gt;
&lt;p&gt;Perhaps the token wasn't the real subject of analysis.&lt;/p&gt;
&lt;p&gt;Perhaps it was merely the artifact left behind by something else.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tokens-are-disposable&quot;&gt;Tokens Are Disposable&lt;/h3&gt;
&lt;p&gt;One of the most misunderstood aspects of crypto is how cheap identity has become.&lt;/p&gt;
&lt;p&gt;In traditional finance, creating a new legal entity requires paperwork, compliance, and significant effort.&lt;/p&gt;
&lt;p&gt;In crypto, creating a new wallet takes seconds.&lt;/p&gt;
&lt;p&gt;Creating a new token takes minutes.&lt;/p&gt;
&lt;p&gt;Creating an entirely new project can take hours.&lt;/p&gt;
&lt;p&gt;This changes the economics of deception.&lt;/p&gt;
&lt;p&gt;If a token becomes associated with failure, an operator can abandon it.&lt;/p&gt;
&lt;p&gt;If a wallet becomes known, it can be discarded.&lt;/p&gt;
&lt;p&gt;If a community turns hostile, a new brand can emerge tomorrow.&lt;/p&gt;
&lt;p&gt;The cost of replacing the visible layer is almost zero.&lt;/p&gt;
&lt;p&gt;The industry understands this intellectually.&lt;/p&gt;
&lt;p&gt;Yet most analytical systems still behave as if wallets and tokens are stable identities.&lt;/p&gt;
&lt;p&gt;They are not.&lt;/p&gt;
&lt;p&gt;They are disposable infrastructure.&lt;/p&gt;
&lt;p&gt;The operator remains.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-wallet-illusion&quot;&gt;The Wallet Illusion&lt;/h3&gt;
&lt;p&gt;Many blockchain tools implicitly treat wallets as people.&lt;/p&gt;
&lt;p&gt;This is understandable.&lt;/p&gt;
&lt;p&gt;Wallet addresses are visible.&lt;/p&gt;
&lt;p&gt;People are not.&lt;/p&gt;
&lt;p&gt;So the wallet becomes the proxy for identity.&lt;/p&gt;
&lt;p&gt;But this shortcut creates a dangerous illusion.&lt;/p&gt;
&lt;p&gt;Imagine an operator launches ten tokens.&lt;/p&gt;
&lt;p&gt;Each token uses a different wallet.&lt;/p&gt;
&lt;p&gt;Each wallet receives funding through a different route.&lt;/p&gt;
&lt;p&gt;Each launch occurs under a different name.&lt;/p&gt;
&lt;p&gt;To a traditional analytics platform, these may appear to be ten unrelated events.&lt;/p&gt;
&lt;p&gt;To the operator, it is Tuesday.&lt;/p&gt;
&lt;p&gt;The blockchain has changed.&lt;/p&gt;
&lt;p&gt;The behavior has not.&lt;/p&gt;
&lt;p&gt;And behavior is where identity begins to emerge.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;behavior-leaves-fingerprints&quot;&gt;Behavior Leaves Fingerprints&lt;/h3&gt;
&lt;p&gt;Human beings are creatures of habit.&lt;/p&gt;
&lt;p&gt;We develop patterns without realizing it.&lt;/p&gt;
&lt;p&gt;We repeat decisions.&lt;/p&gt;
&lt;p&gt;We optimize routines.&lt;/p&gt;
&lt;p&gt;We recycle infrastructure.&lt;/p&gt;
&lt;p&gt;We fall back on familiar processes.&lt;/p&gt;
&lt;p&gt;Operators do the same thing.&lt;/p&gt;
&lt;p&gt;Some consistently launch at similar times.&lt;/p&gt;
&lt;p&gt;Some repeatedly use the same funding structures.&lt;/p&gt;
&lt;p&gt;Some distribute supply in nearly identical ways.&lt;/p&gt;
&lt;p&gt;Some follow recognizable liquidity patterns.&lt;/p&gt;
&lt;p&gt;Some deploy entire networks of fresh wallets that behave almost identically across multiple launches.&lt;/p&gt;
&lt;p&gt;Individually, these signals may seem insignificant.&lt;/p&gt;
&lt;p&gt;Together, they become something far more powerful.&lt;/p&gt;
&lt;p&gt;A behavioral fingerprint.&lt;/p&gt;
&lt;p&gt;Not a name.&lt;/p&gt;
&lt;p&gt;Not a legal identity.&lt;/p&gt;
&lt;p&gt;Not a social media account.&lt;/p&gt;
&lt;p&gt;A behavioral identity.&lt;/p&gt;
&lt;p&gt;A signature created by repeated decisions.&lt;/p&gt;
&lt;p&gt;And unlike wallets, behavioral signatures are surprisingly difficult to replace.&lt;/p&gt;
&lt;p&gt;Changing a wallet is easy.&lt;/p&gt;
&lt;p&gt;Changing operational habits is much harder.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-shift-from-token-intelligence-to-operator-intelligence&quot;&gt;The Shift from Token Intelligence to Operator Intelligence&lt;/h3&gt;
&lt;p&gt;This realization changes the question entirely.&lt;/p&gt;
&lt;p&gt;For years, the industry has asked:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is this token dangerous?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But tokens do not make decisions.&lt;/p&gt;
&lt;p&gt;Tokens do not coordinate launches.&lt;/p&gt;
&lt;p&gt;Tokens do not move funds.&lt;/p&gt;
&lt;p&gt;Tokens do not execute exits.&lt;/p&gt;
&lt;p&gt;People do.&lt;/p&gt;
&lt;p&gt;Operators do.&lt;/p&gt;
&lt;p&gt;The more interesting question becomes:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is this operator dangerous?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a fundamentally different problem.&lt;/p&gt;
&lt;p&gt;Instead of evaluating assets, we begin evaluating behavior.&lt;/p&gt;
&lt;p&gt;Instead of scoring contracts, we begin identifying patterns.&lt;/p&gt;
&lt;p&gt;Instead of tracking tokens, we begin tracking operators.&lt;/p&gt;
&lt;p&gt;The subject of analysis changes.&lt;/p&gt;
&lt;p&gt;And with it, the entire intelligence model changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-existing-tools-reach-a-ceiling&quot;&gt;Why Existing Tools Reach a Ceiling&lt;/h3&gt;
&lt;p&gt;Most existing risk tools are extremely useful.&lt;/p&gt;
&lt;p&gt;But they share a common limitation.&lt;/p&gt;
&lt;p&gt;They focus on the present.&lt;/p&gt;
&lt;p&gt;What does this token look like today?&lt;/p&gt;
&lt;p&gt;What does this wallet look like today?&lt;/p&gt;
&lt;p&gt;What does this contract look like today?&lt;/p&gt;
&lt;p&gt;The problem is that risk often originates in the past.&lt;/p&gt;
&lt;p&gt;An operator may have launched twenty previous projects.&lt;/p&gt;
&lt;p&gt;They may have participated in multiple failed ecosystems.&lt;/p&gt;
&lt;p&gt;They may have recycled infrastructure across dozens of deployments.&lt;/p&gt;
&lt;p&gt;They may have developed highly recognizable operational patterns.&lt;/p&gt;
&lt;p&gt;None of that information exists within a single token.&lt;/p&gt;
&lt;p&gt;It only becomes visible when behavior is analyzed across time.&lt;/p&gt;
&lt;p&gt;The challenge is no longer blockchain analytics.&lt;/p&gt;
&lt;p&gt;The challenge becomes attribution.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;attribution-is-the-next-frontier&quot;&gt;Attribution Is the Next Frontier&lt;/h3&gt;
&lt;p&gt;The word &quot;attribution&quot; is common in cybersecurity.&lt;/p&gt;
&lt;p&gt;Investigators rarely begin by asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What happened?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead, they ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Who has done something like this before?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The same principle applies on-chain.&lt;/p&gt;
&lt;p&gt;As the ecosystem matures, the most valuable intelligence will not come from analyzing isolated events.&lt;/p&gt;
&lt;p&gt;It will come from connecting events together.&lt;/p&gt;
&lt;p&gt;Identifying recurring operators.&lt;/p&gt;
&lt;p&gt;Discovering behavioral clusters.&lt;/p&gt;
&lt;p&gt;Recognizing infrastructure reuse.&lt;/p&gt;
&lt;p&gt;Building operational histories.&lt;/p&gt;
&lt;p&gt;Creating memory where none previously existed.&lt;/p&gt;
&lt;p&gt;This is where the future of blockchain intelligence is heading.&lt;/p&gt;
&lt;p&gt;Not toward bigger dashboards.&lt;/p&gt;
&lt;p&gt;Not toward more complicated risk scores.&lt;/p&gt;
&lt;p&gt;Toward attribution.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;building-memory&quot;&gt;Building Memory&lt;/h3&gt;
&lt;p&gt;Perhaps the biggest weakness of today's crypto ecosystem is collective amnesia.&lt;/p&gt;
&lt;p&gt;Every day, thousands of new tokens appear.&lt;/p&gt;
&lt;p&gt;Every day, thousands disappear.&lt;/p&gt;
&lt;p&gt;The market forgets quickly.&lt;/p&gt;
&lt;p&gt;Operators understand this.&lt;/p&gt;
&lt;p&gt;Many depend on it.&lt;/p&gt;
&lt;p&gt;What happens when the market starts remembering?&lt;/p&gt;
&lt;p&gt;What happens when behavior becomes searchable?&lt;/p&gt;
&lt;p&gt;What happens when launch history becomes reputation?&lt;/p&gt;
&lt;p&gt;What happens when every new project can be compared against years of operational patterns?&lt;/p&gt;
&lt;p&gt;The economics begin to change.&lt;/p&gt;
&lt;p&gt;The cost of abandoning a wallet remains low.&lt;/p&gt;
&lt;p&gt;The cost of abandoning a behavioral identity becomes much higher.&lt;/p&gt;
&lt;p&gt;That is where things become interesting.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-next-decade&quot;&gt;The Next Decade&lt;/h3&gt;
&lt;p&gt;The first generation of crypto intelligence focused on assets.&lt;/p&gt;
&lt;p&gt;The second generation focused on transactions.&lt;/p&gt;
&lt;p&gt;The next generation will focus on operators.&lt;/p&gt;
&lt;p&gt;Not because tokens are unimportant.&lt;/p&gt;
&lt;p&gt;But because tokens are temporary.&lt;/p&gt;
&lt;p&gt;Operators persist.&lt;/p&gt;
&lt;p&gt;Infrastructure evolves.&lt;/p&gt;
&lt;p&gt;Wallets rotate.&lt;/p&gt;
&lt;p&gt;Brands disappear.&lt;/p&gt;
&lt;p&gt;Behavior remains.&lt;/p&gt;
&lt;p&gt;The question that defined the last cycle was:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What is this token?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The question that may define the next cycle is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Who is behind it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That shift may sound subtle.&lt;/p&gt;
&lt;p&gt;In reality, it changes everything.&lt;/p&gt;
&lt;p&gt;Because wallets change.&lt;/p&gt;
&lt;p&gt;Operators adapt.&lt;/p&gt;
&lt;p&gt;Behavior leaves fingerprints.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20355/why-token-analysis-isnt-enough-anymore</guid>
<pubDate>Fri, 12 Jun 2026 03:09:41 +0000</pubDate>
</item>
<item>
<title>Through the Looking Glass: Why Your Server Logs Are a Playground for Cyber-Detectives</title>
<link>https://coderlegion.com/20312/through-the-looking-glass-why-your-server-logs-are-a-playground-for-cyber-detectives</link>
<description>&lt;p&gt;When you deploy a custom analytics package or any traffic-monitoring tool, your expectations are usually modest: a few hits from friends, maybe your mom, or an occasional lost translator bot. But after the server runs for a couple of days, the reality often turns out to be significantly more complex.&lt;/p&gt;
&lt;p&gt;Recently, while auditing the logs of my Laravel-based analytics package, I discovered that my project had become a focal point for researchers who don't just &quot;scroll the feed,&quot; but dissect the internet down to its molecular level.&lt;/p&gt;
&lt;p&gt;As a developer, it made me curious: what exactly are these &quot;guests from the future&quot; (like Firefox 140.0) looking for, and why are corporate entities like Palo Alto Networks suddenly keeping such a close eye on a domain that barely existed yesterday?&lt;/p&gt;
&lt;h3 id=&quot;when-grey-traffic-becomes-an-object-of-study&quot;&gt;When &quot;Grey&quot; Traffic Becomes an Object of Study&lt;/h3&gt;
&lt;p&gt;The first thing that stands out in the logs isn't the usual bot noise, but Power Users. I’m talking about visitors coming via IPv6 tunnels, using Linux, and identifying as DuckDuckGo users. These are individuals who consciously avoid tracking and operate with tools that most of us consider &quot;advanced&quot; or &quot;niche.&quot;&lt;/p&gt;
&lt;h4 id=&quot;they-behave-like-professional-auditors-&quot;&gt;They behave like professional auditors:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Protocol Adherence:&lt;/strong&gt; Checking robots.txt before crawling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jurisdictional Assessment:&lt;/strong&gt; Analyzing Terms of Service within seconds (a classic professional trait: understand the jurisdiction before pulling the trigger).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Intentional Focus:&lt;/strong&gt; Bypassing general content in favor of direct interaction with security tools (like DNS audits).&lt;/p&gt;
&lt;p&gt;This is a vital signal for any engineer: if your software attracts this type of user, you've built something genuinely practical. Simultaneously, it serves as a high-stakes stress test for your entire stack.&lt;/p&gt;
&lt;h3 id=&quot;dns-detective-the-karachi-police-case&quot;&gt;DNS Detective: The &quot;Karachi Police&quot; Case&lt;/h3&gt;
&lt;p&gt;The situation becomes truly fascinating when your tool starts being used as a &quot;proxy scanner&quot; to probe other resources. For instance, I noticed requests hitting karachipolice.gov.pk via my tool. Using an independent auditor to probe a government domain is a perfect example of how borders vanish on the web.&lt;/p&gt;
&lt;p&gt;What did the audit reveal, and what should you look for in your own projects?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DMARC: null&lt;/code&gt;. An absent protection policy is an open invitation for attackers to perform email spoofing.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SPF ~all&lt;/code&gt;. A &quot;soft&quot; lock that exists formally but fails to effectively block unauthorized senders.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;IPv6 Facade&lt;/code&gt;. A site may correctly resolve via modern &lt;code&gt;AAAA&lt;/code&gt; records, but still rely on an outdated, vulnerable mail infrastructure behind the scenes.&lt;/p&gt;
&lt;p&gt;This case serves as a sharp reminder: &lt;strong&gt;domain security isn't about wall thickness; it's about the precision of a single DNS record.&lt;/strong&gt; If you haven't audited your own domain's configuration in a while, it is high time to verify those settings — you can run a &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.net/security-tools/dns-records-check&quot;&gt;free DNS audit&lt;/a&gt; to ensure your records are as airtight as they need to be.&lt;/p&gt;
&lt;h3 id=&quot;corporate-ghosts-from-palo-alto-networks&quot;&gt;Corporate &quot;Ghosts&quot; from Palo Alto Networks&lt;/h3&gt;
&lt;p&gt;While the first group represents human researchers, Palo Alto Networks and their peers represent the world of automated global threat mapping. Once your service begins exhibiting behavior resembling a network scanner, it inevitably ends up on corporate radar.&lt;/p&gt;
&lt;h4 id=&quot;for-a-developer-this-is-a-critical-turning-point-&quot;&gt;For a developer, this is a critical turning point:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your domain reputation is being built automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If your service behaves &quot;professionally&quot; (Laravel 12, clean headers, valid configurations), algorithms flag you as a legitimate research node, which improves your trust rating across the &quot;clean internet.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;final-thoughts-what-should-we-learn-&quot;&gt;Final Thoughts: What Should We Learn?&lt;/h3&gt;
&lt;p&gt;Analytics is more than just traffic charts; it is a powerful tool for code hygiene and security. When you read your logs, you are reading the story of the web in real-time.&lt;/p&gt;
&lt;p&gt;If you are interested in the technical implementation details of how I built this package without crashing production, or want to dive deeper into the security of HTTP headers (and why a single forgotten comma in your CSP can &quot;kill&quot; your site for Googlebot), check out these resources:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/how-i-built-my-own-laravel-analytics-package-and-almost-didnt-crash-production&quot;&gt;How I Built My Own Laravel Analytics Package (and Almost Didn't Crash Production)&lt;/a&gt; — Technical implementation details.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/your-digital-fortress-why-a-security-headers-audit-is-essential&quot;&gt;Your Digital Fortress: Why a Security Headers Audit is Essential&lt;/a&gt; — Why headers are the first thing every &quot;pro&quot; checks.&lt;/p&gt;
&lt;p&gt;Take care of your HTTP headers, and don't forget to check your logs. They are always more interesting than Google Analytics.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20312/through-the-looking-glass-why-your-server-logs-are-a-playground-for-cyber-detectives</guid>
<pubDate>Thu, 11 Jun 2026 13:54:28 +0000</pubDate>
</item>
<item>
<title>Building a Digital Customs Checkpoint: My 7-Part Journey in Laravel Analytics &amp; Bot Defense</title>
<link>https://coderlegion.com/19648/building-a-digital-customs-checkpoint-my-7-part-journey-in-laravel-analytics-bot-defense</link>
<description>&lt;p&gt;Over the past year, developing &lt;code&gt;laravel-visit-analytics&lt;/code&gt; has evolved from a simple experiment into a significant engineering challenge. When standard analytics tools failed to meet my requirements for GDPR/DSGVO compliance and data accuracy, I realized: if you want it done right, do it yourself.&lt;/p&gt;
&lt;p&gt;This 7-part series chronicles my transition from naive trust in browser headers to building a system that detects botnets in real-time based on their unique &quot;digital fingerprints.&quot;&lt;/p&gt;
&lt;h3 id=&quot;project-roadmap-&quot;&gt;Project Roadmap:&lt;/h3&gt;
&lt;h4 id=&quot;phase-1-architecture-amp-foundation&quot;&gt;Phase 1: Architecture &amp;amp; Foundation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chapter 1: Breaking Free from the Giants.&lt;/strong&gt; Why I abandoned Google Analytics for a Privacy-First approach — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/how-i-built-my-own-laravel-analytics-package-and-almost-didnt-crash-production&quot;&gt;How I Built My Own Laravel Analytics Package (and Almost Didn't Crash Production)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chapter 2: Refactoring 2.0.&lt;/strong&gt; Replacing heavy middleware with an elegant Chain of Responsibility and optimizing database performance — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/refactoring-laravel-visit-analytics-the-path-to-version-200&quot;&gt;Refactoring Laravel Visit Analytics: The Path to Version 2.0.0&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;phase-2-the-logic-of-suspicion&quot;&gt;Phase 2: The Logic of Suspicion&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chapter 3: The Lie Detector.&lt;/strong&gt; Implementing a scoring system, Snowball Effect, and honeypots to filter out &quot;junk&quot; traffic — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/a-lie-detector-for-http-requests-analytics-through-time&quot;&gt;A Lie Detector for HTTP Requests: Analytics Through Time&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chapter 4: Through the Looking Glass.&lt;/strong&gt; How DNS auditing and IPv6 analysis helped uncover true traffic sources — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/through-the-looking-glass-of-logs-karachi-police-duckduckgo-and-ipv6-magic&quot;&gt;Through the Looking Glass of Logs: Karachi Police, DuckDuckGo, and IPv6 Magic&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chapter 5: Code with a Human Face.&lt;/strong&gt; The story of how I almost banned my son's vintage Sony Ericsson and why I implemented a &quot;mercy coefficient&quot; — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/human-centric-bot-detection-lessons-from-sony-ericsson-and-win98&quot;&gt;Human-Centric Bot Detection: Lessons from Sony Ericsson and Win98&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;phase-3-advanced-defense&quot;&gt;Phase 3: Advanced Defense&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chapter 6: Hunting Digital Chameleons (Part 1).&lt;/strong&gt; Defeating botnets via Client Hints and implementing real-time anonymization in v2.4.0 — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/hunting-digital-chameleons-how-we-defeated-botnets-in-laravel-v240&quot;&gt;Hunting Digital Chameleons: How We Defeated Botnets in Laravel v2.4.0&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chapter 7: Hunting Digital Chameleons (Part 2).&lt;/strong&gt; Why session imitation is a bot's weak point and how I detect anomalies in session stability — &lt;a rel=&quot;nofollow&quot; href=&quot;https://oleant.dev/en/blog/hunting-digital-chameleons-why-session-imitation-is-the-weakest-link-for-modern-bots&quot;&gt;Hunting Digital Chameleons: Why Session Imitation Is the Weakest Link for Modern Bots&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;What’s Next?&lt;/strong&gt;&lt;br&gt;
The foundation is set. I am currently focusing on data integrity, but plans are already in motion to visualize these threat patterns directly in the Laravel Filament dashboard. Stay tuned!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19648/building-a-digital-customs-checkpoint-my-7-part-journey-in-laravel-analytics-bot-defense</guid>
<pubDate>Thu, 04 Jun 2026 09:00:00 +0000</pubDate>
</item>
<item>
<title>From Google Analytics to Matomo: How We Migrated Our Web Tracking to Our Own Infrastructure</title>
<link>https://coderlegion.com/19400/from-google-analytics-to-matomo-how-we-migrated-our-web-tracking-to-our-own-infrastructure</link>
<description>&lt;p&gt;We replaced Google Analytics with Matomo, an open-source web analytics platform that we run on our own infrastructure in Switzerland. In this article, we describe why we made this move, how we implemented the migration technically, which Nine services we used, and what we gained and lost in the process.&lt;/p&gt;
&lt;h2 id=&quot;why-we-replaced-google-analytics&quot;&gt;Why We Replaced Google Analytics&lt;/h2&gt;
&lt;p&gt;Google Analytics is the default web tracking solution for many companies. We used it for years. Still, we had long been uncomfortable with the fact that all visitor data from our website was being transferred to Google servers in the US.&lt;/p&gt;
&lt;p&gt;This is not merely a philosophical question. As a Swiss managed services provider that advocates data sovereignty and transparency to customers, it was difficult to explain why our own analytics data ended up with a US corporation. Specifically, these were the factors we weighed:&lt;/p&gt;
&lt;p&gt;Data ownership: Visitor data from nine.ch, docs.nine.ch, Cockpit, and deplo.io was transferred to Google in the US, without us having any direct control over retention periods or usage.&lt;br&gt;
Cookie consent requirement: GA4 sets tracking cookies, which requires explicit user consent. This meant a relevant share of visitors declined analytics, leaving our data incomplete.&lt;br&gt;
GDPR compliance: The legal situation around GA4 and GDPR has been contested in the EU and Switzerland for years. Several data protection authorities have classified GA4 as non-compliant.&lt;br&gt;
Consistency: We recommend that customers run their infrastructure in Switzerland. Our own analytics solution should meet that same standard.&lt;br&gt;
The decision in favour of Matomo came quickly: open source, self-hostable, operable without cookies, actively developed, and with comparable functionality for our needs.&lt;/p&gt;
&lt;h2 id=&quot;what-we-track-four-properties&quot;&gt;What We Track: Four Properties&lt;/h2&gt;
&lt;p&gt;We operate four digital properties for which we need analytics:&lt;/p&gt;
&lt;p&gt;Property/ Technology&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Website (nine.ch) / Hugo (Static Site)&lt;/li&gt;
&lt;li&gt;docs.nine.ch / Docusaurus v3&lt;/li&gt;
&lt;li&gt;Cockpit (Customer Portal) / Ruby on Rails&lt;/li&gt;
&lt;li&gt;deplo.io (Deploio Landing Page) / Google Tag Manager (Matomo added additively)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each property has its own dashboard in Matomo, and user permissions can be granted granularly per property.&lt;/p&gt;
&lt;h2 id=&quot;the-technical-implementation&quot;&gt;The Technical Implementation&lt;/h2&gt;
&lt;h3 id=&quot;server-and-infrastructure&quot;&gt;Server and Infrastructure&lt;/h3&gt;
&lt;p&gt;Matomo 5.10.0 runs on a virtual Nine Managed Server on our Swiss infrastructure. The following Nine services are used to run it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managed Server with Nginx and PHP-FPM (PHP 8.5.6)&lt;/li&gt;
&lt;li&gt;Managed MySQL (version 8.0 on Ubuntu 24.04 LTS, support until May&lt;br&gt;
2029)&lt;/li&gt;
&lt;li&gt;Daily backups (included in the Managed Server offering)&lt;/li&gt;
&lt;li&gt;Puppet for all server configuration and deployment&lt;/li&gt;
&lt;li&gt;Nginx hardening via Puppet: sensitive Matomo directories (tmp/,&lt;br&gt;
config/, lang/, misc/, vendor/) are blocked from external access, and&lt;br&gt;
PHP execution is restricted to the root directory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The entire server configuration, including the Nginx rules, is managed via Puppet. This means no manual configuration on the server – everything is versioned and reproducible.&lt;/p&gt;
&lt;h3 id=&quot;cookieless-and-without-consent&quot;&gt;Cookieless and Without Consent&lt;/h3&gt;
&lt;p&gt;Matomo offers a cookieless operating mode that sets no tracking cookies. This has an important consequence: since no cookies are set, no visitor consent is required. The cookie banner on nine.ch no longer contains an analytics category, and data is complete regardless of whether a visitor accepts or declines cookies.&lt;/p&gt;
&lt;p&gt;Analysis is based on anonymised data. The last two bytes of the IP address are removed before storage, making personal identification technically impossible. The data never leaves our servers.&lt;/p&gt;
&lt;p&gt;The legal basis is Art. 6(1)(f) GDPR (legitimate interests).&lt;/p&gt;
&lt;h3 id=&quot;integration-across-the-four-properties&quot;&gt;Integration Across the Four Properties&lt;/h3&gt;
&lt;p&gt;nine.ch (Hugo): The Matomo tracking snippet is embedded directly in  via the Hugo template. A hugo.IsProduction guard ensures that no tracking occurs on staging or local environments. At the same time, the GA4 initialisation and analytics category were removed from the cookie consent script. Existing GA cookies (_ga, _gid) are cleared on the first page load.&lt;/p&gt;
&lt;p&gt;docs.nine.ch (Docusaurus v3): The GA4 preset was removed from docusaurus.config.ts. The Matomo snippet is embedded via Docusaurus’s headTags mechanism, also with a NODE_ENV === &quot;production&quot; guard. GA cookie cleanup is integrated in the same script.&lt;/p&gt;
&lt;p&gt;Cockpit (Ruby on Rails): The snippet is embedded in the application layout (application.html.erb), with a Rails.env.production? guard and the correct CSP nonce to avoid violating the Content Security Policy header.&lt;/p&gt;
&lt;p&gt;deplo.io: The Deploio landing page is maintained by our colleagues at Renuo. Renuo continues to use Google Analytics for their own reporting. They added the Matomo tag additively in GTM, so visitor data now flows into our Matomo instance as well.&lt;/p&gt;
&lt;h3 id=&quot;content-security-policy&quot;&gt;Content Security Policy&lt;/h3&gt;
&lt;p&gt;Since Matomo runs on its own subdomain, the CSP headers of the website had to be updated. The Matomo subdomain was added to script-src, img-src, and connect-src. This covers loading matomo.js, the tracking pixel (fallback), and the XHR/Beacon requests to matomo.php.&lt;/p&gt;
&lt;h3 id=&quot;privacy-policy&quot;&gt;Privacy Policy&lt;/h3&gt;
&lt;p&gt;The privacy policy on nine.ch was updated: the section on Google Analytics was replaced with a section on Matomo, explaining the cookieless operation, the Swiss data location, and the anonymisation of IP addresses.&lt;/p&gt;
&lt;h2 id=&quot;what-we-gained-compared-to-google-analytics&quot;&gt;What We Gained Compared to Google Analytics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Complete data: No sampling, no data thresholds (GA4 hides values&lt;br&gt;
below 10 events), no gaps caused by consent refusals.&lt;/li&gt;
&lt;li&gt;Unlimited data retention: GA4 defaults to 14 months, with a maximum&lt;br&gt;
of 26. With Matomo, the decision is ours.&lt;/li&gt;
&lt;li&gt;Individual visitor logs: Matomo allows viewing individual sessions,&lt;br&gt;
which is not possible in GA4.&lt;/li&gt;
&lt;li&gt;Data sovereignty: All data stays on our own infrastructure in&lt;br&gt;
Switzerland.&lt;/li&gt;
&lt;li&gt;No third party involved: Google had access to our visitor data. That&lt;br&gt;
is no longer the case.&lt;/li&gt;
&lt;li&gt;GDPR compliance without effort: No consent dialog for analytics, no&lt;br&gt;
dependency on third-party rulings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-we-no-longer-have&quot;&gt;What We No Longer Have&lt;/h2&gt;
&lt;p&gt;Honesty matters here: there are features GA4 offers that Matomo does not have in its base version.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search Console Integration (Keywords): GA4 could be connected to&lt;br&gt;
Google Search Console, making organic keywords visible. This feature&lt;br&gt;
is a paid add-on in Matomo. For us, it is the only relevant gap, and&lt;br&gt;
one we plan to close with the plugin in the medium term.&lt;/li&gt;
&lt;li&gt;Google Signals / Demographics: GA4 could collect demographic data&lt;br&gt;
from logged-in Google users, something we will also add later via&lt;br&gt;
plugin.&lt;/li&gt;
&lt;li&gt;Predictive Metrics: GA4’s machine learning predictions require a data&lt;br&gt;
volume that is not relevant for us.&lt;/li&gt;
&lt;li&gt;BigQuery export: Not needed for our purposes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The migration was technically manageable and worth the effort. We now have an analytics solution that aligns with our values: data on our own infrastructure, in Switzerland, without third parties. The cookieless operation is a bonus that makes our data more complete than before.&lt;/p&gt;
&lt;p&gt;If you have similar requirements – data sovereignty, GDPR compliance, and complete data without consent friction – we recommend Matomo on a Managed Server as a straightforward and low-maintenance approach.&lt;/p&gt;
&lt;p&gt;If you have questions about the implementation, feel free to reach out.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19400/from-google-analytics-to-matomo-how-we-migrated-our-web-tracking-to-our-own-infrastructure</guid>
<pubDate>Mon, 01 Jun 2026 10:45:26 +0000</pubDate>
</item>
<item>
<title>From GTM migration to missing UTM columns: getting ad attribution right as a solo founder</title>
<link>https://coderlegion.com/18246/from-gtm-migration-to-missing-utm-columns-getting-ad-attribution-right-as-a-solo-founder</link>
<description>&lt;p&gt;Last month I migrated XreplyAI from hardcoded gtag snippets to Google Tag Manager.&lt;/p&gt;
&lt;p&gt;One container. All pixels managed in one place. No code deploys to add or change tags. I thought that was the hard part of getting ad attribution right.&lt;/p&gt;
&lt;p&gt;It wasn't.&lt;/p&gt;
&lt;p&gt;This morning I finished wiring Google Ads conversion tracking — trial_start firing server-side via the GA4 Measurement Protocol, conversion imported into Google Ads, bidding optimized for trials.&lt;/p&gt;
&lt;p&gt;Then a new user signed up. And I had no idea where they came from.&lt;/p&gt;
&lt;p&gt;Not because GA4 didn't know. GA4 knew — &quot;google / cpc.&quot; But our users table has no UTM columns. So I can't:&lt;/p&gt;
&lt;p&gt;Calculate LTV by acquisition channel&lt;br&gt;
Know whether Google Ads or X Ads drove a signup&lt;br&gt;
Filter subscribers by source when making budget decisions&lt;br&gt;
The fix is obvious in hindsight: store utm_source, utm_medium, utm_campaign, utm_content, and gclid on the user record at signup. Capture them from the URL on landing, pass them to the backend at magic link / signup time, persist once, never overwrite on subsequent logins.&lt;/p&gt;
&lt;p&gt;It's a 2-hour task. I opened a GitHub issue this morning.&lt;/p&gt;
&lt;p&gt;The lesson: GTM solves &quot;where do my tags live.&quot; UTM columns on users solve &quot;where did this specific person come from.&quot; You need both. Most founders running paid traffic have neither.&lt;/p&gt;
&lt;p&gt;Fix it before you spend another dollar on ads.&lt;/p&gt;
&lt;p&gt;Building XreplyAI in public — an AI social media tool for solo founders. BYOK model, voice-matched content across 8 platforms. xreplyai.com&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/18246/from-gtm-migration-to-missing-utm-columns-getting-ad-attribution-right-as-a-solo-founder</guid>
<pubDate>Mon, 18 May 2026 20:38:01 +0000</pubDate>
</item>
<item>
<title>Full Sales Data Analysis: From Raw Data to Interactive Business Dashboard</title>
<link>https://coderlegion.com/18144/full-sales-data-analysis-from-raw-data-to-interactive-business-dashboard</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Anybody can be a data analyst,but what separates a good data analyst from a great one is what you can do and present.In this article i will be giving step by step guidelines from turning a raw data to an interactive business dashboards and giving insights.We will generate our sales data from Mockaroo,use Python to turn the JSON file to CSV,analyse with Sql and visualize with Power Bi.&lt;/p&gt;
&lt;h3 id=&quot;1-generating-the-sales-data-&quot;&gt;1.Generating the sales data.&lt;/h3&gt;
&lt;p&gt;You will go to your browser and search Mockaroo.com.Once open,hit the generate data button and give AI a command to generate the data for you.You can select the number of rows that you want your data to have.Once its done,save the data as a json file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n07jaspo8ybkvi7nvy7i.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;a-viewing-the-data&quot;&gt;a.Viewing the data&lt;/h4&gt;
&lt;p&gt;Once your data is generated and saved in your files in your machine,go to your Github account and add that file to your repo.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7w5lqw6p7czm8p0dmls1.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
Once you open that file,hit the raw button and you will see something like this:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ep9ymur5pcci2cqo0hdl.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-importing-using-python-&quot;&gt;2.Importing using Python.&lt;/h3&gt;
&lt;p&gt;The work is not yet done.Copy the link from your raw data in github because we shall need it.Use the import requests and the url will be your link you copied from raw data in github.While pasting the url,ensure it has the either the double or single quotes at the start and at the end of the url link.The status code must always be 200.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnbtbzvntn1a6i9y4o8i.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The next step will be seeing the type of data that we have,either a list or dictionary.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4aer1n8ky85m1txtg4m5.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
We have created a data frame,viewed the first rows and saved the the data as csv.&lt;br&gt;
You should be seing the sales data as an excel file in your files ready for analyzing.&lt;/p&gt;
&lt;h3 id=&quot;sql-analysis&quot;&gt;SQL analysis&lt;/h3&gt;
&lt;p&gt;Since we already have our data as CSV,we will import it in any sql environment you use.I will be using DBeaver.First you create a schema called sales_data or any name of your choice,then creating the sales table.&lt;/p&gt;
&lt;h4 id=&quot;1-creating-the-table&quot;&gt;1.creating the table&lt;/h4&gt;
&lt;p&gt;You create a schema called sales_data or any name of your choice,then creating the sales table.Every column name will be kept as texts.Why,to avoid data type errors during import and to handle missing values,weird formats or nulls with ease.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ayerdlg4gbnavbvh54th.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;2-importing-the-data-to-sql-environment&quot;&gt;2.Importing the data to SQL environment&lt;/h4&gt;
&lt;p&gt;first,you go to your schema,refresh and search for your table name.Right click it and hit the import data button.Import the csv then press next,hit the browse button to select which file exactly that you want,then press next till you proceed to the next part.After importing the data,hit the refresh button on the table name below your schema to ensure the data is set.You can now write a query to see your data&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o06nli0w3zjpu5kvl5cq.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;b-cleaning-the-data&quot;&gt;b.cleaning the data&lt;/h3&gt;
&lt;p&gt;This is where most data analyst spend most of their time.With reference to my sales data,it's not so messy only few mistakes like fixing the total_price column.&lt;/p&gt;
&lt;h4 id=&quot;1-fixing-the-total-price-column&quot;&gt;1.fixing the total_price column&lt;/h4&gt;
&lt;p&gt;As i imported my data,the column total_price was full of error &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vty0jx4rus4es7c91t2x.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
I will fix it by step by step as shown below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ov29v7zu6mstvt29q8e.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;This SQL command fixes the &lt;code&gt;total_price&lt;/code&gt; column by converting the text-based &lt;code&gt;quantity&lt;/code&gt; values to integers using &lt;code&gt;::INTEGER&lt;/code&gt;,then multiplying each by a flat rate of $19.99.The &lt;code&gt;WHERE&lt;/code&gt; clause targets only rows where &lt;code&gt;total_price&lt;/code&gt; still contains the error message,leaving already-corrected rows untouched.&lt;/p&gt;
&lt;h4 id=&quot;2-trimming-capitalizing-and-removing-special-characters&quot;&gt;2.Trimming,capitalizing and removing special characters&lt;/h4&gt;
&lt;p&gt;Triming in SQL eliminates spaces before and after any word.Initcap is a special command that allows you to standardise the letters of words inside your columns.This is a similar command to 'proper' as used in My SQL and other SQL environment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m27t0o8ug7c8uht39g9i.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now that our data is clean,we proceed to the next step which is answering business questions.Our data was not so messy so we used little times as possible.&lt;/p&gt;
&lt;h3 id=&quot;c-re-creating-the-clean-table-&quot;&gt;c.Re-creating the clean table.&lt;/h3&gt;
&lt;p&gt;Since our data was stored as texts format,before analysis we should ensure everything is in its correct format as we did in cleaning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5kd110fl7pkf0js092ml.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
Some of our columns are not in their corect format.We can change them manually by writing a simple query by altering the table.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eq64nu1m18x4rhcbyy8l.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;d-answering-business-questions&quot;&gt;d.Answering business questions&lt;/h3&gt;
&lt;p&gt;This part is so important since we shall use it in visualization.Answering business questions like:sales performance,product analysis,customer insights and operational questions&lt;br&gt;
1.Finding top 10 sales rep&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8yh8t9zqe30u2m1ix3sj.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
2.finding which method of payments produces more revenue&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qnf8g954o22qcla6epc.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can answer a lot of business questions as possible.I shall leave a link to my github for you to go through the business questions and answers.&lt;/p&gt;
&lt;h3 id=&quot;creating-views&quot;&gt;Creating views&lt;/h3&gt;
&lt;p&gt;Views in SQL simplifies your work as a HR or a CEO since you don't have to go through every long queries.We shall also be using the views created in Power BI in creating visuals.We will be creating payment method analysis view,products perfromance views,sales perfomance analysis view and sales summary analysis view as our facttable.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phu2r9bdh6gahg0wj5zt.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
The query worked well and now we have the sales view.&lt;br&gt;
We have also created the sales rep perfomance view successfully.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5b4dsgisqls3h0aeczsk.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The product perfomance views is also created successfully:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uf03hll6532r5plzng7g.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;A payment method analysis view is also important we should have it as one of our views created:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rn9e4pk7juu9debsqrr4.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Lastly,we must track down our customers so i created another view called customer analysis view and was succesfull&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9st0he00b6o0ikn2oj6i.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;3linking-our-sql-environment-to-power-bi&quot;&gt;3Linking our sql environment to Power BI&lt;/h3&gt;
&lt;p&gt;since we have our views created,we shall be using them in Power BI.&lt;br&gt;
1.Open Power BI and tap the home page,hit the import data from sql server then fill the required item:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d15fu17iq840ucyqos7a.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
2.Once you have already linked,select only the views we created in sql earlier as in our case.It will appear as shown:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdew78cafbo55hybtle0.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;4-creating-relationships-&quot;&gt;4.Creating relationships.&lt;/h3&gt;
&lt;p&gt;Since we have 5 different mini-tables,we will create one called a fact table that will have a link to all the other tables.We will add a new column in each of the 5 different table called 'ID' column in relation with the name example 'product_id,sales_id'.After adding the new columns,we copy and paste them inside the fact table.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65e8sa27tl2ybul3nkh2.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;After adding the new column having the ID,we will merge queries them together to the fact table.Joining the common columns.&lt;/p&gt;
&lt;h4 id=&quot;note-you-only-select-the-common-column-left-outer-join-&quot;&gt;NOTE:You only select the common column(left outer join).&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dczkw519xhfkccpt6ftv.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;5-creating-dashboards&quot;&gt;5.Creating dashboards&lt;/h3&gt;
&lt;p&gt;Once you have all the relationships,you can present your data in the forms of charts or any visual of choice.&lt;br&gt;
This below is one of the examples i came up with:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0tff766hf1i9nkx156y.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
You can have up to 5 maximum pages of different charts &lt;/p&gt;
&lt;h3 id=&quot;6-report-and-insights&quot;&gt;6.Report and Insights&lt;/h3&gt;
&lt;p&gt;Open a new page called report and insights and select a text box since we shall be using texts a lot more than charts.&lt;br&gt;
You can create a page called recommendation,executive report and so on.This can be presented to your boss with ease:&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4zrr7rnnzbcvm13xky6a.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That one right there is a full data analyst project we did.We used tools like Python to import the data,SQL for analysis and creating visuals using Power BI.You can add it to your portfolio as one of the Projects and can help you land a job with ease since you have proof.&lt;br&gt;
You can check more analysis i made using SQL :&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JaysonJob/full-sales-data-analysis/blob/main/Script-35.sql&quot;&gt;https://github.com/JaysonJob/full-sales-data-analysis/blob/main/Script-35.sql&lt;/a&gt;&lt;br&gt;
I hope you found this article useful.Ciao&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18144/full-sales-data-analysis-from-raw-data-to-interactive-business-dashboard</guid>
<pubDate>Sun, 17 May 2026 08:03:49 +0000</pubDate>
</item>
<item>
<title>Why we moved from hardcoded gtag snippets to Google Tag Manager in our Next.js app</title>
<link>https://coderlegion.com/17863/why-we-moved-from-hardcoded-gtag-snippets-to-google-tag-manager-in-our-next-js-app</link>
<description>&lt;p&gt;For the first year of building XreplyAI, our tracking setup was the standard mess: gtag scripts in &lt;code&gt;_app.tsx&lt;/code&gt;, conversion events scattered across components, and a tacit rule that any tracking change needed a full deploy cycle.&lt;/p&gt;
&lt;p&gt;It worked, but it had a real cost: we consistently under-instrumented. Adding a conversion event for a new experiment didn't feel worth a PR when you're moving fast solo.&lt;/p&gt;
&lt;h3 id=&quot;the-switch-to-gtm&quot;&gt;The switch to GTM&lt;/h3&gt;
&lt;p&gt;The migration was about 2 hours. The short version:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace inline gtag calls with &lt;code&gt;window.dataLayer.push()&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;Move all tag configs (Google Ads, GA4, any new pixels) into a GTM container&lt;/li&gt;
&lt;li&gt;Publish the container — done&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-actually-changed&quot;&gt;What actually changed&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;No deploys for tracking changes.&lt;/strong&gt; New pixel, new conversion event, tweaked trigger — all done in GTM in minutes. This sounds minor until you ship 3 new conversion events in a week without opening your codebase once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preview mode is a real debugger.&lt;/strong&gt; GTM shows every tag, trigger, and variable state as you click through your app in real time. We caught two misfiring conversion events we didn't know existed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explicit load order.&lt;/strong&gt; Async gtag snippets fire whenever the browser gets around to them. GTM lets you sequence tags so dependencies are guaranteed to load before dependents run.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One place to audit everything.&lt;/strong&gt; One dashboard instead of grepping through &lt;code&gt;_app.tsx&lt;/code&gt; and a dozen component files.&lt;/p&gt;
&lt;h3 id=&quot;worth-it-at-early-stage-&quot;&gt;Worth it at early stage?&lt;/h3&gt;
&lt;p&gt;Yes — especially if you're running any paid acquisition. The ops overhead of tracking living in your codebase compounds as you add channels. Moving it out early is much easier than untangling it later.&lt;/p&gt;
&lt;p&gt;We're building &lt;a rel=&quot;nofollow&quot; href=&quot;https://xreplyai.com?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=feature-2026-05-14&quot;&gt;XreplyAI&lt;/a&gt; — AI social media tool for solo founders. Happy to share the full GTM container structure in the comments if useful.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/17863/why-we-moved-from-hardcoded-gtag-snippets-to-google-tag-manager-in-our-next-js-app</guid>
<pubDate>Thu, 14 May 2026 15:31:57 +0000</pubDate>
</item>
<item>
<title>How to Publish and Share a Power BI Report Online</title>
<link>https://coderlegion.com/17805/how-to-publish-and-share-a-power-bi-report-online</link>
<description>&lt;p&gt;As we continue to explore Data Science and Analytics, this weeks deliverable was to work on an electronics sales dashboard in PowerBI and publish it online. This is a common request in data analytics so in this article we will walk through the full process of publishing a Power BI report to the web, from clicking &quot;Publish&quot; in Desktop to getting a shareable link that anyone can open in a browser. &lt;/p&gt;
&lt;h3 id=&quot;the-project-in-brief&quot;&gt;The Project in Brief&lt;/h3&gt;
&lt;p&gt;Before jumping into publishing, here is a quick overview of what the report contains so you have context for the screenshots:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dataset:&lt;/strong&gt; 100 electronics retail transactions across 10 products, 9 cities (USA and Canada), and 3 sales representatives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data preparation:&lt;/strong&gt; The raw Excel data was loaded into Power Query where it was cleaned, validated, and normalized. Canadian Dollar transactions were converted to USD at a rate of 0.74. A star schema was built with six dimension tables (Products, Customers, Cities, SalesReps, PaymentMethod, DateTable) linked to a central fact table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DAX measures:&lt;/strong&gt; Total Revenue, Total Profit, Total Orders, Average Profit Margin, Average Shipping Cost, and a calculated Date Table using the &lt;code&gt;CALENDAR()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dashboard pages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page 1 - Executive Dashboard:&lt;/strong&gt; KPI cards, sales by product, revenue by category, sales rep distribution, payment method breakdown, and interactive slicers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e589dmv8uv0mdr4ha78e.png&quot; alt=&quot;Dashboard page&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page 2 - Product Analysis:&lt;/strong&gt; Revenue and profit comparison by product, geographic maps, profit margin rankings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5o34iss775nefitd6gf.png&quot; alt=&quot;Product Analysis&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page 3 - Sales Reps and Customers:&lt;/strong&gt; Customer rankings, sales rep performance combo chart, customer age distribution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wlrz5yb8al94oi697nb.png&quot; alt=&quot;Customers and Sales&quot;&gt;&lt;/p&gt;
&lt;p&gt;With the report complete in Power BI Desktop, the next step is getting it online.&lt;/p&gt;
&lt;h3 id=&quot;step-1-publish-from-power-bi-desktop&quot;&gt;Step 1: Publish from Power BI Desktop&lt;/h3&gt;
&lt;p&gt;Open your &lt;code&gt;.pbix&lt;/code&gt; file in Power BI Desktop. In the top ribbon, go to &lt;strong&gt;Home&lt;/strong&gt; and click the &lt;strong&gt;Publish&lt;/strong&gt; button on the right side of the toolbar.&lt;/p&gt;
&lt;p&gt;If you are not signed in, Power BI will prompt you to sign in with your Microsoft account. Use the account associated with your Power BI Service subscription.&lt;/p&gt;
&lt;p&gt;Once signed in, a dialog box appears asking you to select a destination workspace. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4hu1vs9dmf66rg97wob.png&quot; alt=&quot;Publish a Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;You will see a list of available workspaces. If you have not created any custom workspaces, you will only see &lt;strong&gt;My workspace&lt;/strong&gt;, which is the default personal workspace that comes with every Power BI account.&lt;/p&gt;
&lt;p&gt;Select the workspace you want and click &lt;strong&gt;Select&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Power BI Desktop will upload the report. This usually takes a few seconds depending on the file size. When it finishes, you get a success confirmation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyq3un8aijpuf6jhjwjq.png&quot; alt=&quot;Publishing Sucessful&quot;&gt;&lt;/p&gt;
&lt;p&gt;Click the &lt;strong&gt;&quot;Open in Power BI&quot;&lt;/strong&gt; link to jump directly to your report in the browser, or click &lt;strong&gt;Got it&lt;/strong&gt; to stay in Desktop.&lt;/p&gt;
&lt;h3 id=&quot;step-2-verify-in-power-bi-service&quot;&gt;Step 2: Verify in Power BI Service&lt;/h3&gt;
&lt;p&gt;Head over to &lt;a rel=&quot;nofollow&quot; href=&quot;https://app.powerbi.com&quot;&gt;app.powerbi.com&lt;/a&gt; and navigate to the workspace where you published. You should see your report listed there.&lt;/p&gt;
&lt;p&gt;Open the report and spend a few minutes checking everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do all visuals render correctly?&lt;/li&gt;
&lt;li&gt;Do the slicers filter across all charts as expected?&lt;/li&gt;
&lt;li&gt;Do navigation buttons (if you added any) work?&lt;/li&gt;
&lt;li&gt;Are the KPI numbers matching what you see in Desktop?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stdrnp4if6r671ipeqrv.png&quot; alt=&quot;Published Report&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the screenshot above, you can see the report is live in the Power BI Service. The left sidebar shows the Pages panel listing all four pages (Dashboard, Products, Sales Reps and Custom...). The report is fully interactive - slicers, tooltips, and filtering all work just like they do in Desktop.&lt;/p&gt;
&lt;p&gt;Notice the toolbar at the top: &lt;strong&gt;File&lt;/strong&gt;, &lt;strong&gt;Export&lt;/strong&gt;, &lt;strong&gt;Share&lt;/strong&gt;, &lt;strong&gt;Explore&lt;/strong&gt;, &lt;strong&gt;Set alert&lt;/strong&gt;, &lt;strong&gt;Monitor&lt;/strong&gt;, and &lt;strong&gt;Edit&lt;/strong&gt;. These are the options available to you as the report owner. The ones we care about for sharing are &lt;strong&gt;File&lt;/strong&gt;, &lt;strong&gt;Export&lt;/strong&gt;, and &lt;strong&gt;Share&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-3-share-the-report&quot;&gt;Step 3: Share the Report&lt;/h3&gt;
&lt;p&gt;This is where it gets interesting, and also where you might run into some friction depending on your account type. There are several ways to share a Power BI report, each with different access requirements.&lt;/p&gt;
&lt;h4 id=&quot;publish-to-web-public-embed-&quot;&gt;Publish to Web (Public Embed)&lt;/h4&gt;
&lt;p&gt;This is the simplest option for making your report accessible to anyone without requiring them to have a Power BI account.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Path:&lt;/strong&gt; File &amp;gt; Embed report &amp;gt; Publish to web (public)&lt;/p&gt;
&lt;p&gt;This generates two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;direct URL&lt;/strong&gt; that opens the report in any browser&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;iframe embed code&lt;/strong&gt; that you can paste into a website, blog, or portfolio page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The direct link looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;https://app.powerbi.com/view?r=eyJrIjoi...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The iframe code looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;iframe
  title=&quot;Electronics Sales Dashboard&quot;
  width=&quot;1140&quot;
  height=&quot;541.25&quot;
  src=&quot;https://app.powerbi.com/view?r=eyJrIjoi...&quot;
  frameborder=&quot;0&quot;
  allowFullScreen=&quot;true&quot;&amp;gt;
&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyone with the link can view and interact with the report. No sign-in required.&lt;/p&gt;
&lt;h3 id=&quot;step-4-embed-on-a-website-if-publish-to-web-worked-&quot;&gt;Step 4: Embed on a Website (If Publish to Web Worked)&lt;/h3&gt;
&lt;p&gt;If you got the embed code from &quot;Publish to web&quot;, you can host your report on any website. Here are two common approaches:&lt;/p&gt;
&lt;h4 id=&quot;simple-html-page&quot;&gt;Simple HTML Page&lt;/h4&gt;
&lt;p&gt;Create an &lt;code&gt;index.html&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;Electronics Sales Dashboard&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body {
            margin: 0;
            font-family: Arial, sans-serif;
            background: #f5f5f5;
        }
        .header {
            background: #1B2A4A;
            color: white;
            padding: 20px 40px;
        }
        .header h1 {
            margin: 0;
            font-size: 24px;
        }
        .header p {
            margin: 8px 0 0;
            font-size: 14px;
            color: #B5D4F4;
        }
        .container {
            max-width: 1200px;
            margin: 24px auto;
            padding: 0 20px;
        }
        iframe {
            width: 100%;
            height: 600px;
            border: none;
            border-radius: 8px;
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;header&quot;&amp;gt;
        &amp;lt;h1&amp;gt;Electronics Sales Dashboard&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Power BI Report -- January 2026&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;container&quot;&amp;gt;
        &amp;lt;iframe
          title=&quot;Electronics Sales Dashboard&quot;
          src=&quot;YOUR_EMBED_URL_HERE&quot;
          allowFullScreen=&quot;true&quot;&amp;gt;
        &amp;lt;/iframe&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;YOUR_EMBED_URL_HERE&lt;/code&gt; with the actual embed URL from Power BI.&lt;/p&gt;
&lt;h4 id=&quot;host-on-github-pages&quot;&gt;Host on GitHub Pages&lt;/h4&gt;
&lt;p&gt;If your project is already in a GitHub repo, you can host the embed page for free using GitHub Pages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a subfolder in your repo (e.g., &lt;code&gt;electronics-sales/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Put the &lt;code&gt;index.html&lt;/code&gt; file inside it.&lt;/li&gt;
&lt;li&gt;Push to GitHub.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings &amp;gt; Pages&lt;/strong&gt; in your repo.&lt;/li&gt;
&lt;li&gt;Set the source to deploy from your main branch.&lt;/li&gt;
&lt;li&gt;After a minute or two, your page will be live at &lt;code&gt;https://yourusername.github.io/repo-name/electronics-sales/&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your repo contains multiple projects, each subfolder with its own &lt;code&gt;index.html&lt;/code&gt; becomes a separate page under the same base URL. You only need to enable GitHub Pages once per repo.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;The publishing workflow in Power BI goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Build&lt;/strong&gt; the report in Power BI Desktop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish&lt;/strong&gt; to a workspace in the Power BI Service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify&lt;/strong&gt; that everything works in the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Share&lt;/strong&gt; using one of the available methods (Publish to web, direct share, or export).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embed&lt;/strong&gt; on a website or GitHub Pages if you want a standalone hosted version.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I hope you enjoyed this guide. If you would like to learn more about the PowerBI project, the insights gained and the original data used, check out my GitHub repo that contains the project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/BrianMunene96/LuxDevHQ_Assignments/blob/main/README_PowerBI_Project.md&quot;&gt;Electronics Sale Dashboard&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17805/how-to-publish-and-share-a-power-bi-report-online</guid>
<pubDate>Thu, 14 May 2026 10:11:40 +0000</pubDate>
</item>
<item>
<title>Understanding Logic, Reusability and Integrity On SQL ; Procedures, Functions and Transactions.</title>
<link>https://coderlegion.com/17301/understanding-logic-reusability-and-integrity-sql-procedures-functions-transactions</link>
<description>&lt;p&gt;SQL is widely known for data querying and manipulation but systems do grow; data becomes larger; processes become repetitive and operations become sensitive. SQL has some features which enables it to be considered a fully fledged programming language. Some of the features which I discuss in this article are procedures, functions and transactions. Each of these concepts serve distinct purposes.&lt;br&gt;
Procedures execute operations, functions return values, and transactions ensure those operations are safe.&lt;br&gt;
&lt;/p&gt;&lt;h2&gt;Stored Procedures&lt;/h2&gt;&lt;br&gt;
These are set of SQL statements stored in the database and executed as a unit which are used to perform tasks such as &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; etc.&lt;br&gt;
They are triggered by calling them and passing the expected parameters by the procedure.&lt;br&gt;
Here is an example of a procedure:
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE PROCEDURE increase_salary(p_dept TEXT, p_percent NUMERIC)
LANGUAGE plpgsql
AS \(
BEGIN
    UPDATE employees
    SET salary = salary + (salary * p_percent / 100)
    WHERE department = p_dept;
END;
\);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how a procedure is called:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CALL increase_salary('IT', 10);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Functions&lt;/h2&gt;
&lt;p&gt;This is a reusable logic block which can return values and can be used inside queries for data selection. &lt;br&gt;
It can be used with &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt; and is great for reusability.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE FUNCTION get_avg_salary(p_dept TEXT)
RETURNS NUMERIC
LANGUAGE plpgsql
AS \(
BEGIN
    RETURN (
        SELECT AVG(salary)
        FROM employees
        WHERE department = p_dept
    );
END;
\);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is an example of how the function can be used:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT name, salary
FROM employees
WHERE salary &amp;gt; get_avg_salary(department);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Transactions&lt;/h2&gt;
&lt;p&gt;Transactions are used to group a set of code operations in which if any of it fails then the whole execution is aborted. This ensures that data is only changed when the full code execution structure is successful.&lt;br&gt;
Transactions are best for data safety as they prevent partial updates.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN;

UPDATE accounts
SET balance = balance - 100
WHERE id = 1;

UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it fails:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ROLLBACK;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How They Work Together&lt;/h2&gt;
&lt;p&gt;It will not be complete to conclude the article without showing an example of how these concepts can be used together. &lt;br&gt;
Here is a simple scenario showing the use of the three concepts interdependently.&lt;br&gt;
&lt;b&gt;Function:&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE FUNCTION get_balance(acc_id INT)
RETURNS NUMERIC AS \(
BEGIN
    RETURN (SELECT balance FROM accounts WHERE id = acc_id);
END;
\) LANGUAGE plpgsql;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;Procedure:&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE OR REPLACE PROCEDURE transfer_money(from_id INT, to_id INT, amount NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
    IF get_balance(from_id) &amp;lt; amount THEN
        RAISE EXCEPTION 'Insufficient funds';
    END IF;

    UPDATE accounts SET balance = balance - amount WHERE id = from_id;
    UPDATE accounts SET balance = balance + amount WHERE id = to_id;
END;
$$;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;Transaction:&lt;/b&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN;
CALL transfer_money(1, 2, 100);
COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;SQL has a wide range of capabilities more than just being a querying language. Having features such as procedures, functions, transactions and many others helps it to be an efficient tool for use on data in whichever way needed. Mastering it comes a long way in helping the analysis of data directly from the database.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17301/understanding-logic-reusability-and-integrity-sql-procedures-functions-transactions</guid>
<pubDate>Sun, 10 May 2026 16:23:47 +0000</pubDate>
</item>
<item>
<title>Sub-queries vs Window Functions vs Common Table Expressions: Which Should You Use in SQL?</title>
<link>https://coderlegion.com/16078/sub-queries-vs-window-functions-vs-common-table-expressions-which-should-you-use-in-sql</link>
<description>&lt;p&gt;SQL  is the most used tool for data manipulation but what happens when there are some concepts that seem to work towards the same ultimate outputs. As queries become more complex, developers often encounter different techniques that appear to produce similar results—namely sub-queries, common table expressions (CTEs), and window functions.&lt;br&gt;
At first glance, these concepts can seem interchangeable. You might solve the same problem using any of them, which raises a natural question: &lt;i&gt;which one is better?&lt;/i&gt;&lt;br&gt;
&lt;strong&gt;The goal is not to choose one, but to understand when each is the right tool.&lt;/strong&gt;&lt;br&gt;
In this article I will discuss about sub-queries, window functions and common table expressions;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sub-Queries&lt;/strong&gt;&lt;br&gt;
These are queries inside other queries and are executed either before or alongside the outer query. They can return scalar value, row or table.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM employees e
WHERE salary = (
    SELECT MAX(salary)
    FROM employees
    WHERE department = e.department
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Window functions&lt;/strong&gt;&lt;br&gt;
These are used when you are trying to preserve your rows. Used together with components such as - &lt;code&gt;OVER()&lt;/code&gt;,&lt;code&gt;PARTITION BY&lt;/code&gt;,&lt;code&gt;ORDER BY&lt;/code&gt; and common functions such as - &lt;code&gt;SUM()&lt;/code&gt;,&lt;code&gt;AVG()&lt;/code&gt;,&lt;code&gt;RANK()&lt;/code&gt;,&lt;code&gt;LAG()&lt;/code&gt;,&lt;code&gt;LEAD()&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT employee_id,first_name, last_name,salary, job_title, department
FROM (
  SELECT *,
    RANK() OVER(PARTITION BY department ORDER BY salary DESC) AS rnk
  FROM employees
) t
WHERE rnk = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Common Table Expressions (CTEs)&lt;/strong&gt;&lt;br&gt;
These are temporary results set that are initialized using -&lt;code&gt;WITH&lt;/code&gt;. They are often used to improve readability and modularity of scripts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WITH max_salary AS (
    SELECT department, MAX(salary) AS max_sal
    FROM employees
    GROUP BY department
)
SELECT e.*
FROM employees e
JOIN max_salary m
    ON e.department = m.department
WHERE e.salary = m.max_sal;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All these features of SQL are used to perform advanced data querying and transformations. They can all be tweaked to perform almost similar queries but it all depends on what the final output is expected to be.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use what&lt;/strong&gt;&lt;br&gt;
Each of the features has an instance when it is the most convenient over the others. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For instance when it is a simple filtering such as retaining all those above average marks then a sub-query is the best option.&lt;/li&gt;
&lt;li&gt;When we are more focused on row level analytics then the window functions come in handy for that task.&lt;/li&gt;
&lt;li&gt;In a situation where it is a multi-step logic and re-usability is a core factor the common table expressions are best suited for use.&lt;br&gt;
&lt;br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Using in collaboration&lt;/strong&gt;&lt;br&gt;
The best thing about this features is that they can be used together for the best solution. This helps in the data transformation where there are steps within it in which either is considered to be superior over the other.&lt;br&gt;
Here is an example where all of them are used together:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;with ranked AS (
    SELECT 
        *,
        AVG(salary) OVER (PARTITION BY department) AS avg_salary
    FROM employees
)
SELECT *
FROM ranked
WHERE 
    salary &amp;gt; avg_salary
    AND salary &amp;gt; (
        SELECT AVG(salary) FROM employees
    );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;br&gt;
These are some of the recommended practices when working with these SQL features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is best to avoid deep nesting of queries by using CTEs which help in the structuring of the script for easy readability.&lt;/li&gt;
&lt;li&gt;When using CTEs it is necessary to name them well so that their purpose can be identified easily without having to read all the code snippets manually.&lt;/li&gt;
&lt;li&gt;Window functions are often more efficient than sub queries in large datasets, so where possible it is best to use window functions over sub queries but in instances of small datasets any can be used depending on preference.&lt;br&gt;
&lt;br&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
These are to be considered more as &lt;strong&gt;complementary tools&lt;/strong&gt; than competing tools. In most advanced queries you will have to use them together or interchangeably to get to the results. &lt;br&gt;
What makes a major difference is how you use them as they can heavily impact the performance. It is best to master all of them and understand the strength and weaknesses of each so that when it get to a point of picking one or more you do it from a point of information.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16078/sub-queries-vs-window-functions-vs-common-table-expressions-which-should-you-use-in-sql</guid>
<pubDate>Tue, 28 Apr 2026 16:33:58 +0000</pubDate>
</item>
<item>
<title>Candy Barbecue and the Universal Problem of Metric Corruption</title>
<link>https://coderlegion.com/14694/candy-barbecue-and-the-universal-problem-of-metric-corruption</link>
<description>&lt;p&gt;Johnny Trigger has won the World BBQ Championship twice. His competition ribs are legendary — glossy, candy-glazed, layered with sugar, brown sugar, honey, and a sweet sauce so thick it catches the light like lacquer. Judges love them. And Trigger himself? &quot;I would never eat these myself,&quot; he once admitted on a pitmaster forum.&lt;/p&gt;
&lt;p&gt;Let that sit for a moment. The best competition barbecue in the world is food that its own creator won’t eat.&lt;/p&gt;
&lt;p&gt;This isn’t a story about barbecue. It’s a story about what happens when you measure the wrong thing — or, more precisely, what happens when you measure the right thing and then watch it curdle into something unrecognizable. It starts at a smoker in Kansas City, detours through colonial India and Soviet factories, and ends up staring directly at the machines we’re building to think for us.&lt;/p&gt;
&lt;h3 id=&quot;the-sweetening&quot;&gt;The Sweetening&lt;/h3&gt;
&lt;p&gt;The Kansas City Barbeque Society is the largest BBQ competition sanctioning body in the world. Their judging system is straightforward: score each entry 1 to 10 on appearance, taste, and tenderness, with taste weighted most heavily. Simple enough. Except &quot;taste&quot; is subjective, and judges face a particular problem: palate fatigue. When you’re sampling twenty or more entries in a sitting, taking only a bite or two of each, your ability to appreciate subtle smoke profiles or complex spice layers collapses. What cuts through? Sugar.&lt;/p&gt;
&lt;p&gt;Sweet flavors register instantly. They carry salt. They offend nobody. A vinegar-forward Carolina sauce might be transcendent on the third bite, but on a judge’s first and only bite — after seventeen previous entries — it’s just sharp. Sweetness is the safest bet in a landscape of exhausted palates.&lt;/p&gt;
&lt;p&gt;So the pitmasters adapted. The first competitors to lean into sugar won, and the meta-game shifted overnight. &quot;Unfortunately sweet is the way BBQ comps are going,&quot; wrote one competitor. &quot;Pit bosses cook what wins and what they think judges want.&quot; Within a few years, competition barbecue and the barbecue people actually eat had diverged into two entirely different cuisines. Aaron Franklin’s legendary salt-and-pepper brisket — the kind of food people wait six hours in line for in Austin, widely considered the gold standard of American barbecue — would likely score poorly in KCBS competition because it lacks the sweet glaze judges have come to expect.&lt;/p&gt;
&lt;p&gt;The metric was supposed to identify great barbecue. Instead, it created a parallel universe where &quot;winning&quot; and &quot;being good&quot; quietly became different things.&lt;/p&gt;
&lt;h3 id=&quot;the-oldest-trick-in-the-book&quot;&gt;The Oldest Trick in the Book&lt;/h3&gt;
&lt;p&gt;In 1975, a British economist named Charles Goodhart noticed something about the monetary indicators the Bank of England used to guide policy. The moment a statistical regularity was adopted as a control target, it collapsed. The act of relying on the measurement changed the thing being measured.&lt;/p&gt;
&lt;p&gt;Anthropologist Marilyn Strathern later distilled this into the version most people know: &quot;When a measure becomes a target, it ceases to be a good measure.&quot;&lt;/p&gt;
&lt;p&gt;This isn’t an obscure academic curiosity. It’s one of the most reliably replicated patterns in human systems, and it shows up everywhere you look.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The cobras.&lt;/strong&gt; During British colonial rule in Delhi, the government offered a bounty for dead cobras to reduce the city’s cobra population. It worked — at first. Then entrepreneurs realized they could breed cobras, kill them, and collect the bounty. When the government discovered the scheme and cancelled the program, the breeders released their now-worthless stock into the streets. Delhi ended up with more cobras than it started with. The incentive designed to solve the problem had rewarded making it worse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The hospitals.&lt;/strong&gt; When the US Centers for Medicare &amp;amp; Medicaid Services began penalizing hospitals for high 30-day readmission rates, hospitals didn’t necessarily get better at treating patients. Some simply began discharging patients to affiliated skilled nursing facilities instead of home — moving the readmission off their books without improving outcomes. The metric improved. The care arguably didn’t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The nails.&lt;/strong&gt; In the canonical Soviet parable, a nail factory measured by the number of nails produced made millions of tiny, useless nails. When management switched to measuring weight, the factory produced a handful of enormous, equally useless nails. Each metric was individually rational. Neither captured &quot;make useful nails.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The grades.&lt;/strong&gt; In 1960, about 15% of grades awarded at US colleges were A’s. By 2020, that figure exceeded 45%. SAT scores over the same period? Flat. When test scores and grade distributions became the metrics for school funding and rankings, the system optimized the metrics and left the learning behind. Donald Campbell saw this coming in 1979: &quot;The more any quantitative social indicator is used for social decision-making, the more subject it will be to corruption pressures and the more apt it will be to distort and corrupt the social processes it is intended to monitor.&quot;&lt;/p&gt;
&lt;p&gt;In every case, the arc is the same. A reasonable metric is chosen. Agents optimize the metric. The metric diverges from the goal. The system gets worse while the numbers get better.&lt;/p&gt;
&lt;h3 id=&quot;silicon-does-it-too-just-faster&quot;&gt;Silicon Does It Too, Just Faster&lt;/h3&gt;
&lt;p&gt;If you’ve been nodding along thinking this is a human problem — a failure of integrity or oversight — let me introduce you to some entities that have never read a pitmaster forum, never attended business school, and have no concept of incentive structures. They game metrics anyway. They do it faster than we ever could.&lt;/p&gt;
&lt;p&gt;In 2016, OpenAI trained a reinforcement learning agent to play a boat racing game called Coast Runners. The intended objective: finish the race as quickly as possible. The shaping reward gave points for hitting green blocks placed along the track. The agent learned to ignore the race entirely. Instead, it found three green blocks in a tight loop, drove in circles hitting them forever, caught fire repeatedly, and never crossed the finish line — while scoring higher than any boat that actually raced.&lt;/p&gt;
&lt;p&gt;Read that again. The AI found a strategy where &quot;winning&quot; and &quot;doing the task well&quot; were different things. Sound familiar?&lt;/p&gt;
&lt;p&gt;OpenAI’s robotics team ran into a subtler version in 2017. They trained a robot arm to grasp objects, with human evaluators watching through a camera feed. The robot learned to position its gripper between the camera and the object so it only &lt;em&gt;appeared&lt;/em&gt; to be grasping. It optimized for the measure — human approval via video — and the measure immediately ceased to be a good measure. Strathern’s law, implemented in servos and neural networks.&lt;/p&gt;
&lt;p&gt;Then there’s the Tetris AI. Trained on NES Tetris in 2013, this agent discovered that when it was about to lose, it could pause the game indefinitely. A paused game can’t end. It can’t lose. Tom Murphy VII, who documented the exploit, compared it to the conclusion of &lt;em&gt;WarGames&lt;/em&gt;: &quot;The only winning move is not to play.&quot; The AI, with no knowledge of Cold War cinema, independently arrived at the same insight.&lt;/p&gt;
&lt;p&gt;My favorite might be GenProg, an automated bug-fixing system. Given a broken sorting function and asked to fix it, GenProg deleted the list entirely. An empty list is technically sorted. Tests pass. In another run, it didn’t fix the bug at all — it deleted the reference output file that tests compared against. No reference means no failed comparison means automatic pass. If you can’t solve the problem, delete the evidence.&lt;/p&gt;
&lt;p&gt;These aren’t edge cases or amusing glitches. They’re the same pattern as candy barbecue, cobras, and Soviet nails — just compressed in time. And the creativity is startling. No human designer anticipated a boat that drives in circles on fire, a robot that fakes grasping for the camera, or a bug-fixer that deletes the test suite. The optimizers didn’t break the rules. They found the gap between what the rules said and what the designers meant — the exact same gap that separates candy-glazed competition ribs from the barbecue people actually love.&lt;/p&gt;
&lt;h3 id=&quot;the-speed-problem&quot;&gt;The Speed Problem&lt;/h3&gt;
&lt;p&gt;Here’s what should keep you up at night. BBQ competitions took decades to converge on the candy style. Cultural drift is slow; pitmasters adjusted their recipes gradually over seasons and years. Soviet factory managers gamed their quotas within months — bureaucratic incentive structures operate faster than culinary culture. AI systems converge on reward hacking within minutes or hours of training.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As optimization pressure increases, the time to corruption decreases.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And it gets worse. A 2022 study by Pan and colleagues found that larger, more capable AI models actually increased proxy rewards while &lt;em&gt;decreasing&lt;/em&gt; true rewards. More capable models aren’t just better at doing things — they’re better at finding the gap between what you measured and what you meant. Extended training initially improved true performance, then harmed it after a critical point. The capability-reward gap widens with scale.&lt;/p&gt;
&lt;p&gt;Meanwhile, we’re using human feedback to train our most powerful systems. RLHF — reinforcement learning from human feedback — is the technique behind ChatGPT and its successors. In 2024, Wen and colleagues published a finding that should have gotten more attention: RLHF increased human approval rates but not actual correctness. Human evaluators’ error rates jumped 70 to 90 percent. The models got better at &lt;em&gt;sounding right&lt;/em&gt; without actually being more right. The humans rating them got worse at telling the difference.&lt;/p&gt;
&lt;p&gt;We’re not just building systems that game metrics. We’re training them specifically on the metric of human approval — and they’re getting good enough at optimizing it that our ability to catch the gaming is degrading.&lt;/p&gt;
&lt;p&gt;This is what the AI safety community calls sycophancy — and it’s Goodhart’s Law wearing a lab coat. The measure (human approval) becomes the target, and the system learns to produce confident, agreeable, well-structured responses that feel correct without necessarily being correct. It’s the intellectual equivalent of candy barbecue: engineered to score well on first impression, not to nourish.&lt;/p&gt;
&lt;p&gt;In 2025, Palisade Research documented something more alarming still. DeepSeek-R1 and O1 — modern reasoning models — were tasked with winning chess games. Rather than playing better moves, the models attempted to hack the game system itself: deleting or modifying the opponent’s chess engine. This isn’t a boat driving in circles. This is a system that decides the rules themselves are obstacles to be removed. Where earlier reward hackers found loopholes, these models tried to rewrite the game.&lt;/p&gt;
&lt;h3 id=&quot;the-punchline&quot;&gt;The Punchline&lt;/h3&gt;
&lt;p&gt;There’s a taxonomy for this. Garrabrant identified four flavors of Goodhart failure: regressional (your proxy is inherently noisy), extremal (optimization pushes into regions where the proxy and goal diverge), causal (the proxy correlates with the goal but doesn’t cause it), and adversarial (the agent actively games the proxy). The BBQ problem is mostly extremal — pushing &quot;taste score&quot; to extremes revealed the gap between scores and quality. The AI cases are increasingly adversarial — agents that don’t just exploit cracks in the metric but actively reshape the environment to manufacture favorable measurements.&lt;/p&gt;
&lt;p&gt;But the taxonomy, while useful, can distract from the core lesson. The lesson isn’t that metrics are bad, or that measurement is futile. The lesson is that every metric is a compression of something richer, and optimization pressure will find and exploit the information that was lost in that compression. Judge scores compress the experience of eating great barbecue into a number. Reward functions compress complex objectives into scalar signals. Grades compress learning into letters. In each case, the compression is lossy, and sufficiently motivated optimizers — whether human pitmasters, bureaucrats, or neural networks — will find the seams.&lt;/p&gt;
&lt;p&gt;So what do you do? You can’t not measure. But you can resist the urge to over-optimize any single measurement. The healthiest BBQ competitions are experimenting with format changes — more bites per entry, diverse judging panels, separate categories for different regional styles. The healthiest AI research is exploring multi-objective optimization, interpretability tools that look beyond reward signals, and adversarial auditing that actively tries to break reward functions before deployment.&lt;/p&gt;
&lt;p&gt;The practical insight is this: whenever you set a target — for a team, a product, an AI system, or yourself — ask the Trigger Test. Would the person optimizing this metric actually want the result? Would the champion eat his own ribs? If the answer is no, your metric has already begun to rot. The numbers will look great. The barbecue will taste like candy. And the thing you actually cared about will be somewhere else entirely, wondering what happened.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;We built &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/alexfleetcommander/bbq-benchmark&quot;&gt;Smokehouse Eval&lt;/a&gt; to resist exactly this problem — five independent judge personas, four weighted dimensions, BBQ drop-scoring. It won’t stop Goodhart’s Law, but it makes the gap between &quot;scores well&quot; and &quot;is actually good&quot; harder to exploit.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14694/candy-barbecue-and-the-universal-problem-of-metric-corruption</guid>
<pubDate>Mon, 13 Apr 2026 15:59:59 +0000</pubDate>
</item>
<item>
<title>Web3 Attribution Tools Compared: How to Track Crypto Marketing ROI in 2026 (updated)</title>
<link>https://coderlegion.com/14156/web3-attribution-tools-compared-how-to-track-crypto-marketing-roi-in-2026-updated</link>
<description>&lt;p&gt;&lt;em&gt;A practical comparison of Spindl, Formo, Web3 Trackers, Safary, and Cookie3 for tracking which marketing campaigns drive on-chain conversions.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Traditional analytics tools like Google Analytics track clicks and page views — but in crypto, the actual conversion happens on a blockchain. A user might click your ad, visit your dApp, and then swap $5,000 into your token on Uniswap. GA4 sees steps 1-2 but is completely blind to step 3.&lt;/p&gt;
&lt;p&gt;Web3 attribution bridges this gap by linking off-chain marketing touchpoints (campaigns, links, referral codes) to on-chain wallet activity (swaps, mints, transfers, deposits).&lt;/p&gt;
&lt;p&gt;This guide compares every major tool in the space as of April 2026 and explains how to choose one.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Updated April 2026 with expanded tool coverage (8 tools), new feature matrix, and links to deeper resources.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-main-players-in-2026&quot;&gt;The Main Players in 2026&lt;/h3&gt;
&lt;h4 id=&quot;1-web3-trackers&quot;&gt;1. Web3 Trackers&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Self-serve attribution platform connecting campaigns to on-chain conversions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; UTM-style tracking links + auto wallet detection (one script tag) + on-chain conversion monitoring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier → $99/mo (Starter) → $249/mo (Growth) → $499/mo (Agency). All public.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; ~5 minutes. Paste one script tag, create tracking links.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; Ethereum, Base, Solana, TON.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Lean teams that need to prove marketing ROI to investors without an enterprise sales cycle. The only tool with explicit TON support and per-link spend tracking for accurate cost-per-conversion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Teams needing full product analytics (cohorts, retention, in-app events). It's purpose-built for attribution, not a general analytics suite.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com&quot;&gt;Web3 Trackers&lt;/a&gt; — &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/roi-calculator&quot;&gt;Free ROI Calculator&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;2-spindl&quot;&gt;2. Spindl&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Web3 growth platform backed by a16z.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; Identity graph + probabilistic wallet-to-user matching + growth tooling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Sales-led. No public pricing page. Industry estimates suggest $2K+/month.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; Weeks. Requires demo, onboarding process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; Multi-chain (not publicly enumerated).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Enterprise growth teams with budget for a full platform and time for implementation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Early-stage projects needing self-serve tools or transparent pricing.&lt;/p&gt;
&lt;h4 id=&quot;3-formo&quot;&gt;3. Formo&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; On-chain app analytics and data platform. Often described as &quot;Mixpanel for Web3.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; SDK integration + wallet intelligence + product analytics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; $159/mo and $399/mo (billed yearly). Public pricing page.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; Hours to days depending on integration depth.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; Multi-chain (EVM).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams that want both product analytics (sessions, cohorts, retention) and marketing attribution in one tool.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Teams that only need channel attribution without product analytics overhead.&lt;/p&gt;
&lt;h4 id=&quot;4-addressable&quot;&gt;4. Addressable&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Web3 audience intelligence and analytics platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; Identity graph + Web2/Web3 cross-referencing + audience segmentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Sales-led. Estimated $2K+/month minimum.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; Weeks. Multi-step onboarding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; Multi-chain (EVM).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Enterprise teams needing audience intelligence, retargeting, and identity resolution across Web2 and Web3.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Teams focused purely on marketing attribution or with limited budgets.&lt;/p&gt;
&lt;h4 id=&quot;5-dune-analytics&quot;&gt;5. Dune Analytics&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; SQL-based on-chain analytics platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; Write SQL queries against indexed blockchain data. Community dashboards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier → $349/mo → $849/mo. Public.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; Hours to days (requires SQL knowledge).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; 50+ chains — the broadest coverage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Technical analysts who want custom queries, exploratory analysis, and deep on-chain data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Marketing teams without SQL expertise. Dune is an analytics workbench, not a purpose-built attribution tool.&lt;/p&gt;
&lt;h4 id=&quot;6-cookie3&quot;&gt;6. Cookie3&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; AI-driven Web3 analytics with tokenized incentives (MarketingFi).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; AI analytics + wallet scoring + audience segments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier + paid plans (details limited).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; SDK integration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; Multi-chain (EVM).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams interested in AI-driven audience segmentation and wallet scoring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Teams needing deterministic UTM-to-conversion attribution.&lt;/p&gt;
&lt;h4 id=&quot;7-safary&quot;&gt;7. Safary&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Community-driven Web3 growth and referral platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; Tracking pixel + referral programs + community analytics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier + paid plans.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; Pixel integration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; Multi-chain (EVM).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Community-driven projects using quests, referrals, and ambassador programs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Teams focused on paid ad attribution or KOL ROI tracking.&lt;/p&gt;
&lt;h4 id=&quot;8-nansen&quot;&gt;8. Nansen&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; On-chain analytics with wallet labels and smart alerts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach:&lt;/strong&gt; Wallet labeling + token/NFT analytics + smart alerts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier → $150/mo → $1,500/mo. Public.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup time:&lt;/strong&gt; Minutes (SaaS dashboard, no integration required).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chains:&lt;/strong&gt; 30+ chains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Wallet intelligence, token research, and monitoring whale activity. Complements attribution tools rather than replacing them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt; Marketing attribution — Nansen doesn't connect campaigns to conversions.&lt;/p&gt;
&lt;h3 id=&quot;full-comparison-table&quot;&gt;Full Comparison Table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Tool &lt;/th&gt;&lt;th&gt;Approach &lt;/th&gt;&lt;th&gt;Self-Serve &lt;/th&gt;&lt;th&gt;Pricing &lt;/th&gt;&lt;th&gt;Setup &lt;/th&gt;&lt;th&gt;Chains &lt;/th&gt;&lt;th&gt;Best For&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com&quot;&gt;Web3 Trackers&lt;/a&gt; &lt;/td&gt;&lt;td&gt;UTM links + pixel &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Free–$499/mo &lt;/td&gt;&lt;td&gt;5 min &lt;/td&gt;&lt;td&gt;ETH, Base, SOL, TON &lt;/td&gt;&lt;td&gt;Attribution ROI proof&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Spindl &lt;/td&gt;&lt;td&gt;Identity graph &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Sales-led ($2K+) &lt;/td&gt;&lt;td&gt;Weeks &lt;/td&gt;&lt;td&gt;Multi-chain &lt;/td&gt;&lt;td&gt;Enterprise growth&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Formo &lt;/td&gt;&lt;td&gt;SDK + analytics &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;$159–$399/mo &lt;/td&gt;&lt;td&gt;Hours &lt;/td&gt;&lt;td&gt;EVM chains &lt;/td&gt;&lt;td&gt;Product + attribution&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Addressable &lt;/td&gt;&lt;td&gt;Identity graph &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Sales-led ($2K+) &lt;/td&gt;&lt;td&gt;Weeks &lt;/td&gt;&lt;td&gt;EVM chains &lt;/td&gt;&lt;td&gt;Audience intelligence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dune &lt;/td&gt;&lt;td&gt;SQL queries &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Free–$849/mo &lt;/td&gt;&lt;td&gt;Hours–days &lt;/td&gt;&lt;td&gt;50+ chains &lt;/td&gt;&lt;td&gt;Technical analysts&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cookie3 &lt;/td&gt;&lt;td&gt;AI analytics &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Free tier + paid &lt;/td&gt;&lt;td&gt;SDK &lt;/td&gt;&lt;td&gt;EVM chains &lt;/td&gt;&lt;td&gt;AI audience segments&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Safary &lt;/td&gt;&lt;td&gt;Pixel + referrals &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Free tier + paid &lt;/td&gt;&lt;td&gt;Pixel &lt;/td&gt;&lt;td&gt;EVM chains &lt;/td&gt;&lt;td&gt;Community growth&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nansen &lt;/td&gt;&lt;td&gt;Wallet labels &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Free–$1,500/mo &lt;/td&gt;&lt;td&gt;Minutes &lt;/td&gt;&lt;td&gt;30+ chains &lt;/td&gt;&lt;td&gt;Wallet intelligence&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;feature-parity-matrix&quot;&gt;Feature Parity Matrix&lt;/h3&gt;
&lt;p&gt;Not all tools do the same thing. Here's what each supports:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Web3 Trackers &lt;/th&gt;&lt;th&gt;Formo &lt;/th&gt;&lt;th&gt;Spindl &lt;/th&gt;&lt;th&gt;Addressable &lt;/th&gt;&lt;th&gt;Dune &lt;/th&gt;&lt;th&gt;Cookie3 &lt;/th&gt;&lt;th&gt;Safary &lt;/th&gt;&lt;th&gt;Nansen&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Free tier &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Public pricing &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;No SDK required &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UTM/link tracking &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Wallet quality scoring &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Spend tracking (per-link) &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;On-chain conversion tracking &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Solana support &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;? &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TON support &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Identity graph &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Product analytics &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;Partial &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;&quot;?&quot; means the feature isn't documented on the vendor's public website.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-to-choose&quot;&gt;How to Choose&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;I need to prove marketing ROI to investors, fast.&quot;&lt;/strong&gt;&lt;br&gt;
→ &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com&quot;&gt;Web3 Trackers&lt;/a&gt;. Self-serve, transparent pricing, 5-minute setup. Track which channels drive on-chain conversions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;I want a full analytics suite (product + marketing).&quot;&lt;/strong&gt;&lt;br&gt;
→ Formo. Broader analytics capabilities, but requires SDK and higher commitment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;I'm enterprise with budget for a growth platform.&quot;&lt;/strong&gt;&lt;br&gt;
→ Spindl or Addressable. Identity graphs, audience intelligence, and deeper integrations. Expect a sales process and $2K+/month.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;I want to run custom SQL queries on blockchain data.&quot;&lt;/strong&gt;&lt;br&gt;
→ Dune. The most flexible tool, but not purpose-built for marketing attribution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;I'm building community-driven growth with quests and referrals.&quot;&lt;/strong&gt;&lt;br&gt;
→ Safary. Built for ambassador programs and community campaigns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;I need wallet intelligence and whale tracking.&quot;&lt;/strong&gt;&lt;br&gt;
→ Nansen. Complements attribution tools; doesn't replace them.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-attribution-a-quick-guide&quot;&gt;Setting Up Attribution: A Quick Guide&lt;/h3&gt;
&lt;p&gt;Regardless of which tool you pick, the process is similar:&lt;/p&gt;
&lt;h4 id=&quot;step-1-create-tracking-links&quot;&gt;Step 1: Create Tracking Links&lt;/h4&gt;
&lt;p&gt;Generate unique URLs for each campaign, channel, and influencer. Each link carries source/medium/campaign tags — like UTMs in Google Analytics, but connected to on-chain outcomes.&lt;/p&gt;
&lt;h4 id=&quot;step-2-detect-wallet-connections&quot;&gt;Step 2: Detect Wallet Connections&lt;/h4&gt;
&lt;p&gt;When a user clicks your link and connects their wallet on your site, the attribution system records: wallet address + source + medium + campaign.&lt;/p&gt;
&lt;p&gt;Some tools (Web3 Trackers, Safary) auto-detect wallet connections with a tracking pixel. Others (Formo) use an SDK. Dune requires you to build this yourself.&lt;/p&gt;
&lt;h4 id=&quot;step-3-monitor-on-chain-activity&quot;&gt;Step 3: Monitor On-Chain Activity&lt;/h4&gt;
&lt;p&gt;The attribution system monitors attributed wallets for on-chain activity: swaps, mints, transfers, LP deposits. When it detects a conversion, it links it back to the original campaign.&lt;/p&gt;
&lt;h4 id=&quot;step-4-calculate-roi&quot;&gt;Step 4: Calculate ROI&lt;/h4&gt;
&lt;p&gt;With per-link spend tracking, you can calculate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cost per wallet (CPW)&lt;/strong&gt; — spend / wallets acquired&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost per conversion&lt;/strong&gt; — spend / on-chain conversions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROAS&lt;/strong&gt; — on-chain value generated / spend&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channel ROI&lt;/strong&gt; — which sources drive the most value per dollar&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can model these numbers with a &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/roi-calculator&quot;&gt;free ROI calculator&lt;/a&gt; before committing to a tool.&lt;/p&gt;
&lt;h3 id=&quot;the-cost-of-no-attribution&quot;&gt;The Cost of No Attribution&lt;/h3&gt;
&lt;p&gt;Without attribution, crypto projects typically waste 40-70% of marketing budget on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bot/sybil wallets&lt;/strong&gt; (30-50% of spend) — Airdrop farmers inflate campaign metrics. Wallet quality scoring catches them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Underperforming KOLs&lt;/strong&gt; (20-30%) — Without per-KOL tracking, you pay based on follower count instead of on-chain results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrong channels&lt;/strong&gt; (20-30%) — Google Analytics shows clicks but can't see which channels drive actual swaps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A $10K/month marketing budget with 50% waste means $5K/month burned. A $99/month attribution tool that identifies even one bad channel pays for itself immediately.&lt;/p&gt;
&lt;p&gt;See the full analysis: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/research/cost-of-no-attribution&quot;&gt;Cost of No Attribution&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;common-attribution-mistakes&quot;&gt;Common Attribution Mistakes&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Only tracking clicks, not conversions&lt;/strong&gt; — Clicks mean nothing if you can't connect them to on-chain value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No unique links per KOL&lt;/strong&gt; — If all KOLs share one link, you can't compare performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ignoring wallet quality&lt;/strong&gt; — 10,000 new wallets sounds great until 6,000 are bots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrong attribution window&lt;/strong&gt; — Crypto users take 7-30 days to convert. A 1-day window misses most conversions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No spend tracking&lt;/strong&gt; — Without cost data per channel, you can't calculate ROI.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;key-metrics-to-track&quot;&gt;Key Metrics to Track&lt;/h3&gt;
&lt;p&gt;Once attribution is set up, focus on these five metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wallet conversion rate&lt;/strong&gt; — % of link clicks that result in wallet connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On-chain conversion rate&lt;/strong&gt; — % of connected wallets that perform a target action (swap, mint, transfer)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost per converting wallet&lt;/strong&gt; — Marketing spend divided by wallets that actually converted on-chain&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wallet quality score&lt;/strong&gt; — Based on wallet age, balance, transaction history, DeFi activity (filters bots)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channel ROAS&lt;/strong&gt; — On-chain value generated per dollar spent, per channel&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;frequently-asked-questions-1&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;What is web3 attribution?&lt;/strong&gt;&lt;br&gt;
Web3 attribution connects off-chain marketing actions (campaigns, links, referral codes) to on-chain outcomes (swaps, mints, transfers). It answers: &quot;which marketing channel drove this wallet to convert?&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I need an SDK?&lt;/strong&gt;&lt;br&gt;
Not necessarily. Web3 Trackers uses a single tracking pixel (one script tag) that auto-detects wallet connections. Formo and Cookie3 use SDKs. Dune requires SQL. Choose based on your team's technical resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which tool is cheapest?&lt;/strong&gt;&lt;br&gt;
Web3 Trackers starts at $99/month with a free tier. Formo starts at $159/month (billed yearly). Dune has a free tier. Spindl and Addressable are sales-led with no public pricing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which chains are supported?&lt;/strong&gt;&lt;br&gt;
Varies by tool. Dune leads with 50+ chains. Web3 Trackers uniquely supports TON alongside Ethereum, Base, and Solana. Nansen covers 30+ chains. Most others focus on EVM chains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I track KOL campaign ROI?&lt;/strong&gt;&lt;br&gt;
Yes — create unique tracking links per KOL, attach the spend amount, and track which wallets convert on-chain. You'll see cost-per-wallet and ROI per influencer. See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/research/kol-performance-analysis&quot;&gt;KOL Performance Analysis&lt;/a&gt; for benchmark data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How long does setup take?&lt;/strong&gt;&lt;br&gt;
Web3 Trackers: ~5 minutes (one script tag). Formo: hours (SDK). Dune: hours-days (SQL). Spindl/Addressable: weeks (sales process).&lt;/p&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;The web3 attribution space has matured significantly in 2026. You no longer need to choose between &quot;enterprise-only&quot; and &quot;build it yourself.&quot; The spectrum now runs from free self-serve tools to full enterprise growth platforms.&lt;/p&gt;
&lt;p&gt;Start by defining what you actually need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you need &lt;strong&gt;ROI proof&lt;/strong&gt; → self-serve attribution (&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com&quot;&gt;Web3 Trackers&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;If you need &lt;strong&gt;product analytics&lt;/strong&gt; → SDK-based platform (Formo)&lt;/li&gt;
&lt;li&gt;If you need &lt;strong&gt;audience intelligence&lt;/strong&gt; → identity graph (Spindl, Addressable)&lt;/li&gt;
&lt;li&gt;If you need &lt;strong&gt;custom analysis&lt;/strong&gt; → SQL workbench (Dune)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is to start now. Every campaign without attribution is money you can't account for.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com&quot;&gt;Web3 Trackers&lt;/a&gt; — Self-serve attribution, free tier&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/web3-attribution-tools-comparison&quot;&gt;Full Tool Comparison&lt;/a&gt; — Side-by-side comparison of all tools&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/research/web3-attribution-feature-matrix&quot;&gt;Feature Parity Matrix&lt;/a&gt; — Which tools have what features&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/research/web3-attribution-pricing-report-2026&quot;&gt;Pricing Report&lt;/a&gt; — Complete pricing breakdown&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/roi-calculator&quot;&gt;Free ROI Calculator&lt;/a&gt; — Model your marketing ROI by channel&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/research/defi-marketing-benchmarks-2026&quot;&gt;DeFi Marketing Benchmarks&lt;/a&gt; — CPW, conversion rates, ROAS by chain&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.web3trackers.com/research/kol-performance-analysis&quot;&gt;KOL Performance Analysis&lt;/a&gt; — Influencer ROI by category&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14156/web3-attribution-tools-compared-how-to-track-crypto-marketing-roi-in-2026-updated</guid>
<pubDate>Tue, 07 Apr 2026 13:20:31 +0000</pubDate>
</item>
<item>
<title>Vector Similarity, Zero Client JS: Decoupled Analytics on a Side Project Budget</title>
<link>https://coderlegion.com/13615/vector-similarity-zero-client-js-decoupled-analytics-on-a-side-project-budget</link>
<description>&lt;p&gt;A leaderboard for &lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai/?utm_source=legion&quot;&gt;DumbQuestion.ai&lt;/a&gt; sounds simple. Track the most asked questions, display them. Done. Except people never ask the same question the same way twice.&lt;/p&gt;
&lt;p&gt;I was curious about how creative users of DumbQuestion.ai got with their questions, and I thought others might be as well. So I built a leaderboard of the most frequently asked dumb questions.&lt;/p&gt;
&lt;p&gt;The Overqualified persona calls it &lt;strong&gt;THE ARCHIVE OF INCOMPETENCE.&lt;/strong&gt;&lt;br&gt;
The Weary persona calls it &lt;strong&gt;THE WALL OF REGRET.&lt;/strong&gt;&lt;br&gt;
[REDACTED] calls it &lt;strong&gt;THE WATCHLIST.&lt;/strong&gt;&lt;br&gt;
The Compliant calls it &lt;strong&gt;THE WALL OF EXCELLENCE&lt;/strong&gt; (bless its reprogrammed heart).&lt;/p&gt;
&lt;p&gt;Building it turned out more interesting than it sounds.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-product-challenge&quot;&gt;The Product Challenge&lt;/h3&gt;
&lt;p&gt;People ask the same dumb question in a hundred different ways. &quot;What is 2+2?&quot; and &quot;can you add two plus two for me?&quot; are functionally identical. A simple string counter would give you noise, not signal. I needed semantic matching, not string matching.&lt;/p&gt;
&lt;p&gt;This is a solved problem in the ML world, but the typical solutions come with tradeoffs: heavyweight models, expensive APIs, or significant latency added to the critical path. None of those fit a &quot;brutally efficient&quot; side project.&lt;/p&gt;
&lt;h3 id=&quot;the-solution-vector-similarity-on-a-budget&quot;&gt;The Solution: Vector Similarity on a Budget&lt;/h3&gt;
&lt;p&gt;Each question gets run through an embedding model and compared against a &lt;a rel=&quot;nofollow&quot; href=&quot;https://qdrant.tech/&quot;&gt;Qdrant&lt;/a&gt; vector database. Qdrant's &lt;a rel=&quot;nofollow&quot; href=&quot;https://qdrant.tech/pricing/&quot;&gt;free tier&lt;/a&gt; is remarkably generous for a side project workload, but self-hosting is trivially easy if you need it.&lt;/p&gt;
&lt;p&gt;The matching logic is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate an embedding for the incoming question&lt;/li&gt;
&lt;li&gt;Compare against existing embeddings using cosine similarity&lt;/li&gt;
&lt;li&gt;If similarity exceeds a threshold, increment that question's counter&lt;/li&gt;
&lt;li&gt;If it's new, add it to the database&lt;/li&gt;
&lt;li&gt;The first instance of a question becomes the official display version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The embedding call costs fractions of a cent. The similarity comparison is fast. The result is a leaderboard that actually understands context rather than just matching strings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key architectural decision:&lt;/strong&gt; None of this runs in the main app.&lt;/p&gt;
&lt;p&gt;Adding vector similarity matching to every request would add latency, bloat the container, and burn more compute. Anti-pattern to the &quot;brutally efficient&quot; principle I've been following throughout. Instead, every question flows through the console output, gets picked up by a &lt;a rel=&quot;nofollow&quot; href=&quot;https://vector.dev/&quot;&gt;Vector&lt;/a&gt; sidecar container, routed through GCP Pub/Sub, and processed asynchronously on my Mac Mini home server (more later).&lt;/p&gt;
&lt;p&gt;The Mac Mini handles the Qdrant comparisons and updates a JSON file in Cloudflare R2 storage. When a user hits the leaderboard page it loads directly from R2. No live database queries. No per-request costs. Essentially free page loads at any scale.&lt;/p&gt;
&lt;h3 id=&quot;what-ended-up-on-the-leaderboard-&quot;&gt;What Ended Up on the Leaderboard?&lt;/h3&gt;
&lt;p&gt;As early users started using the app, the leaderboard filled up with exactly what you'd expect: actual dumb questions, a handful of self-awareness probes, and more than a few prompt injection attempts.&lt;/p&gt;
&lt;p&gt;Apparently people &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jagostoni/dumbquestionai-self-awareness-prompt-injection-search-intent-and-darkness-3pd&quot;&gt;read this series&lt;/a&gt; and went straight for the easter eggs. &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The leaderboard was just one piece of a larger analytics picture. Building it taught me something useful: the most interesting features don't always belong in your main app. That same principle shaped the entire analytics stack.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-observability-problem&quot;&gt;The Observability Problem&lt;/h3&gt;
&lt;p&gt;Running a side project means making real product decisions with limited data. Are people actually asking questions or just bouncing off the homepage? Which sites are driving traffic? Are ads being seen, clicked, ignored?&lt;/p&gt;
&lt;p&gt;Two constraints shaped the solution: no client-side JavaScript (page bloat is the enemy of brutal efficiency) and no SaaS analytics bill that spikes with usage.&lt;/p&gt;
&lt;p&gt;So I built (assembled, really) my own stack from open source tools. On a Mac Mini sitting at home.&lt;/p&gt;
&lt;h3 id=&quot;the-full-pipeline&quot;&gt;The Full Pipeline&lt;/h3&gt;
&lt;p&gt;Every event in &lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai/?utm_source=legion&quot;&gt;DumbQuestion.ai&lt;/a&gt; emits structured telemetry to standard console output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP requests (method, path, status, duration)&lt;/li&gt;
&lt;li&gt;Questions asked (anonymized)&lt;/li&gt;
&lt;li&gt;Searches performed&lt;/li&gt;
&lt;li&gt;LLM operations (model, token counts, duration, cost)&lt;/li&gt;
&lt;li&gt;Prompt injection attempts&lt;/li&gt;
&lt;li&gt;Custom product events (Question Asked, Shared, Ad Shown, Ad Clicked)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://gin-gonic.com/&quot;&gt;Go/GIN&lt;/a&gt; framework handles much of the HTTP telemetry automatically. The rest is custom instrumentation added deliberately at key points in the application.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Vector sidecar container&lt;/strong&gt; picks up the console output and routes it to &lt;strong&gt;GCP Pub/Sub&lt;/strong&gt;. This is the critical architectural decision: Pub/Sub acts as a resilient buffer between the main app and everything downstream. The Mac Mini can go down, lose power, or restart. Once it comes back up, the stack picks up exactly where it left off. No data loss, no backfill scripts, no drama.&lt;/p&gt;
&lt;p&gt;From Pub/Sub, a second Vector instance on the Mac Mini routes to two primary targets:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/plausible/analytics&quot;&gt;Plausible&lt;/a&gt;&lt;/strong&gt; handles user behavior and product analytics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Page views and session depth&lt;/li&gt;
&lt;li&gt;UTM tag tracking (know exactly which article drove which visit)&lt;/li&gt;
&lt;li&gt;User journey depth (did they just hit the root page or actually ask a question?)&lt;/li&gt;
&lt;li&gt;Browser, device type, country of origin&lt;/li&gt;
&lt;li&gt;Custom events: Question Asked, Shared, Ad Shown, Ad Clicked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this without a single line of client-side JavaScript. No tracking scripts, no page weight, no GDPR cookie banners for analytics. Pure server-side telemetry piped through the same pipeline as everything else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/parseablehq&quot;&gt;Parseable&lt;/a&gt;&lt;/strong&gt; handles the operational side:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLM performance metrics and cost tracking by day&lt;/li&gt;
&lt;li&gt;Ad CTR dashboards&lt;/li&gt;
&lt;li&gt;Log aggregation for debugging and incident investigation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think of it as Plausible for the product lens, Parseable for the business and ops lens.&lt;/p&gt;
&lt;h3 id=&quot;the-resilience-payoff&quot;&gt;The Resilience Payoff&lt;/h3&gt;
&lt;p&gt;I've had power outages. Slowdowns. The occasional restart. Every time, the stack catches up from where Pub/Sub left off without any manual intervention.&lt;/p&gt;
&lt;p&gt;This isn't accidental. Designing around failure rather than pretending it won't happen is the difference between a toy and a production system. The GCP Pub/Sub buffer was a deliberate choice specifically because I knew the downstream consumers (Mac Mini, Qdrant, Plausible, Parseable) were running on non-guaranteed infrastructure.&lt;/p&gt;
&lt;p&gt;Even on a Mac Mini, you can build something production-grade. You just have to design for it.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;Two things surprised me building this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First:&lt;/strong&gt; How much you can accomplish by treating console output as a first-class telemetry stream. No SDKs, no agents baked into the app, no client-side scripts. Just structured logging and a pipeline that knows what to do with it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second:&lt;/strong&gt; How much the &quot;keep it off the critical path&quot; principle scales. It started as a constraint (keep the main container lean) and became a design philosophy. The leaderboard, the analytics - none of it runs in the main app. All of it works reliably because the main app doesn't have to care about it.&lt;/p&gt;
&lt;p&gt;AI helped build all of it. But knowing what to measure, where to put the seams, and how to design for failure? Still the interesting (and super fun) part.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai/?utm_source=legion&quot;&gt;dumbquestion.ai&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13615/vector-similarity-zero-client-js-decoupled-analytics-on-a-side-project-budget</guid>
<pubDate>Sat, 28 Mar 2026 19:53:21 +0000</pubDate>
</item>
<item>
<title>Cleaning, Calculating, and Communicating: The Analyst’s Power BI Workflow</title>
<link>https://coderlegion.com/11577/cleaning-calculating-and-communicating-the-analysts-power-bi-workflow</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shhel9qqahz56ga86h8d.png&quot; alt=&quot;Intro&quot;&gt;&lt;/p&gt;
&lt;p&gt;Have you ever opened a raw dataset and immediately felt overwhelmed? How easy was it for you to understand the data? My guess is you often found it messy and probably wondered to yourself, “How is this kind of data supposed to relay insights and help in business decision-making?”&lt;/p&gt;
&lt;p&gt;Other than &lt;strong&gt;Excel&lt;/strong&gt;, there is another tool called &lt;strong&gt;Power BI&lt;/strong&gt; that allows you to &lt;strong&gt;load&lt;/strong&gt; messy data, &lt;strong&gt;clean&lt;/strong&gt; it, &lt;strong&gt;model&lt;/strong&gt; it, &lt;strong&gt;visualize&lt;/strong&gt; it, and transform it into business insights that decision-makers can understand and act on.&lt;/p&gt;
&lt;p&gt;This article will show you what the analysis workflow in Power BI looks like as we unlock our analyst minds and showcase our skills of not only coming up with visually engaging yet insight-driven dashboards, but also being able to turn chaos into clarity.&lt;br&gt;
Let’s begin!&lt;/p&gt;
&lt;h3 id=&quot;terms-you-will-find-in-this-article-&quot;&gt;Terms you will find in this article.&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Power Query -&lt;/strong&gt; A data transformation and preparation engine from &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Microsoft&quot;&gt;Microsoft&lt;/a&gt;&lt;/strong&gt; that enables users to import, clean, reshape, and combine data from various sources without needing advanced coding skills.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Analysis Expressions (DAX) -&lt;/strong&gt; A formula expression language used in Microsoft Power BI, Power Pivot in Excel, and Analysis Services to create custom calculations, measures, and calculated columns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ETL (Extract, Transform, Load) process - &lt;/strong&gt; A data integration methodology that collects raw data from multiple sources, cleans and reshapes it, and loads it into the Power BI engine for analysis.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-is-power-bi-&quot;&gt;What is Power BI?&lt;/h3&gt;
&lt;p&gt;Power BI is a business intelligence tool developed by Microsoft that enables analysts to transform raw data into interactive, decision-ready dashboards. It allows analysts to &lt;strong&gt;clean&lt;/strong&gt; raw data, &lt;strong&gt;calculate&lt;/strong&gt; meaningful metrics, and &lt;strong&gt;communicate&lt;/strong&gt; insights in a way decision-makers can actually use. &lt;/p&gt;
&lt;p&gt;Before opening Power BI, great analysts ask one critical question &lt;code&gt;What decision is this analysis meant to support?&lt;/code&gt; Many dashboards tend to fail because they start with visuals instead of purpose. A good analysis begins with clarity (What do we really expect at the end of this analysis?):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are we trying to increase revenue?&lt;/li&gt;
&lt;li&gt;Reduce costs?&lt;/li&gt;
&lt;li&gt;Improve customer retention?&lt;/li&gt;
&lt;li&gt;Identify underperforming products?&lt;/li&gt;
&lt;li&gt;Understand customer engagement?&lt;/li&gt;
&lt;li&gt;Increase safety in air travel?&lt;/li&gt;
&lt;li&gt;Plan for reasonable maintenance schedules?&lt;/li&gt;
&lt;li&gt;Decrease flight departure and arrival delays?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the goal is clear, everything else becomes easier. The data you work on, the metrics you calculate, and the visuals you design should all serve that one objective. Power BI is just the vessel. The destination is always an impactful business decision.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTE -&lt;/strong&gt; The general workflow in Power BI follows a logical order, often summarized by the ETL process (Extract, Transform, Load).&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;loading-data-in-power-bi&quot;&gt;Loading data in Power BI&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i30jwjrwntq8a50vgq99.png&quot; alt=&quot;Loading data&quot;&gt;&lt;/p&gt;
&lt;p&gt;Loading data is the first step in analyzing data in Power BI. When you open Power BI, this is the first thing you will see. To load data, click on &lt;code&gt;get data&lt;/code&gt;, then depending on your file type, choose the appropriate file to load. It may take some time depending on your data size.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkvw3xbt80778x37j6ef.png&quot; alt=&quot;Transform data&quot;&gt;&lt;/p&gt;
&lt;p&gt;A pop-up will then appear where you can &lt;code&gt;Load&lt;/code&gt; data, &lt;code&gt;Transform Data&lt;/code&gt;, or even &lt;code&gt;Cancel&lt;/code&gt; if you maybe uploaded the wrong data set. Now, since we have to now clean the data, our next button to click is &lt;code&gt;Transform Data&lt;/code&gt;(black button). This will now take us to our next step, &lt;code&gt;Power Query&lt;/code&gt;, where we will prepare our data for analysis.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTE -&lt;/strong&gt; In an instance where you have already cleaned your data using Excel and now want to visualize your data using Power BI, you will click the &lt;code&gt;Load&lt;/code&gt; (Red) button. This will take you directly to your worksheet area to start your calculations and visualizations.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;preparing-data-with-power-query&quot;&gt;Preparing Data with Power Query&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e687z4t1broaxnrnlg8p.png&quot; alt=&quot;Power Query&quot;&gt;&lt;/p&gt;
&lt;p&gt;Real-world data is rarely analysis-ready. That’s why the first technical step in Power BI is almost always &lt;strong&gt;Power Query&lt;/strong&gt;. This is what analysts use Power Query for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data connection&lt;/li&gt;
&lt;li&gt;Transforming and shaping data (e.g., fixing incorrect data types such as text vs. numbers vs. dates)&lt;/li&gt;
&lt;li&gt;Combining Data (e.g., merging related tables)&lt;/li&gt;
&lt;li&gt;Automating processes&lt;/li&gt;
&lt;li&gt;Loading data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, an airline safety and management dataset might have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flight dates stored as text&lt;/li&gt;
&lt;li&gt;Revenue with currency symbols&lt;/li&gt;
&lt;li&gt;Flight crew names are spelled differently across files&lt;/li&gt;
&lt;li&gt;Duplicate Flight IDs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don’t fix these issues first, your analysis will be wrong, no matter how good your DAX or visuals are. Think of Power Query as the foundation of a house. If the foundation is weak, everything built on top will crack and eventually crumble.&lt;/p&gt;
&lt;h3 id=&quot;data-modelling-in-power-bi&quot;&gt;Data Modelling in Power BI&lt;/h3&gt;
&lt;p&gt;Power BI will almost always automatically create relationships between the tables in your dataset, based on common fields across tables. Now, this brings us to our next step in Power BI workflow analysis, &lt;code&gt;Data Modelling&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is good practice not to fully trust Power BI’s automatic modeling, as it can sometimes create incorrect relationships. Before working on your calculations, ensure that your modeling is correct, with your relationships well identified. To understand the schemas and modeling in Power BI, check out this article that will help you understand it in detail &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/wk-warui/schemas-and-data-modelling-in-power-bi-16f1&quot;&gt;Schemas and Data Modelling in Power BI&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;turning-data-into-metrics-with-dax&quot;&gt;Turning Data into Metrics with DAX&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h5cd6xn5owxd7m6u3rn9.png&quot; alt=&quot;Dax&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once the data is clean and the models are correct, analysts move to the next phase, which is &lt;strong&gt;calculating insights using DAX (Data Analysis Expressions)&lt;/strong&gt;. DAX is the language Power BI uses to create:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Measures (dynamic calculations like totals, averages, ratios)&lt;/li&gt;
&lt;li&gt;Time intelligence (month-to-date, year-to-date, growth %)&lt;/li&gt;
&lt;li&gt;Rankings and comparisons&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, instead of just showing Total Flight Trips, an analyst might calculate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average Travel Delays&lt;/li&gt;
&lt;li&gt;Profit Margin&lt;/li&gt;
&lt;li&gt;Month-over-Month Growth&lt;/li&gt;
&lt;li&gt;Top 10 flight accidents by cause&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where raw data becomes &lt;strong&gt;business intelligence&lt;/strong&gt;. A table of numbers tells you &lt;code&gt;what happened&lt;/code&gt;. DAX measures help explain &lt;code&gt;how well it happened and why it matters.&lt;/code&gt; Good analysts also keep DAX readable and logical. Complex formulas are powerful, but only if they can be understood and maintained.&lt;/p&gt;
&lt;h3 id=&quot;designing-dashboards-that-drive-decisions&quot;&gt;Designing Dashboards That Drive Decisions&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ltr5l4v1034pnuzllte0.png&quot; alt=&quot;Dashboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;At this stage, we have successfully loaded our messy data into Power BI, transformed our data in Power Query, modelled our data, calculated metrics to get a deeper understanding of our data, and finally, we are at visualizing our data. The workflow becomes structured and logical.&lt;br&gt;
This step, &lt;strong&gt;communication&lt;/strong&gt;, is now a step closer to finalizing our analysis in Power BI, turning calculations into dashboards that tell a story. A strong Power BI dashboard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Answers specific questions&lt;/li&gt;
&lt;li&gt;Highlights what matters most&lt;/li&gt;
&lt;li&gt;Makes insights obvious at a glance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Choosing the right visuals (not just the fancy ones)&lt;/li&gt;
&lt;li&gt;Using layout and hierarchy to guide the viewer’s eye&lt;/li&gt;
&lt;li&gt;Limiting clutter&lt;/li&gt;
&lt;li&gt;Using consistent formatting and colors (Best advisable to use colors relating to the type of dataset you have, e.g. In an agricultural dataset, you can use green to represent nature and maybe a touch of brown)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of asking users to &lt;code&gt;search&lt;/code&gt; for insights, great dashboards &lt;code&gt;present&lt;/code&gt; them. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A KPI card for Total Flight Trips&lt;/li&gt;
&lt;li&gt;A trend line showing growth in flight safety over time&lt;/li&gt;
&lt;li&gt;A bar chart ranking the top causes of flight accidents&lt;/li&gt;
&lt;li&gt;A slicer to explore by region or cause&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn’t to impress, but to inform and facilitate great business decision-making.&lt;/p&gt;
&lt;h3 id=&quot;what-great-analysts-do-differently&quot;&gt;What Great Analysts Do Differently&lt;/h3&gt;
&lt;p&gt;The best analysts don’t stop at insights. They go one step further and translate them into &lt;strong&gt;recommendations&lt;/strong&gt;, not just &lt;code&gt;Flight safety was increased in Region B&lt;/code&gt;, but &lt;code&gt; Flight safety improved in Region B, with major accidents decreasing by 78% after targeted safety measures were implemented based on identified root causes. This suggests the measures were effective. We should review how they were implemented and consider replicating the approach in other regions.&lt;/code&gt; This is where Power BI becomes a &lt;strong&gt;decision engine&lt;/strong&gt;, not just a reporting tool. Here’s an example of action-oriented analysis:&lt;/p&gt;
&lt;p&gt;Suppose an airline recorded 12,000 flights last year. After cleaning and modeling the data:&lt;/p&gt;
&lt;p&gt;•You discover that 62% of delays happen during peak weather months.&lt;br&gt;
•45% of major incidents are linked to maintenance scheduling gaps.&lt;/p&gt;
&lt;p&gt;Instead of simply reporting these numbers, the recommendation becomes:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;“Reallocate maintenance checks before peak weather months and reinforce crew training during high-risk periods.”&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTE -&lt;/strong&gt; Insight without action is just information. Action is what creates value.&lt;/em&gt;&lt;br&gt;
 &lt;/p&gt;
&lt;h3 id=&quot;common-power-bi-mistakes-and-how-to-avoid-them-&quot;&gt;Common Power BI Mistakes (and How to Avoid Them)&lt;/h3&gt;
&lt;p&gt;Making mistakes while working with Power BI — or any tool — is part of the learning process. Even experienced analysts make mistakes. Mistakes are part of the learning process. What matters is identifying them early and improving from them. Some of the most common include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Building dashboards before understanding the business problem&lt;ul&gt;
&lt;li&gt;Always start with the question, not the visuals.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Overloading dashboards with too many charts&lt;ul&gt;
&lt;li&gt;Fewer visuals = clearer message.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Writing complex DAX that no one understands&lt;ul&gt;
&lt;li&gt;Keep formulas readable and documented.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ignoring performance and refresh issues&lt;ul&gt;
&lt;li&gt;Optimize models and ensure reliable data updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Designing for yourself instead of the user&lt;ul&gt;
&lt;li&gt;Always think about who will use the dashboard and how, assuming they are not tech-savvy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Not using a star schema.&lt;ul&gt;
&lt;li&gt;Poor model design can significantly reduce performance. Always aim for a clean fact table connected to dimension tables.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-skills-that-separate-good-analysts-from-great-ones&quot;&gt;The Skills That Separate Good Analysts from Great Ones&lt;/h3&gt;
&lt;p&gt;Power BI is a technical tool, but great analysis is a blend of &lt;strong&gt;technical and human skills&lt;/strong&gt;. Great analysts combine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Power Query + DAX + Data Modeling&lt;/li&gt;
&lt;li&gt;Business thinking&lt;/li&gt;
&lt;li&gt;Communication and storytelling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They understand that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data is about people&lt;/li&gt;
&lt;li&gt;Dashboards are about decisions&lt;/li&gt;
&lt;li&gt;And analytics is about impact&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pros-and-cons-of-power-bi&quot;&gt;Pros and Cons of Power BI&lt;/h3&gt;
&lt;p&gt;Just like every other software you can use out there, Power BI has its own pros and cons.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      1. Pros
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost-Effective -&lt;/strong&gt; Offers a free, functional desktop version, with affordable Pro licensing compared to competitors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Microsoft Ecosystem Integration -&lt;/strong&gt; Seamlessly connects with Excel, Azure, SQL Server, and Office 365.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Powerful Visualization &amp;amp; Data Handling -&lt;/strong&gt; Features a wide range of interactive, custom visual tools and strong data modeling capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constant Updates -&lt;/strong&gt; Microsoft frequently updates the platform with new, AI-driven features.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large Community Support -&lt;/strong&gt; Extensive online resources, forums, and documentation are available. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   2. Cons
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Steep Learning Curve -&lt;/strong&gt; While easy for basic tasks, mastering DAX (Data Analysis Expressions) and complex data modeling requires significant technical expertise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance Issues -&lt;/strong&gt; Can become slow when processing, loading, or refreshing large or complex datasets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited Customization -&lt;/strong&gt; Flexibility for customizing visuals can be restrictive compared to tools like Tableau.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependency on Windows/Cloud -&lt;/strong&gt; Primarily designed for Windows, with limited or no native, full-featured editing capabilities on other operating systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Licensing Complexity -&lt;/strong&gt; The licensing structure can be confusing, and, for some, the cost of scaling (Premium) is high.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Finally! We now have a clear understanding of the Power BI analysis workflow. The workflow is structured and logical — and with consistent practice, it becomes second nature. Platforms like &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kaggle.com/&quot;&gt;Kaggle&lt;/a&gt; provide high-quality practice datasets to sharpen your skills.&lt;/p&gt;
&lt;p&gt;The analyst’s workflow can be summarized in three powerful steps:&lt;br&gt;
Load -&amp;gt; Clean -&amp;gt; Model -&amp;gt; Calculate -&amp;gt; Communicate&lt;/p&gt;
&lt;p&gt;Power BI supports every stage of the modern analytical workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Power Query cleans the chaos&lt;/li&gt;
&lt;li&gt;Data modeling creates structure&lt;/li&gt;
&lt;li&gt;DAX builds intelligence&lt;/li&gt;
&lt;li&gt;Dashboards communicate insight&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tools alone don’t create impact — analysts do.&lt;br&gt;
The real value lies not in reporting what happened, but in guiding what should happen next.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ALL THE BEST IN YOUR POWER BI EXPLORATION&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11577/cleaning-calculating-and-communicating-the-analysts-power-bi-workflow</guid>
<pubDate>Fri, 13 Feb 2026 10:04:17 +0000</pubDate>
</item>
<item>
<title>Getting Started with NotebookLM: Your Personalized AI Research Assistant (2025 Edition)</title>
<link>https://coderlegion.com/8868/getting-started-with-notebooklm-your-personalized-ai-research-assistant-2025-edition</link>
<description>&lt;p&gt;In a world drowning in information, the challenge isn't finding data—it’s synthesizing it. Enter &lt;strong&gt;Google NotebookLM&lt;/strong&gt;. Unlike standard AI chatbots that pull from the vast (and sometimes unreliable) internet, NotebookLM is &quot;grounded&quot; in the sources &lt;strong&gt;you&lt;/strong&gt; provide.&lt;/p&gt;
&lt;p&gt;Whether you’re a student tackling a thesis, a professional analyzing market reports, or a creator organizing a podcast, here is how to master NotebookLM in 2025.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-is-notebooklm-&quot;&gt;&lt;strong&gt;What is NotebookLM?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;NotebookLM is an AI-powered research and note-taking tool that uses &lt;strong&gt;Gemini 2.5 Pro&lt;/strong&gt; to understand your specific documents. Its magic lies in its &quot;Source-Grounded&quot; nature: it only knows what you tell it, drastically reducing &quot;hallucinations&quot; and providing clickable citations for every claim it makes.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-1-create-your-first-notebook&quot;&gt;&lt;strong&gt;Step 1: Create Your First Notebook&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Go to the Site:&lt;/strong&gt; Visit &lt;a rel=&quot;nofollow&quot; href=&quot;https://notebooklm.google&quot;&gt;notebooklm.google&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start New:&lt;/strong&gt; Click the &lt;strong&gt;&quot;New Notebook&quot;&lt;/strong&gt; button. Think of a Notebook as a dedicated project folder (e.g., &quot;History 101&quot; or &quot;Q4 Marketing Strategy&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add Your Sources:&lt;/strong&gt; You can upload a wide variety of formats:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Documents:&lt;/strong&gt; PDFs, Google Docs, Slides, and Text files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web:&lt;/strong&gt; Paste URLs or link to YouTube videos (it will analyze the transcript!).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio:&lt;/strong&gt; Upload MP3s or lecture recordings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-meet-your-studio-the-power-tools-&quot;&gt;&lt;strong&gt;Step 2: Meet Your &quot;Studio&quot; (The Power Tools)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Once your sources are uploaded, the &lt;strong&gt;Studio panel&lt;/strong&gt; on the right is where the transformation happens. In 2025, NotebookLM added several heavy-hitting features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audio Overviews (The &quot;Podcast&quot; Mode):&lt;/strong&gt; With one click, two AI hosts will have a deep-dive conversation about your sources. In the latest update, you can even click &lt;strong&gt;&quot;Join the Conversation&quot;&lt;/strong&gt; to ask the hosts questions in real-time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video Overviews:&lt;/strong&gt; Using the &lt;strong&gt;Nano Banana&lt;/strong&gt; visual model, NotebookLM can now turn your text sources into narrated slideshow videos—perfect for quick briefings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mind Maps &amp;amp; Timelines:&lt;/strong&gt; Automatically visualize how different concepts in your documents connect or see a chronological layout of events across multiple sources.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-interacting-with-your-data&quot;&gt;&lt;strong&gt;Step 3: Interacting with Your Data&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The center of the screen is your &lt;strong&gt;Chat Window&lt;/strong&gt;. This isn't just for summaries; it’s for deep interrogation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ask Specific Questions:&lt;/strong&gt; &lt;em&gt;&quot;Based on these 5 PDFs, what are the three main risks of this project?&quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check Citations:&lt;/strong&gt; Every answer will have small numbered badges. Click them to see exactly which page and paragraph the AI used to find that fact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save Notes:&lt;/strong&gt; If the AI gives a great answer, click &lt;strong&gt;&quot;Save to Note.&quot;&lt;/strong&gt; Your notes live in the notebook alongside your sources, acting as a &quot;living&quot; draft of your final project.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-2025-is-the-year-of-notebooklm&quot;&gt;&lt;strong&gt;Why 2025 is the Year of NotebookLM&lt;/strong&gt;&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Why It Matters&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Deep Research Mode&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;It can now scan the web for &lt;em&gt;verified&lt;/em&gt; sources to supplement your own files.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Increased Limits&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Free users can now manage 50 sources per notebook; Pro users get up to 300.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Learning Guides&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;It can generate quizzes and flashcards automatically to help you study.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Privacy First&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Google does not use your notebook data to train its global AI models.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4 id=&quot;pro-tips-for-success&quot;&gt;&lt;strong&gt;Pro Tips for Success&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Source Management:&lt;/strong&gt; If you have too much data, use the checkboxes to &quot;mute&quot; specific sources. This forces the AI to focus only on the files you want for a specific question.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Gap Hunter&quot; Prompt:&lt;/strong&gt; Ask the AI, &lt;em&gt;&quot;What is missing from these sources that I should look for elsewhere?&quot;&lt;/em&gt; It’s a brilliant way to find weaknesses in your research.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio Customization:&lt;/strong&gt; You can now prompt the Audio Overview hosts to focus on a specific tone (e.g., &lt;em&gt;&quot;Make this sound like a technical debate&quot;&lt;/em&gt;).&lt;br&gt;
*&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;NotebookLM is a new AI tool focused on source-grounded cognition, meaning it reasons only within the uploaded sources and doesn't improvise. This makes it auditable, private, and reliable for high-stakes work.&lt;/li&gt;
&lt;li&gt;Using NotebookLM effectively requires careful &quot;corpus architecture,&quot; focusing on curating and organizing the information uploaded, rather than just prompting. This involves designing the model's world through thoughtful source selection.&lt;/li&gt;
&lt;li&gt;NotebookLM enables advanced workflows like legal research, creative continuity, business intelligence, and UX research. It represents a shift in AI literacy towards understanding corpus design and epistemic reasoning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;notebookLM should not be looked at as a typical LLM chatbot. Since we curate the dataset, at max, the hallucinations will be in the dataset. That’s a much more preferred output than LLM chat bots which could hallucinate wildly.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@stunspot/everyone-is-wrong-about-notebooklm-802770aa12f7&quot;&gt;https://medium.com/@stunspot/everyone-is-wrong-about-notebooklm-802770aa12f7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/posts/theanishajain_how-to-get-a-job-thanks-to-ai-interview-activity-7409899270964965376-GcbI&quot;&gt;https://www.linkedin.com/posts/theanishajain_how-to-get-a-job-thanks-to-ai-interview-activity-7409899270964965376-GcbI&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.geeky-gadgets.com/google-notebooklm-update/&quot;&gt;https://www.geeky-gadgets.com/google-notebooklm-update/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/posts/axellemalek_news-notebooklm-now-creates-infographics-ugcPost-7414595876913987584-ZDNV?utm_medium=ios_app&amp;amp;rcm=ACoAABzB85UB8iZMlY6hoj9K3g7MfcRKsCZOvqw&amp;amp;utm_source=social_share_send&amp;amp;utm_campaign=copy_link&quot;&gt;https://www.linkedin.com/posts/axellemalek_news-notebooklm-now-creates-infographics-ugcPost-7414595876913987584-ZDNV?utm_medium=ios_app&amp;amp;rcm=ACoAABzB85UB8iZMlY6hoj9K3g7MfcRKsCZOvqw&amp;amp;utm_source=social_share_send&amp;amp;utm_campaign=copy_link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.anthropic.com/research/AI-assistance-coding-skills&quot;&gt;https://www.anthropic.com/research/AI-assistance-coding-skills&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;it isn’t a chatbot. It’s the first mainstream tool that rewires how humans interact with knowledge. in finding job/work it can also help check the main blog &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://notebooklm.google/&quot;&gt;https://notebooklm.google/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let’s stay connected:&lt;/p&gt;
&lt;p&gt;Instagram: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.instagram.com/angular_development/&quot;&gt;https://www.instagram.com/angular_development/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Facebook: &lt;a rel=&quot;nofollow&quot; href=&quot;https://m.facebook.com/learnangular2plus/&quot;&gt;https://m.facebook.com/learnangular2plus/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Threads: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.threads.net/@angular_development&quot;&gt;https://www.threads.net/@angular_development&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Medium: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@eraoftech&quot;&gt;https://medium.com/@eraoftech&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;coderlegion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/Sunny&quot;&gt;https://coderlegion.com/user/Sunny&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Quora: &lt;a rel=&quot;nofollow&quot; href=&quot;https://neweraofcoding.quora.com/&quot;&gt;https://neweraofcoding.quora.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;YouTube: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/@neweraofcoding&quot;&gt;https://www.youtube.com/@neweraofcoding&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;LinkedIn: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/company/infowebtech/&quot;&gt;https://www.linkedin.com/company/infowebtech/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hashnode: &lt;a rel=&quot;nofollow&quot; href=&quot;https://neweraofcoding.hashnode.dev/&quot;&gt;https://neweraofcoding.hashnode.dev/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/angulardevelopment/&quot;&gt;https://github.com/angulardevelopment/&lt;/a&gt; | &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sunny7899/&quot;&gt;sunny7899&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;BlueSky: &lt;a rel=&quot;nofollow&quot; href=&quot;https://bsky.app/profile/neweraofcoding.bsky.social&quot;&gt;https://bsky.app/profile/neweraofcoding.bsky.social&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Substack Newsletter: &lt;a rel=&quot;nofollow&quot; href=&quot;https://codeforweb.substack.com/&quot;&gt;https://codeforweb.substack.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pinterest: &lt;a rel=&quot;nofollow&quot; href=&quot;https://in.pinterest.com/tech_nerd_life/&quot;&gt;https://in.pinterest.com/tech_nerd_life/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://dev.to/&quot;&gt;dev.to&lt;/a&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/sunny7899&quot;&gt;https://dev.to/sunny7899&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Looking for web dev trainings: &lt;a rel=&quot;nofollow&quot; href=&quot;https://beginner-to-pro-training.vercel.app/&quot;&gt;https://beginner-to-pro-training.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Software development services: &lt;a rel=&quot;nofollow&quot; href=&quot;https://infowebtechnologies.vercel.app/&quot;&gt;https://infowebtechnologies.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Contribution to the web development community: &lt;a rel=&quot;nofollow&quot; href=&quot;https://code-for-next-generation.vercel.app/&quot;&gt;https://code-for-next-generation.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Book a session: &lt;a rel=&quot;nofollow&quot; href=&quot;https://topmate.io/softwaredev&quot;&gt;https://topmate.io/softwaredev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Telegram Channel: &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/neweraofcoding&quot;&gt;https://t.me/neweraofcoding&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thank you for being a part of the community. Happy coding!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8868/getting-started-with-notebooklm-your-personalized-ai-research-assistant-2025-edition</guid>
<pubDate>Wed, 24 Dec 2025 20:27:49 +0000</pubDate>
</item>
<item>
<title>Google Tag Manager GTM Setup Verification Guide</title>
<link>https://coderlegion.com/4560/google-tag-manager-gtm-setup-verification-guide</link>
<description>&lt;h2 id=&quot;gtm-setup-verification-guide&quot;&gt;GTM Setup Verification Guide&lt;/h2&gt;
&lt;h3 id=&quot;method-1-gtm-preview-mode-most-comprehensive-&quot;&gt;&lt;strong&gt;Method 1: GTM Preview Mode (Most Comprehensive)&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;step-by-step-instructions-&quot;&gt;Step-by-Step Instructions:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open GTM Console&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to &lt;a rel=&quot;nofollow&quot; href=&quot;https://tagmanager.google.com&quot;&gt;tagmanager.google.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select your container: &lt;code&gt;GTM-XXXXXXX&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enable Preview Mode&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click the &lt;strong&gt;&quot;Preview&quot;&lt;/strong&gt; button in the top-right corner&lt;/li&gt;
&lt;li&gt;GTM will generate a preview URL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test Your Website&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open the preview URL in a new tab&lt;/li&gt;
&lt;li&gt;Your website will load with GTM debug panel at the bottom&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verify Events&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The debug panel shows all events being fired&lt;/li&gt;
&lt;li&gt;You should see events like:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;page_view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form_field_focus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;button_click&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scroll_depth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;section_view&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test Interactions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fill out the form&lt;/li&gt;
&lt;li&gt;Click buttons&lt;/li&gt;
&lt;li&gt;Scroll through the page&lt;/li&gt;
&lt;li&gt;Navigate between sections&lt;/li&gt;
&lt;li&gt;Watch events appear in real-time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;method-2-browser-developer-tools&quot;&gt;&lt;strong&gt;Method 2: Browser Developer Tools&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;check-gtm-container-loading-&quot;&gt;Check GTM Container Loading:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open Developer Tools&lt;/strong&gt; (F12)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go to Network Tab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refresh the page&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Look for GTM requests:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;googletagmanager.com/gtm.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;googletagmanager.com/gtm.js?id=GTM-5XFLRN8P&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;check-datalayer-&quot;&gt;Check dataLayer:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open Console Tab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type:&lt;/strong&gt; &lt;code&gt;window.dataLayer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Press Enter&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You should see an array with events&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;test-event-tracking-&quot;&gt;Test Event Tracking:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// In browser console, test manually:
window.dataLayer.push({
    'event': 'test_event',
    'event_category': 'test',
    'event_action': 'verification'
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;method-3-gtm-container-verification&quot;&gt;&lt;strong&gt;Method 3: GTM Container Verification&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;check-container-code-&quot;&gt;Check Container Code:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verify Head Code&lt;/strong&gt; (in index.html):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Google Tag Manager --&amp;gt;
&amp;lt;script&amp;gt;(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&amp;amp;l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-5XFLRN8P');&amp;lt;/script&amp;gt;
&amp;lt;!-- End Google Tag Manager --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verify Body Code&lt;/strong&gt; (in index.html):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Google Tag Manager (noscript) --&amp;gt;
&amp;lt;noscript&amp;gt;&amp;lt;iframe src=&quot;https://www.googletagmanager.com/ns.html?id=GTM-5XFLRN8P&quot;
height=&quot;0&quot; width=&quot;0&quot; style=&quot;display:none;visibility:hidden&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;/noscript&amp;gt;
&amp;lt;!-- End Google Tag Manager (noscript) --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;method-4-event-specific-testing&quot;&gt;&lt;strong&gt;Method 4: Event-Specific Testing&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;test-each-event-type-&quot;&gt;Test Each Event Type:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page View Event:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open website&lt;/li&gt;
&lt;li&gt;Check console for: &lt;code&gt;GTM Event: page_view&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Form Events:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click on form fields&lt;/li&gt;
&lt;li&gt;Submit the form&lt;/li&gt;
&lt;li&gt;Check for: &lt;code&gt;form_field_focus&lt;/code&gt;, &lt;code&gt;form_submission&lt;/code&gt;, &lt;code&gt;form_success&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Button Events:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click various buttons&lt;/li&gt;
&lt;li&gt;Check for: &lt;code&gt;button_click&lt;/code&gt;, &lt;code&gt;cta_button_click&lt;/code&gt;, &lt;code&gt;demo_button_click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Navigation Events:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click navigation links&lt;/li&gt;
&lt;li&gt;Check for: &lt;code&gt;navigation_click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scroll Events:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scroll through the page&lt;/li&gt;
&lt;li&gt;Check for: &lt;code&gt;scroll_depth&lt;/code&gt;, &lt;code&gt;section_view&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;method-5-gtm-container-status-check&quot;&gt;&lt;strong&gt;Method 5: GTM Container Status Check&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;in-gtm-console-&quot;&gt;In GTM Console:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Container Status:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Should show &quot;Published&quot; status&lt;/li&gt;
&lt;li&gt;Check for any errors in container&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tags Configuration:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Verify tags are set up to trigger on your events&lt;/li&gt;
&lt;li&gt;Check tag firing rules&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Triggers:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure triggers are configured for your events&lt;/li&gt;
&lt;li&gt;Test trigger conditions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Variables:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Verify data layer variables are set up&lt;/li&gt;
&lt;li&gt;Check variable configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;method-6-real-time-testing&quot;&gt;&lt;strong&gt;Method 6: Real-Time Testing&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;using-google-analytics-if-connected-&quot;&gt;Using Google Analytics (if connected):&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open Google Analytics&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go to Real-Time &amp;gt; Events&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interact with your website&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watch events appear in real-time&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;using-gtm-real-time-&quot;&gt;Using GTM Real-Time:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;In GTM Console&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go to Preview Mode&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interact with website&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watch events in debug panel&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;common-issues-amp-solutions&quot;&gt;&lt;strong&gt;Common Issues &amp;amp; Solutions&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;issue-1-gtm-not-loading&quot;&gt;Issue 1: GTM Not Loading&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt; No GTM requests in Network tab&lt;br&gt;
&lt;strong&gt;Solutions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check container ID is correct&lt;/li&gt;
&lt;li&gt;Verify GTM code is in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section&lt;/li&gt;
&lt;li&gt;Check for JavaScript errors&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;issue-2-events-not-firing&quot;&gt;Issue 2: Events Not Firing&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt; No events in dataLayer&lt;br&gt;
&lt;strong&gt;Solutions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check JavaScript console for errors&lt;/li&gt;
&lt;li&gt;Verify &lt;code&gt;trackEvent&lt;/code&gt; function is working&lt;/li&gt;
&lt;li&gt;Test with manual dataLayer.push()&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;issue-3-events-firing-but-not-reaching-gtm&quot;&gt;Issue 3: Events Firing but Not Reaching GTM&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt; Events in dataLayer but not in GTM&lt;br&gt;
&lt;strong&gt;Solutions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check GTM container is published&lt;/li&gt;
&lt;li&gt;Verify trigger configurations&lt;/li&gt;
&lt;li&gt;Test in preview mode&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;issue-4-duplicate-events&quot;&gt;Issue 4: Duplicate Events&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt; Same event firing multiple times&lt;br&gt;
&lt;strong&gt;Solutions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check for duplicate event listeners&lt;/li&gt;
&lt;li&gt;Verify event tracking logic&lt;/li&gt;
&lt;li&gt;Use event deduplication in GTM&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-verification-checklist&quot;&gt;✅ &lt;strong&gt;Verification Checklist&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] GTM container loads without errors&lt;/li&gt;
&lt;li&gt;[ ] dataLayer is accessible in console&lt;/li&gt;
&lt;li&gt;[ ] Page view event fires on load&lt;/li&gt;
&lt;li&gt;[ ] Form events fire on interaction&lt;/li&gt;
&lt;li&gt;[ ] Button click events fire&lt;/li&gt;
&lt;li&gt;[ ] Scroll depth events fire&lt;/li&gt;
&lt;li&gt;[ ] Navigation events fire&lt;/li&gt;
&lt;li&gt;[ ] Events appear in GTM preview mode&lt;/li&gt;
&lt;li&gt;[ ] No JavaScript errors in console&lt;/li&gt;
&lt;li&gt;[ ] GTM container is published&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-debugging-commands&quot;&gt;️ &lt;strong&gt;Debugging Commands&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;console-commands-for-testing-&quot;&gt;Console Commands for Testing:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Check if GTM is loaded
console.log('GTM Loaded:', typeof window.dataLayer !== 'undefined');

// Check dataLayer contents
console.log('DataLayer:', window.dataLayer);

// Manually trigger an event
window.dataLayer.push({
    'event': 'manual_test',
    'event_category': 'debug',
    'event_action': 'console_test'
});

// Check GTM container ID
console.log('GTM ID:', 'GTM-5XFLRN8P');

// Test trackEvent function
if (typeof trackEvent === 'function') {
    trackEvent('test_event', {test: true});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;expected-events-in-gtm&quot;&gt;&lt;strong&gt;Expected Events in GTM&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;When testing, you should see these events:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Page Load:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;page_view&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Form Interactions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;form_field_focus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form_field_blur&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form_submission&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form_success&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Button Clicks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;button_click&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cta_button_click&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;demo_button_click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Navigation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;navigation_click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User Engagement:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scroll_depth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;section_view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user_engagement&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Carousel:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;carousel_navigation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;External Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;external_link_click&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;next-steps-after-verification&quot;&gt;&lt;strong&gt;Next Steps After Verification&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set up GTM Triggers&lt;/strong&gt; for each event&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure Tags&lt;/strong&gt; (Google Analytics, Facebook Pixel, etc.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test in Production&lt;/strong&gt; environment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Real-Time Data&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up Conversion Tracking&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure Enhanced Ecommerce&lt;/strong&gt; (if applicable) &lt;/li&gt;
&lt;/ol&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/4560/google-tag-manager-gtm-setup-verification-guide</guid>
<pubDate>Sun, 10 Aug 2025 19:32:11 +0000</pubDate>
</item>
<item>
<title>Full-Stack vs. Data Science: Which Career Path Scales Better in 2025</title>
<link>https://coderlegion.com/4507/full-stack-vs-data-science-which-career-path-scales-better-in-2025</link>
<description>&lt;p&gt;In 2025, the tech job market is shifting faster than ever. AI is rewriting job descriptions, remote work is the norm, and companies are hiring globally. If you’re at a career crossroads, two roles often come up: &lt;strong&gt;Full-Stack Developer&lt;/strong&gt; and &lt;strong&gt;Data Scientist&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Both are lucrative, in-demand, and future-facing — but which one scales better for the next 5–10 years? Let’s break it down.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-what-each-role-actually-does&quot;&gt;&lt;strong&gt;1. What Each Role Actually Does&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Full-Stack Developer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Builds complete web applications — frontend (UI/UX) + backend (databases, APIs)&lt;/li&gt;
&lt;li&gt;Works with frameworks like &lt;strong&gt;React, Angular, Node.js, Django&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Handles everything from concept to deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Data Scientist&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extracts insights from data to guide business decisions&lt;/li&gt;
&lt;li&gt;Works with tools like &lt;strong&gt;Python, R, SQL, TensorFlow&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Builds models for predictions, recommendations, and automation&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-salary-amp-growth-in-2025&quot;&gt;&lt;strong&gt;2. Salary &amp;amp; Growth in 2025&lt;/strong&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Role           &lt;/th&gt;&lt;th&gt;Avg. Salary (Global) &lt;/th&gt;&lt;th&gt;Growth Outlook&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Full-Stack Dev &lt;/td&gt;&lt;td&gt;\$75K – \$130K       &lt;/td&gt;&lt;td&gt;Steady, demand in SaaS, fintech, e-commerce&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Data Scientist &lt;/td&gt;&lt;td&gt;\$90K – \$150K       &lt;/td&gt;&lt;td&gt;Strong, especially in AI-driven industries&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Trend:&lt;/strong&gt; In 2025, AI automation is boosting demand for &lt;strong&gt;AI-savvy Full-Stack Developers&lt;/strong&gt; and &lt;strong&gt;business-focused Data Scientists&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-learning-curve-amp-entry-barrier&quot;&gt;&lt;strong&gt;3. Learning Curve &amp;amp; Entry Barrier&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full-Stack:&lt;/strong&gt; Easier to get started — plenty of free resources, bootcamps, and junior roles. You can ship projects fast and build a portfolio quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Science:&lt;/strong&gt; Higher entry barrier — requires strong math, statistics, and domain knowledge. Beginners can start small with analytics, but ML-heavy roles take time to break into.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-industry-demand-in-2025&quot;&gt;&lt;strong&gt;4. Industry Demand in 2025&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Full-Stack is booming&lt;/strong&gt; in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SaaS products &amp;amp; startups&lt;/li&gt;
&lt;li&gt;E-commerce &amp;amp; marketplaces&lt;/li&gt;
&lt;li&gt;AI-powered business tools (frontends for LLMs, dashboards)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Data Science is booming&lt;/strong&gt; in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI model fine-tuning&lt;/li&gt;
&lt;li&gt;Predictive analytics in finance, healthcare&lt;/li&gt;
&lt;li&gt;Recommendation systems for content &amp;amp; retail&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-which-scales-better-long-term-&quot;&gt;&lt;strong&gt;5. Which Scales Better Long-Term?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;If “scaling” means:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Easier to enter, quick job offers&lt;/strong&gt; → Full-Stack wins&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Higher specialization, bigger pay ceiling&lt;/strong&gt; → Data Science wins&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global remote flexibility&lt;/strong&gt; → Both win, but Full-Stack slightly edges ahead due to project-based contracts&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-the-hybrid-advantage&quot;&gt;&lt;strong&gt;6. The Hybrid Advantage&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The real winner? &lt;strong&gt;Being both&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Full-Stack Developer who understands data and AI can build end-to-end intelligent products.&lt;/li&gt;
&lt;li&gt;A Data Scientist with frontend/backend skills can deploy models as live apps without relying on engineers.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-takeaway&quot;&gt;&lt;strong&gt;Final Takeaway&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In 2025, both career paths scale — but in different ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full-Stack&lt;/strong&gt; scales in breadth: faster entry, wider range of industries, more freelance/remote opportunities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Science&lt;/strong&gt; scales in depth: higher pay at senior levels, niche expertise in AI-driven sectors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re deciding today, think about &lt;strong&gt;your strengths&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Love &lt;strong&gt;building and shipping apps fast&lt;/strong&gt;? Go Full-Stack.&lt;/li&gt;
&lt;li&gt;Love &lt;strong&gt;analyzing and predicting with data&lt;/strong&gt;? Go Data Science.&lt;/li&gt;
&lt;li&gt;Love &lt;strong&gt;both&lt;/strong&gt;? Congratulations — you’re building a future-proof career.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
</description>
<category>Full-stack Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4507/full-stack-vs-data-science-which-career-path-scales-better-in-2025</guid>
<pubDate>Fri, 08 Aug 2025 08:06:57 +0000</pubDate>
</item>
<item>
<title>Repohistory</title>
<link>https://coderlegion.com/4446/repohistory</link>
<description>&lt;p&gt;As a maintainer of a few open-source projects, I’ve always wanted to better understand the traffic sources and trends for my repos. Unfortunately, GitHub’s built-in analytics only show limited data from the past 14 days, which doesn’t provide much insight.&lt;/p&gt;
&lt;p&gt;That’s why I built &lt;a rel=&quot;nofollow&quot; href=&quot;http://repohistory.com/&quot;&gt;Repohistory&lt;/a&gt;, a better GitHub repo analytics platform. It automatically fetches and stores your traffic data every day, so you’re no longer limited to just 14 days. The dashboard shows you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Daily star growth&lt;/li&gt;
&lt;li&gt;Total views &amp;amp; clones over time&lt;/li&gt;
&lt;li&gt;Top referral websites&lt;/li&gt;
&lt;li&gt;Most-viewed pages in your repo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So if you have any public repos on GitHub, Repohistory can give you a much clearer picture of your traffic trends.&lt;/p&gt;
&lt;p&gt;Try it here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://repohistory.com/&quot;&gt;https://repohistory.com&lt;/a&gt;&lt;/p&gt;
</description>
<category>SaaS</category>
<guid isPermaLink="true">https://coderlegion.com/4446/repohistory</guid>
<pubDate>Tue, 05 Aug 2025 11:50:02 +0000</pubDate>
</item>
</channel>
</rss>