<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chang Sup Woo</title>
    <description>The latest articles on DEV Community by Chang Sup Woo (@llmsnetwork).</description>
    <link>https://dev.to/llmsnetwork</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136710%2Fa23450ce-4093-428c-aa84-262deaeb5e11.JPG</url>
      <title>DEV Community: Chang Sup Woo</title>
      <link>https://dev.to/llmsnetwork</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/llmsnetwork"/>
    <language>en</language>
    <item>
      <title>I removed 40% of my own rows. I can only prove that 27% of them were wrong.</title>
      <dc:creator>Chang Sup Woo</dc:creator>
      <pubDate>Wed, 23 Sep 2026 02:51:19 +0000</pubDate>
      <link>https://dev.to/llmsnetwork/i-removed-40-of-my-own-rows-i-can-only-prove-that-27-of-them-were-wrong-1afi</link>
      <guid>https://dev.to/llmsnetwork/i-removed-40-of-my-own-rows-i-can-only-prove-that-27-of-them-were-wrong-1afi</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I build measurement tooling in this area. Nothing here is a claim about any vendor's product — every error described is mine.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For 19 days in July I ran the same 20 questions through one model every morning and counted which companies came back. Then I looked at what the counter was actually doing.&lt;/p&gt;

&lt;p&gt;I threw away 40% of the rows. Only part of that was provably wrong, and separating the two turned out to be the whole exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On naming:&lt;/strong&gt; I don't name any of the companies involved, or the vendor whose API I used. The bugs were mine, not theirs, and attaching my errors to their names would be a second mistake in the opposite direction. Where a figure is a direct count I say so; where it's derived by arithmetic, or comes from a note I wrote at the time rather than a fresh query, I say that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19 consecutive days&lt;/strong&gt;, 8–26 July 2026, Sydney time, no gaps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 fixed questions&lt;/strong&gt;, unchanged for the whole run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One commercial LLM API&lt;/strong&gt;, one model, held constant&lt;/li&gt;
&lt;li&gt;A watchlist of &lt;strong&gt;71 name entries&lt;/strong&gt; — I called them companies at the time, but several are open-source projects, which turns out to matter&lt;/li&gt;
&lt;li&gt;Matching, verbatim from the code that ran:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Two separate 500s, which I'll come back to: a &lt;strong&gt;500-token cap on the API request&lt;/strong&gt;, and &lt;strong&gt;only the first 500 characters of each response written to the database&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matching rule is where I would have pointed if you'd asked me what went wrong. It turned out to be one of three causes, and not the most interesting one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20 questions × 19 days = 380 answers.&lt;/strong&gt; Those 380 produced &lt;strong&gt;1,814 rows&lt;/strong&gt; — one row per name matched per answer, so about 4.8 rows each. Daily row counts ranged from &lt;strong&gt;79 to 117&lt;/strong&gt;, a 1.48x spread across days where the questions, the model and the schedule were all identical.&lt;/p&gt;

&lt;p&gt;Except it wasn't 380. It was 376. I'll come back to that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "I removed 40%" actually means
&lt;/h2&gt;

&lt;p&gt;I removed &lt;strong&gt;723 rows, 39.9%&lt;/strong&gt; of the total. For months I described that as a false positive rate. It isn't one. It's three different things with three different causes, and only the first is an error I can demonstrate.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Substring matches — 492 rows (27.1%)
&lt;/h3&gt;

&lt;p&gt;Seven names on the watchlist collide with ordinary English — either as whole words, or as strings sitting inside longer words. A substring match has no way to tell a company from a sentence.&lt;/p&gt;

&lt;p&gt;Individually, those seven produced between 31 and 152 phantom rows each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause: code.&lt;/strong&gt; This is the part I can call wrong without qualification: the matched text is not a mention by any reading.&lt;/p&gt;

&lt;p&gt;Word boundaries and case sensitivity would have caught the clearest of these. I have not re-run the corrected rule against the original answers, because those answers no longer exist in full — so I'm describing a fix, not a measured improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. An entry whose count can't be interpreted — 213 rows (11.7%)
&lt;/h3&gt;

&lt;p&gt;One entry accounted for 213 rows by itself. I've since checked every name's count: it is the largest of all 50 names that appeared, ahead of the second by a wide margin, and more than a tenth of the entire dataset.&lt;/p&gt;

