<?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: KENGNI DORIANE</title>
    <description>The latest articles on DEV Community by KENGNI DORIANE (@dolvis).</description>
    <link>https://dev.to/dolvis</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%2F1162794%2Fac1b4647-21a5-45ad-8c65-f8c9ea61f212.png</url>
      <title>DEV Community: KENGNI DORIANE</title>
      <link>https://dev.to/dolvis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dolvis"/>
    <language>en</language>
    <item>
      <title>Everyone knows black dogs wait longer at the shelter. I checked 82,510 of them. published: true</title>
      <dc:creator>KENGNI DORIANE</dc:creator>
      <pubDate>Mon, 17 Aug 2026 08:48:17 +0000</pubDate>
      <link>https://dev.to/dolvis/everyone-knows-black-dogs-wait-longer-at-the-shelter-i-checked-82510-of-thempublished-true-4cnl</link>
      <guid>https://dev.to/dolvis/everyone-knows-black-dogs-wait-longer-at-the-shelter-i-checked-82510-of-thempublished-true-4cnl</guid>
      <description>&lt;p&gt;&lt;em&gt;I started this for the &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;DEV Weekend Challenge: Dog Days Edition&lt;/a&gt; and didn't finish in time to submit. Publishing it anyway — what the data turned out to say deserves an audience more than it deserves a deadline.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;International Dog Day is August 26. Most of the internet will spend it posting&lt;br&gt;
photos of the dogs people have. I spent a weekend counting the ones nobody came&lt;br&gt;
for.&lt;/p&gt;

&lt;p&gt;The Austin Animal Center — the largest no-kill shelter in the United States —&lt;br&gt;
publishes two public registers: every animal that arrived, and every animal that&lt;br&gt;
left. Between them sit 297,982 rows going back to October 2013.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neither register says how long a dog stayed.&lt;/strong&gt; That number doesn't exist in the&lt;br&gt;
data. It has to be built.&lt;/p&gt;

&lt;p&gt;So I built it, for all 82,510 dogs.&lt;/p&gt;
&lt;h2&gt;
  
  
  The thing everyone knows is not true
&lt;/h2&gt;

&lt;p&gt;"Black dog syndrome" is the widely repeated belief that black dogs are adopted&lt;br&gt;
more slowly than lighter ones. It has its own Wikipedia section. Shelters run&lt;br&gt;
campaigns about it. Rescue blogs treat it as settled.&lt;/p&gt;

&lt;p&gt;Across a decade of records:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Coat&lt;/th&gt;
&lt;th&gt;Dogs&lt;/th&gt;
&lt;th&gt;Median wait&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;td&gt;29,596&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every other colour&lt;/td&gt;
&lt;td&gt;52,914&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Identical. Among dogs that were actually adopted, also identical — eight days&lt;br&gt;
each. The mean is marginally &lt;em&gt;lower&lt;/em&gt; for black dogs.&lt;/p&gt;

&lt;p&gt;I built an entire cohort filter expecting to visualise a gap. There isn't one.&lt;/p&gt;
&lt;h2&gt;
  
  
  The real divide is breed, and it isn't close
&lt;/h2&gt;

&lt;p&gt;A pit-bull-type dog waits a median of &lt;strong&gt;23 days&lt;/strong&gt; for adoption. A terrier or a&lt;br&gt;
small dog waits &lt;strong&gt;5&lt;/strong&gt; — same building, same staff, same day.&lt;/p&gt;

&lt;p&gt;And the number that stopped me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pit-bull-type dogs are 18.7% of the dogs in this dataset.&lt;br&gt;
They are 44.7% of every dog that waited more than ninety days.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nearly half of the forgotten dogs are one kind of dog.&lt;/p&gt;
&lt;h2&gt;
  
  
  The oldest dogs leave fastest, and that's the worst finding here
&lt;/h2&gt;

&lt;p&gt;Senior dogs have the &lt;em&gt;shortest&lt;/em&gt; median stay of any age group — three days. For a&lt;br&gt;
moment that reads as good news.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Age at intake&lt;/th&gt;
&lt;th&gt;Dogs&lt;/th&gt;
&lt;th&gt;Median stay&lt;/th&gt;
&lt;th&gt;Adopted&lt;/th&gt;
&lt;th&gt;Euthanised&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Puppy (&amp;lt;1 yr)&lt;/td&gt;
&lt;td&gt;26,616&lt;/td&gt;
&lt;td&gt;5 d&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adult (3–7)&lt;/td&gt;
&lt;td&gt;18,192&lt;/td&gt;
&lt;td&gt;6 d&lt;/td&gt;
&lt;td&gt;36.1%&lt;/td&gt;
&lt;td&gt;3.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Senior (8+)&lt;/td&gt;
&lt;td&gt;6,939&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3 d&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A senior is less than half as likely to be adopted as a puppy, and six times more&lt;br&gt;
likely to be euthanised. They leave quickly because somebody comes to reclaim&lt;br&gt;
them, or because they die. Not because they're wanted.&lt;/p&gt;