&lt;p&gt;Two things are true about that entry at once. It is &lt;strong&gt;common technical vocabulary in this field&lt;/strong&gt; — it turns up in ordinary writing about the subject, independently of whether any company is being discussed. And &lt;strong&gt;the vendor whose model generated all 380 answers was itself on my watchlist.&lt;/strong&gt; A model naming its own vendor is expected behaviour, not a bug.&lt;/p&gt;

&lt;p&gt;So those 213 rows are some mixture of genuine third-party mention, ordinary vocabulary, and a system talking about itself — and I stored only a 500-character prefix of each answer, so I cannot recover the proportions.&lt;/p&gt;

&lt;p&gt;I want to be careful here, because I got this wrong in an earlier draft: I wrote that &lt;em&gt;no&lt;/em&gt; matching rule could have saved this entry. That's too strong. Case sensitivity would separate some of it — I've since written exactly that rule. But it would not separate self-reference from genuine mention, and nothing in my stored data can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause: design.&lt;/strong&gt; I built a list of "companies in this category" without asking which entries were capable of producing an interpretable count — and without noticing that one of them was the thing doing the measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is why I call it removed rather than wrong.&lt;/strong&gt; Some unknown share of those 213 were real.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Names I no longer have — 18 rows (1.0%)
&lt;/h3&gt;

&lt;p&gt;Twelve names were excluded in the original cleanup. I can reconstruct eight from a note written at the time; those eight total 705 rows. The other four are gone.&lt;/p&gt;

&lt;p&gt;I know they account for 18 rows because the arithmetic closes — but the 18 rests on a filtered count of &lt;strong&gt;1,091&lt;/strong&gt; that I recorded on 4 August 2026 and have not re-derived from the raw table. I can't re-derive it: the query would need the four names I no longer have. So the 1.0% is real arithmetic on a number I'm trusting my past self about.&lt;/p&gt;

&lt;p&gt;I can't recover the four from the totals either. With every name's count now in front of me, &lt;strong&gt;235 different four-name combinations sum to 18.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause: record-keeping.&lt;/strong&gt; The cleanup happened outside anything that kept a log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These 18 rows aren't false positives at all.&lt;/strong&gt; They were removed because I'd lost track of them.&lt;/p&gt;




&lt;p&gt;Add those up and the honest version is: &lt;strong&gt;492 rows I can show were wrong (27.1%), 213 I can't classify either way (11.7%), and 18 I dropped for bookkeeping (1.0%).&lt;/strong&gt; After removing all 723: 1,091 rows, 38 names, 57.4 per day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I can't close: false negatives
&lt;/h2&gt;

&lt;p&gt;Everything above is about counting things that weren't there. The harder question is what I failed to count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21 of the 71 entries on the watchlist produced zero rows across all 19 days.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I know why one of them did. The watchlist entry was missing a punctuation character that appears in the name — I have the file, and the entry is written without it. The string never matched anything. Not one row, across nineteen days, out of 376 answers.&lt;/p&gt;

&lt;p&gt;From inside the data, an entry with a typo and an entry genuinely absent from every answer look identical. Both are zero.&lt;/p&gt;

&lt;p&gt;I found that one by accident. &lt;strong&gt;I have not checked the other twenty.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the summary of the whole exercise is: I can account for 27% of my rows being wrong, and I have no idea what my false negative rate is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways to get a zero
&lt;/h2&gt;

&lt;p&gt;A zero in this dataset has at least four possible causes, and from stored data you usually can't tell them apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The name genuinely wasn't mentioned.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The name didn't match&lt;/strong&gt; — punctuation, spacing, a rebrand, an abbreviation the model used instead. Confirmed at least once, as above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The answer was cut off before it got there.&lt;/strong&gt; The API request capped responses at 500 tokens, so long answers stopped mid-sentence and anything named after the cut-off could not be counted. I did not store the stop reason, so I can't tell you how many answers actually hit the cap — only that the cap applied to all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The call failed, or came back empty.&lt;/strong&gt; The pipeline had no retry policy and no guard for an empty response. A failed call and an answer containing none of my 71 names produce exactly the same thing: nothing.&lt;/p&gt;

&lt;p&gt;Which brings me back to 376. Twenty questions across nineteen days should be 380 question-days. The database has &lt;strong&gt;376&lt;/strong&gt;. Four answers produced no rows at all, and &lt;strong&gt;I cannot tell you which of the four causes above applies to any of them.&lt;/strong&gt; That's 1.05% of the run, and it's the cleanest illustration I have of the problem: the gap is visible, the reason isn't.&lt;/p&gt;

&lt;p&gt;This matters to me because &lt;em&gt;"we don't appear in AI answers"&lt;/em&gt; is a claim I was preparing to make. It requires ruling out causes 2, 3 and 4, and I had ruled out none of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two 500s, doing different damage
&lt;/h2&gt;

&lt;p&gt;I spent a while treating these as one limitation. They aren't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API cap (500 tokens)&lt;/strong&gt; truncates the answer before matching runs. It limits &lt;strong&gt;what could be counted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The storage truncation (500 characters)&lt;/strong&gt; happens after matching, when the row is written. It limits &lt;strong&gt;what can be re-examined&lt;/strong&gt;. I can re-read the first 500 characters of each answer, so I can check matches that happened early — but any match past that point is unverifiable from storage, and 500 characters is a fraction of a 500-token answer.&lt;/p&gt;

&lt;p&gt;One caps the measurement. The other caps the audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more things that were wrong, which I'd have missed
&lt;/h2&gt;

&lt;p&gt;While preparing this I re-read the stored schema rather than just the counts, and found two problems I hadn't reported to myself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A column that never meant anything.&lt;/strong&gt; Each row carries a &lt;code&gt;rank_position&lt;/code&gt;. I'd assumed it was position within an answer. It was assigned as the row's index in a results array spanning all 20 questions of that day's run — so it encodes the order rows happened to be appended, nothing more. The column isn't noisy. It's meaningless, and it has been in the table since day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A date column off by a day.&lt;/strong&gt; Rows store a run date computed in UTC, while every analysis query I've written converts to Sydney time. The job runs at 01:00 Sydney — I checked, and every row in the table was written in that hour — which is the previous day in UTC. The two columns disagree by one day for all 1,814 rows. I've been reading the converted one, so the figures here hold; had I grouped by the stored column I'd have gotten a different answer to the same question.&lt;/p&gt;

&lt;p&gt;Neither of these shows up as a wrong number in a dashboard. Both would have survived any amount of staring at the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this writeup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Four of the twelve excluded names aren't preserved.&lt;/strong&gt; 18 rows, 1.0%. I'm reporting arithmetic, not a list — and 235 combinations fit it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 1,091 figure is from a note dated 4 August 2026&lt;/strong&gt;, not a fresh query, and it can't be re-derived without the four missing names. The 18, the 723, the 39.9%, the 38 and the 57.4 all depend on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the first 500 characters of each response were stored.&lt;/strong&gt; The counts are recoverable; the matching decisions mostly aren't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The per-row mention count was hardcoded to 1.&lt;/strong&gt; Every figure here counts &lt;em&gt;records&lt;/em&gt;, not how many times a name appeared inside an answer. Those are different measurements and I've seen them conflated, including by me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pipeline definition is preserved&lt;/strong&gt; — an exported workflow file whose last-modified date is the day before the run began, and the code quoted above comes from it. I can't prove from inside the file that it's byte-identical to what executed on each of the 19 days; the file carries no internal timestamp, and I have three similarly-named exports.&lt;/li&gt;
&lt;li&gt;One model, one set of 20 questions, 19 days, one category. I haven't tested whether any of this generalises, and I'm not claiming it does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduction
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'Australia/Sydney'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                     &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;companies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                                         &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;geo_research&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'Australia/Sydney'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-08'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-26'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 19, 20, 50, 1814&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 376:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'Australia/Sydney'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;geo_research&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'Australia/Sydney'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-08'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-26'&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 376, not 380&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;companies = 50&lt;/code&gt; rather than 71 is the false-negative problem stated as a query result: 21 entries never produced a single row.&lt;/p&gt;

&lt;p&gt;The per-name breakdown behind the 492 / 213 / 18 split is the same table grouped by name, which I've left out for the reason at the top. Every piece of arithmetic is in the piece.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Word boundaries and case sensitivity before anything else&lt;/li&gt;
&lt;li&gt;Ask, for every entry on a watchlist, whether it &lt;em&gt;can&lt;/em&gt; produce an interpretable count — starting with whether the measuring tool is on the list&lt;/li&gt;
&lt;li&gt;Store the whole response, not a prefix. Storage is cheaper than doubt&lt;/li&gt;
&lt;li&gt;Store the stop reason and the failure, so a zero can be traced&lt;/li&gt;
&lt;li&gt;Write down every exclusion &lt;em&gt;as&lt;/em&gt; you exclude it&lt;/li&gt;
&lt;li&gt;Read the schema, not just the totals&lt;/li&gt;
&lt;li&gt;Treat a zero as a question, not an answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one is the only one I'd call a lesson. The rest is just being careful.&lt;/p&gt;

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