&lt;p&gt;Speed of exit is not the same thing as being chosen — a distinction invisible in&lt;br&gt;
any single average, which is exactly why I ended up showing every dog instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  See it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://kengnidoriane.github.io/The-waiting-room/" rel="noopener noreferrer"&gt;The Waiting Room&lt;/a&gt;&lt;/strong&gt; renders all 82,510 stays as 82,510 dots.&lt;br&gt;
One dot is one real dog, sorted by how long it waited and stacked into bands, so&lt;br&gt;
the shape of the field &lt;em&gt;is&lt;/em&gt; the distribution: a huge mass gone within a week, and&lt;br&gt;
a thin bright line at the bottom of dogs who waited over three months. Filter by&lt;br&gt;
coat or breed and watch a cohort concentrate — or fail to — in the long-wait&lt;br&gt;
bands. Hover any dot for that animal's actual record.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenzpquvnhfjhmeloix02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenzpquvnhfjhmeloix02.png" alt="82,510 shelter dogs as dots in six bands by wait length: most gone within a week, a thin pale line of 3,040 who waited over three months" width="800" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff3sf0ycygq2g67qlai49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff3sf0ycygq2g67qlai49.png" alt="The same field with pit-bull types in orange: sparse in the fast bands, thickening toward the long waits. Median rises from five days to nine." width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kengnidoriane" rel="noopener noreferrer"&gt;
        kengnidoriane
      &lt;/a&gt; / &lt;a href="https://github.com/kengnidoriane/The-waiting-room" rel="noopener noreferrer"&gt;
        The-waiting-room
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      94,505 real shelter dogs, and how long each one waited. Twelve years of open records joined in Snowflake — one dot is one dog.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;The Waiting Room&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;94,505 real shelter dogs, and how long each one waited.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Twelve years of intake and outcome records from the Austin Animal Center, joined
to answer a question neither register states directly: &lt;em&gt;how many days did each dog
wait before somebody came?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One dot on the page is one dog.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/weekend-2026-08-13" rel="nofollow"&gt;DEV Weekend Challenge: Dog Days Edition&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem this solves&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The City of Austin publishes two separate public registers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intakes&lt;/strong&gt; — every animal that arrived, with breed, colour, age and condition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outcomes&lt;/strong&gt; — every animal that left, and what became of it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither one contains a length of stay. That number does not exist in the source
data. It has to be reconstructed by pairing each arrival with its matching
departure — and that is harder than it looks, because a dog can pass through the
shelter more than once. A naive join on…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kengnidoriane/The-waiting-room" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The join is the whole project.&lt;/strong&gt; A dog can pass through the shelter more than&lt;br&gt;
once — 12,932 of these stays belong to an animal that had been there before. A&lt;br&gt;
naive join on animal ID pairs every arrival with every departure and produces&lt;br&gt;
nonsense. What you need, for each arrival, is &lt;em&gt;its&lt;/em&gt; next departure:&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="n"&gt;i&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="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out_ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outcome_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;date_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;in_ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out_ts&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_waited&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animal_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animal_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;in_ts&lt;/span&gt;
&lt;span class="n"&gt;QUALIFY&lt;/span&gt; &lt;span class="n"&gt;row_number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animal_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visit_no&lt;/span&gt;
                           &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out_ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;QUALIFY&lt;/code&gt; is a &lt;code&gt;WHERE&lt;/code&gt; that runs &lt;em&gt;after&lt;/em&gt; the window function. Without it this&lt;br&gt;
needs a nested subquery and the intent gets buried; with it the rule reads in one&lt;br&gt;
line. That single clause is what turns two flat CSVs into this piece.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything is computed before the browser sees it.&lt;/strong&gt; DuckDB handles ingestion,&lt;br&gt;
the join and the aggregation, precomputing every coat × breed-group combination&lt;br&gt;
with a &lt;code&gt;CUBE&lt;/code&gt;. The page is entirely static — no API, no server, nothing that can&lt;br&gt;
fall over months from now. The browser does no analysis. It draws.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canvas, not SVG.&lt;/strong&gt; 82,510 SVG nodes would cost hundreds of megabytes of DOM and&lt;br&gt;
seconds of layout; the same marks as &lt;code&gt;fillRect&lt;/code&gt; calls paint in about twenty&lt;br&gt;
milliseconds, so the field redraws instantly on every filter change. Sorting the&lt;br&gt;
dogs and stacking them into six labelled bands — each band's height proportional&lt;br&gt;
to its count — is what makes 82,510 dots a chart instead of a texture. The palette&lt;br&gt;
was validated rather than eyeballed: worst adjacent colour-vision-deficiency&lt;br&gt;
separation ΔE 26.8 against a floor of 8, with a legend and a table view so&lt;br&gt;
identity never rests on colour alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What went wrong.&lt;/strong&gt; The Austin portal 403s Python's default User-Agent, then&lt;br&gt;
refused my network entirely, browser included — the pipeline now tries three&lt;br&gt;
sources and prints a manual fallback. The two sources also disagree about&lt;br&gt;
everything: one calls the column &lt;code&gt;animal_id&lt;/code&gt;, the other &lt;code&gt;"Animal ID"&lt;/code&gt;; one dates&lt;br&gt;
in ISO 8601, the other as &lt;code&gt;12/02/2013 12:00:00 AM&lt;/code&gt;. Columns are matched on letters&lt;br&gt;
and digits alone, and timestamps parse under either format.&lt;/p&gt;

&lt;p&gt;I also wired an optional Gemini path to normalise the 2,503 free-text breed&lt;br&gt;
strings, classifying the &lt;em&gt;distinct&lt;/em&gt; values and joining back rather than labelling&lt;br&gt;
all 82,510 rows. My network dropped the outbound connection, the script degraded&lt;br&gt;
to pattern matching, and the run completed. &lt;strong&gt;The published figures come from&lt;br&gt;
pattern matching.&lt;/strong&gt; I'd rather say that than imply an AI step that didn't happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't prove
&lt;/h2&gt;

&lt;p&gt;One shelter, one American city, 1 October 2013 to 11 March 2023. This describes&lt;br&gt;
Austin, not dogs. "Black" is a substring match on a free-text colour field, so a&lt;br&gt;
black-and-white dog counts as black. Breed groups come from staff guesses at&lt;br&gt;
intake, unreliable for mixed dogs — a caveat that lands hardest on exactly the&lt;br&gt;
group with the worst numbers. Median stay mixes adoption, reclaim, transfer and&lt;br&gt;
death, which is why the age table breaks outcomes out separately.&lt;/p&gt;

&lt;p&gt;Every figure here is reproducible with one command. If you think I got something&lt;br&gt;
wrong, the repo is right there and I'd genuinely like to know.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source.&lt;/strong&gt; Austin Animal Center Intakes and Outcomes, published by the&lt;br&gt;
&lt;a href="https://data.austintexas.gov/Health-and-Community-Services/Austin-Animal-Center-Outcomes-10-01-2013-to-05-05-/9t4d-g238" rel="noopener noreferrer"&gt;City of Austin Open Data Portal&lt;/a&gt;.&lt;br&gt;
The portal returns &lt;code&gt;403 Forbidden&lt;/code&gt; on some networks, mine included, so I worked&lt;br&gt;
from &lt;a href="https://github.com/pierrechristian/Austin-animal-shelter" rel="noopener noreferrer"&gt;a mirror of the same export&lt;/a&gt;&lt;br&gt;
— a copy, not the source. The data is the City of Austin's, and a decade of every&lt;br&gt;
arrival and every departure, free to anyone who asks, is the reason a stranger on&lt;br&gt;
another continent could find this out in a weekend.&lt;/p&gt;

&lt;p&gt;3,040 dogs in this dataset waited more than ninety days. 1,359 of them were the&lt;br&gt;
same kind of dog.&lt;/p&gt;

</description>
      <category>shadowdev</category>
      <category>datascience</category>
      <category>sql</category>
      <category>opendata</category>
    </item>
    <item>
      <title>title</title>
      <dc:creator>KENGNI DORIANE</dc:creator>
      <pubDate>Sat, 05 Oct 2024 08:24:45 +0000</pubDate>
      <link>https://dev.to/dolvis/title-1phg</link>
      <guid>https://dev.to/dolvis/title-1phg</guid>
      <description></description>
      <category>webdev</category>
      <category>programming</category>
      <category>cloud</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
