<?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: Alexandra de Lima Diniz</title>
    <description>The latest articles on DEV Community by Alexandra de Lima Diniz (@crime_brasil).</description>
    <link>https://dev.to/crime_brasil</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%2F3893007%2F1e92bba0-f99c-4daa-b9b6-7cfc95d198a3.jpg</url>
      <title>DEV Community: Alexandra de Lima Diniz</title>
      <link>https://dev.to/crime_brasil</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crime_brasil"/>
    <language>en</language>
    <item>
      <title>The last month is always a lie: lessons from querying 1 billion rows of Brazilian crime data</title>
      <dc:creator>Alexandra de Lima Diniz</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:58:08 +0000</pubDate>
      <link>https://dev.to/crime_brasil/the-last-month-is-always-a-lie-lessons-from-querying-1-billion-rows-of-brazilian-crime-data-2bhn</link>
      <guid>https://dev.to/crime_brasil/the-last-month-is-always-a-lie-lessons-from-querying-1-billion-rows-of-brazilian-crime-data-2bhn</guid>
      <description>&lt;p&gt;We run &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;Crime Brasil&lt;/a&gt;, a site that turns official Brazilian public-safety data into maps and rankings for every neighbourhood and municipality in the country. Under the hood that is about 1 billion rows: police reports from state security departments, health-ministry mortality records, court and prison data, traffic and more.&lt;/p&gt;

&lt;p&gt;Most of the hard problems are not about scale. They are about data that looks right and isn't. Here are two rules we now apply before any number leaves the database, plus what they say about São Paulo in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1: the last month in the file is always incomplete
&lt;/h2&gt;

&lt;p&gt;State police data arrives in batches. A report filed today may describe a crime from last week, so the newest month keeps growing for weeks after it "ends".&lt;/p&gt;

&lt;p&gt;Here is the São Paulo police-report feed by day, at the end of our current extract:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Reports&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-26&lt;/td&gt;
&lt;td&gt;2,459&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-27&lt;/td&gt;
&lt;td&gt;2,413&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-28&lt;/td&gt;
&lt;td&gt;2,275&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-29&lt;/td&gt;
&lt;td&gt;2,061&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-30&lt;/td&gt;
&lt;td&gt;1,665&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-08-31&lt;/td&gt;
&lt;td&gt;994&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A normal day in São Paulo is 2,400 to 2,900 reports. If you compare "August 2026" with "August 2025" you get a drop in crime that is really a drop in paperwork.&lt;/p&gt;

&lt;p&gt;The check is one query. Count by day over the last two weeks and look for the tail:&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;data_fato&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&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;FROM&lt;/span&gt; &lt;span class="n"&gt;crimes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SP'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;data_fato&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_fato&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;crimes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SP'&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;ORDER&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the last days have half the volume of the days before, close your window one month earlier. We do this for every state, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 2: keep huge microdata out of your main tables
&lt;/h2&gt;

&lt;p&gt;Some official datasets are far bigger than what a website needs. Brazil's hospital-admission records alone are over 120 million rows. Loading that into normal Postgres tables would eat disk and make backups crawl, for data the site touches a few times a day.&lt;/p&gt;

&lt;p&gt;So every dataset gets one of two homes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Site-facing data&lt;/strong&gt; lives in regular Postgres tables, partitioned by state, indexed for the queries the pages actually run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big research microdata&lt;/strong&gt; stays as Parquet files on disk, queried from inside Postgres with &lt;a href="https://github.com/duckdb/pg_duckdb" rel="noopener noreferrer"&gt;pg_duckdb&lt;/a&gt;. Same SQL, same connection, no copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule we enforce: never bulk-load a Parquet dataset into a heap table. When we need a slice for the site, we aggregate it first and store only the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What São Paulo looks like in 2026
&lt;/h2&gt;

&lt;p&gt;With both rules applied (January to July only, because August is still filling in), here is São Paulo state compared with the same months of 2025. These are counts of police reports from the SSP-SP open-data portal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Crime&lt;/th&gt;
&lt;th&gt;Jan–Jul 2025&lt;/th&gt;
&lt;th&gt;Jan–Jul 2026&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Robbery (excluding vehicles, cargo and banks)&lt;/td&gt;
&lt;td&gt;96,706&lt;/td&gt;
&lt;td&gt;80,180&lt;/td&gt;
&lt;td&gt;−17.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intentional homicide&lt;/td&gt;
&lt;td&gt;1,415&lt;/td&gt;
&lt;td&gt;1,278&lt;/td&gt;
&lt;td&gt;−9.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robbery followed by death (latrocínio)&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;−44.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The robbery decline is not a one-month blip: the monthly count went from about 15,000 in mid-2024 to about 10,500 in July 2026, falling a little almost every month.&lt;/p&gt;

&lt;p&gt;You can explore the same data by city and neighbourhood on the &lt;a href="https://crimebrasil.com.br/estado/sao-paulo" rel="noopener noreferrer"&gt;São Paulo state page&lt;/a&gt;, or compare places in the &lt;a href="https://crimebrasil.com.br/rankings" rel="noopener noreferrer"&gt;rankings&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Before you compute a change, prove the newest month is complete.&lt;/li&gt;
&lt;li&gt;Decide where each dataset lives before you load it, not after the disk fills up.&lt;/li&gt;
&lt;li&gt;Show your window. "January to July" is less exciting than "2026", and much harder to get wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you work with Brazilian open data, I'd like to hear which datasets gave you the most trouble.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>dataengineering</category>
      <category>opendata</category>
      <category>sql</category>
    </item>
    <item>
      <title>Querying 3M+ Brazil crime records with a free public REST API</title>
      <dc:creator>Alexandra de Lima Diniz</dc:creator>
      <pubDate>Thu, 23 Apr 2026 23:50:28 +0000</pubDate>
      <link>https://dev.to/crime_brasil/querying-3m-brazil-crime-records-with-a-free-public-rest-api-47i3</link>
      <guid>https://dev.to/crime_brasil/querying-3m-brazil-crime-records-with-a-free-public-rest-api-47i3</guid>
      <description>&lt;p&gt;Brazil has one of the most complex crime landscapes in Latin America — but getting clean, queryable data has historically been a nightmare of PDFs, broken government portals, and inconsistent formats.&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;Crime Brasil&lt;/a&gt; to fix that. It's a free platform with 3M+ geocoded crime records from RS, RJ and MG, exposed via a public REST API.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3M+ individual crime records&lt;/strong&gt; from Rio Grande do Sul (bairro-level granularity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Municipality-level aggregates&lt;/strong&gt; for Rio de Janeiro and Minas Gerais&lt;/li&gt;
&lt;li&gt;Data from 2019–2026, updated monthly from official SSP sources&lt;/li&gt;
&lt;li&gt;Rates per 100,000 inhabitants for fair regional comparison&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick API examples
&lt;/h2&gt;

&lt;p&gt;Get state-level totals for Rio Grande do Sul:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://crimebrasil.com.br/api/heatmap/states?selected_states=RS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get municipality heatmap data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://crimebrasil.com.br/api/heatmap/municipios?selected_states=RJ&amp;amp;ano=2024
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search for a location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://crimebrasil.com.br/api/search?q=Porto+Alegre
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get detailed stats for a specific location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://crimebrasil.com.br/api/location-stats?state=RS&amp;amp;municipio=PORTO+ALEGRE&amp;amp;bairro=CENTRO+HISTORICO&amp;amp;ultimos_meses=12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Python example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://crimebrasil.com.br/api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Get top municipalities by crime rate in RJ
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/heatmap/municipios&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selected_states&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RJ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;top10&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per 100K&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Open data, open source
&lt;/h2&gt;

&lt;p&gt;All source data comes from official Brazilian state security secretariats (SSP/RS, ISP/RJ, SEJUSP/MG) under open data laws. The platform itself is also open — explore it at &lt;strong&gt;&lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;https://crimebrasil.com.br&lt;/a&gt;&lt;/strong&gt; or check our datasets on Kaggle, Zenodo, and HuggingFace.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the data or methodology in the comments.&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>python</category>
      <category>datascience</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>3 million geocoded crime records from Brazil — free API and datasets</title>
      <dc:creator>Alexandra de Lima Diniz</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:47:45 +0000</pubDate>
      <link>https://dev.to/crime_brasil/3-million-geocoded-crime-records-from-brazil-free-api-and-datasets-33fc</link>
      <guid>https://dev.to/crime_brasil/3-million-geocoded-crime-records-from-brazil-free-api-and-datasets-33fc</guid>
      <description>&lt;p&gt;We've been quietly building &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;Crime Brasil&lt;/a&gt; — an open-data platform covering violent crime across Brazil. Here's what's in it and how to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data covers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rio Grande do Sul&lt;/strong&gt;: 2.99 million occurrences (2022–2025), 497 municipalities, geocoded to neighborhood level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minas Gerais&lt;/strong&gt;: 965K violent-crime records (2019–2026), 853 municipalities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rio de Janeiro&lt;/strong&gt;: 37K ISP/CISP records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PRF DATATRAN&lt;/strong&gt;: ~500K federal road accidents nationally (2020–2026)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DATASUS SINAN&lt;/strong&gt;: interpersonal-violence records nationally (2009–2024)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All data comes from state Secretariats of Public Security (SSP-RS, SSP-MG, ISP-RJ) and federal sources. License: CC BY 4.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;Every city/state has its own endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Crime counts by month for Porto Alegre&lt;/span&gt;
curl &lt;span class="s1"&gt;'https://crimebrasil.com.br/api/city/rs/porto-alegre?type=monthly'&lt;/span&gt;

&lt;span class="c"&gt;# Top 10 neighborhoods by homicide, last 12 months&lt;/span&gt;
curl &lt;span class="s1"&gt;'https://crimebrasil.com.br/api/city/rs/porto-alegre/bairros?crime=homicidio'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is plain JSON — crime counts aggregated by type, period, and neighborhood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downloading datasets
&lt;/h2&gt;

&lt;p&gt;Full datasets are on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://zenodo.org/records/19712036" rel="noopener noreferrer"&gt;Zenodo&lt;/a&gt; — DOI &lt;code&gt;10.5281/zenodo.19712036&lt;/code&gt; (RS, 3M rows)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zenodo.org/records/19711971" rel="noopener noreferrer"&gt;Zenodo&lt;/a&gt; — DOI &lt;code&gt;10.5281/zenodo.19711971&lt;/code&gt; (MG, 965K rows)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.kaggle.com/datasets/valdovaldo/minas-gerais-violent-crime-2019-2026" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt; — MG dataset&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/datasets/crimebrasil/rio-grande-do-sul-crime-2022-2025" rel="noopener noreferrer"&gt;HuggingFace&lt;/a&gt; — RS dataset&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Python example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# Load RS dataset from Zenodo
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://zenodo.org/records/19712036/files/crime-brasil-rs-2022-2025.csv.gz&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gzip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Homicides per year in Porto Alegre
&lt;/span&gt;&lt;span class="n"&gt;homicidios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;municipio_fato&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PORTO ALEGRE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tipo_enquadramento&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HOMICIDIO&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;year_month&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;homicidios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Coverage and methodology
&lt;/h2&gt;

&lt;p&gt;We cover &lt;strong&gt;99.4% of the Brazilian population&lt;/strong&gt; across the states we have data for. The geocoding pipeline assigns lat/lon using a four-pass hierarchy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exact address match&lt;/li&gt;
&lt;li&gt;Neighborhood centroid&lt;/li&gt;
&lt;li&gt;Municipality centroid&lt;/li&gt;
&lt;li&gt;State centroid (fallback)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;99.99% of RS records land at pass 1 or 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  LAI requests in progress
&lt;/h2&gt;

&lt;p&gt;We've filed Freedom of Information (LAI) requests in 14 states: AL, AM, BA, ES, MA, MG, PR, RJ, RN, RO, RR, RS, SC, TO. As responses arrive, we publish them.&lt;/p&gt;




&lt;p&gt;The platform is at &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;crimebrasil.com.br&lt;/a&gt;. Press kit and methodology at &lt;a href="https://crimebrasil.com.br/imprensa" rel="noopener noreferrer"&gt;crimebrasil.com.br/imprensa&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>python</category>
      <category>braziliandevs</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How We Built an Open Crime Data Platform for Brazil (3 Million Records)</title>
      <dc:creator>Alexandra de Lima Diniz</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:42:20 +0000</pubDate>
      <link>https://dev.to/crime_brasil/how-we-built-an-open-crime-data-platform-for-brazil-3-million-records-36l2</link>
      <guid>https://dev.to/crime_brasil/how-we-built-an-open-crime-data-platform-for-brazil-3-million-records-36l2</guid>
      <description>&lt;h1&gt;
  
  
  How We Built an Open Crime Data Platform for Brazil (3 Million Records)
&lt;/h1&gt;

&lt;p&gt;Brazil has one of the world's highest homicide rates — over 47,000 murders per year. Yet accessing crime statistics is surprisingly hard. Data is fragmented across 27 state secretariats, each with different formats, different crime taxonomies, and wildly inconsistent update schedules.&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://crimebrasil.com.br/" rel="noopener noreferrer"&gt;Crime Brasil&lt;/a&gt; to fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Crime Brasil&lt;/strong&gt; is an interactive crime map covering Rio Grande do Sul, Rio de Janeiro, and Minas Gerais — with bairro (neighborhood) level granularity for RS, and municipality-level for the others.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;3+ million individual crime records for RS (2022–2026)&lt;/li&gt;
&lt;li&gt;Choropleth maps at state → city → neighborhood drill-down&lt;/li&gt;
&lt;li&gt;Per-100K-inhabitants rate normalization (so you can actually compare)&lt;/li&gt;
&lt;li&gt;Crime type filters (homicide, robbery, drug offenses, etc.)&lt;/li&gt;
&lt;li&gt;Time range selector (last 6/12/24/36 months or full history)&lt;/li&gt;
&lt;li&gt;API access — all data available via REST endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: FastAPI + SQLAlchemy + SQLite (yeah, SQLite — handles 3M rows fine with proper indexes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 14 + Leaflet.js + Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Docker + zero-downtime atomic alias swap (no Kubernetes needed at this scale)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data pipeline&lt;/strong&gt;: Python ETL from SSP/RS ZIP files, ISP/RJ CSV, SEJUSP/MG CSV&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The hardest part: bairro name normalization
&lt;/h2&gt;

&lt;p&gt;The same neighborhood appears as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CENTRO HISTÓRICO&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CENTRO HISTORICO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;centro historico&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CENTRO-HISTÓRICO&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CTR HIST&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Across police records, census data, and map boundaries.&lt;/p&gt;

&lt;p&gt;We built a phonetic normalization pipeline using custom Brazilian Portuguese rules — strips accents, normalizes common abbreviations (RUA → R, AVENIDA → AV), handles compound names. This alone took about 3 weeks of iteration against real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SQLite, not PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Conventional wisdom says "use Postgres for anything serious." But SQLite with covering indexes on &lt;code&gt;(state, year_month, tipo_enquadramento)&lt;/code&gt; serves our heatmap queries in &amp;lt;100ms at 3M rows. The database is ~800MB. It fits on any $5 VPS. Zero connection pooling headaches. Zero migrations for most schema changes.&lt;/p&gt;

&lt;p&gt;We do have one performance rule: &lt;strong&gt;always use &lt;code&gt;BETWEEN&lt;/code&gt; for year filters, never &lt;code&gt;LIKE&lt;/code&gt;&lt;/strong&gt;. SQLite uses the covering index for &lt;code&gt;BETWEEN '{ano}-01' AND '{ano}-12'&lt;/code&gt; but not for &lt;code&gt;LIKE '{ano}-%'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open data
&lt;/h2&gt;

&lt;p&gt;All datasets are published:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/datasets/valdovaldo/minas-gerais-violent-crime-2019-2026" rel="noopener noreferrer"&gt;Kaggle: MG Violent Crime 2019–2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zenodo.org/records/19712036" rel="noopener noreferrer"&gt;Zenodo: RS Full Dataset&lt;/a&gt; — DOI 10.5281/zenodo.19712036&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/datasets/crimebrasil/rio-grande-do-sul-crime-2022-2025" rel="noopener noreferrer"&gt;HuggingFace: RS 2022–2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;São Paulo coverage (aggregated municipal-level data already being processed)&lt;/li&gt;
&lt;li&gt;API authentication for high-volume consumers&lt;/li&gt;
&lt;li&gt;Historical comparison charts per location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire REST API is public: &lt;code&gt;https://crimebrasil.com.br/api/heatmap/municipios&lt;/code&gt; returns municipality-level heatmap data, &lt;code&gt;https://crimebrasil.com.br/api/location-stats&lt;/code&gt; returns detailed stats per location.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the data pipeline, the normalization approach, or the zero-downtime deploy architecture.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Crime Brasil: &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;https://crimebrasil.com.br&lt;/a&gt; — open crime data for Brazil&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opendata</category>
      <category>fastapi</category>
      <category>brazil</category>
    </item>
    <item>
      <title>How we built Crime Brasil: an open-data crime map for 214 million Brazilians</title>
      <dc:creator>Alexandra de Lima Diniz</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:19:37 +0000</pubDate>
      <link>https://dev.to/crime_brasil/how-we-built-crime-brasil-an-open-data-crime-map-for-214-million-brazilians-gfl</link>
      <guid>https://dev.to/crime_brasil/how-we-built-crime-brasil-an-open-data-crime-map-for-214-million-brazilians-gfl</guid>
      <description>&lt;p&gt;Brazil has one of the highest homicide rates in the world — yet finding reliable, granular crime data at the neighbourhood level has historically been nearly impossible for ordinary citizens, journalists and researchers.&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;Crime Brasil&lt;/a&gt; to fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Crime Brasil is a free, open-data interactive map that lets anyone explore violence and criminality statistics across Brazil — broken down by state, city and neighbourhood. No paywall. No signup required.&lt;/p&gt;

&lt;p&gt;Current coverage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rio Grande do Sul&lt;/strong&gt; — neighbourhood-level (bairro) data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rio de Janeiro&lt;/strong&gt; — city-level breakdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minas Gerais&lt;/strong&gt; — city-level breakdown&lt;/li&gt;
&lt;li&gt;3 million+ crime records from official government registers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 15&lt;/strong&gt; (App Router) + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; with PostGIS for geospatial queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deck.gl + MapLibre&lt;/strong&gt; for the map rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; CDN and WAF&lt;/li&gt;
&lt;li&gt;REST API with per-100k-inhabitants normalisation built in&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open data, all the way
&lt;/h2&gt;

&lt;p&gt;All source data is published as open datasets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/datasets/valdovaldo/minas-gerais-violent-crime-2019-2026" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenodo.org/records/19711971" rel="noopener noreferrer"&gt;Zenodo DOI 10.5281/zenodo.19711971&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/datasets/valdovaldo1500/crime-brasil-rs-2022-2026" rel="noopener noreferrer"&gt;HuggingFace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;crimebrasil.com.br&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're working on public safety research, data journalism, or civic tech in Brazil — we'd love to hear from you.&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>showdev</category>
      <category>nextjs</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>4,524 commits in 7 weeks: what I learned shipping a real product with Claude Code</title>
      <dc:creator>Alexandra de Lima Diniz</dc:creator>
      <pubDate>Thu, 23 Apr 2026 19:23:29 +0000</pubDate>
      <link>https://dev.to/crime_brasil/4524-commits-in-7-weeks-what-i-learned-shipping-a-real-product-with-claude-code-1kc9</link>
      <guid>https://dev.to/crime_brasil/4524-commits-in-7-weeks-what-i-learned-shipping-a-real-product-with-claude-code-1kc9</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;4,524 git commits in 7 weeks (~92/day)&lt;/li&gt;
&lt;li&gt;~42,000 lines of code across Python backend + Next.js frontend&lt;/li&gt;
&lt;li&gt;~3M records ingested, 79,024 neighborhoods geocoded, zero-downtime production deploys, E2E smoke tests, APScheduler jobs, SEO snapshot system&lt;/li&gt;
&lt;li&gt;Total spend: ~$500 (Claude Max $200 × 2 months + Hetzner ~$30 × 2 months)&lt;/li&gt;
&lt;li&gt;Built alone. Shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not going to tell you Claude Code is magic. It isn't. But if you treat it like a very fast, very literal colleague who forgets things between sessions, you can ship more than you'd think. Here's the workflow that worked for me.&lt;/p&gt;

&lt;p&gt;The product is &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;Crime Brasil&lt;/a&gt; — an open-data platform for Brazilian crime statistics. Doesn't matter for this post. What matters is the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow that actually worked
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Plan mode before anything non-trivial
&lt;/h3&gt;

&lt;p&gt;Before any change touching &amp;gt;3 files or introducing new abstractions, I forced a &lt;code&gt;/plan&lt;/code&gt; pass. No code, just a tree of what changes where and why.&lt;/p&gt;

&lt;p&gt;This cut the "Claude confidently wrote 400 lines of the wrong thing" bug in half. The other half came from me being sloppy about what I was actually asking for.&lt;/p&gt;

&lt;p&gt;Pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Tell Claude the goal
2. Ask for a plan (no code)
3. Push back on anything that feels wrong — usually scope creep
4. When the plan feels right, say "implement"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boring. Works.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Sub-agents for parallel anything
&lt;/h3&gt;

&lt;p&gt;Independent work ran in parallel sub-agents. Research + implementation + QA could all run concurrently instead of me waiting for each to finish.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;research&lt;/code&gt; agent — "understand how X works, come back with a map"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;implement&lt;/code&gt; agent — "apply the diff described"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa&lt;/code&gt; agent — "verify the fix, run the regression tests"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;review&lt;/code&gt; agent — "is this safe to merge"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd fire three of these in parallel and get stitched output in a third the wall time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Session scratchpad as an anti-compaction tool
&lt;/h3&gt;

&lt;p&gt;Claude Code compacts its context when conversations get long. After compaction you can lose crucial decisions from earlier.&lt;/p&gt;

&lt;p&gt;Fix: every session writes to a scratchpad file (&lt;code&gt;~/claude-sessions/session_&amp;lt;topic&amp;gt;.md&lt;/code&gt;) with timestamped entries. Key decisions, user preferences, things we tried that failed. Before spawning any sub-agent I inject the relevant scratchpad entries into the prompt.&lt;/p&gt;

&lt;p&gt;This alone probably saved a dozen hours of "wait, why did we decide not to do that again?"&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Memory system for permanent preferences
&lt;/h3&gt;

&lt;p&gt;Claude Code has a memory system that persists across conversations. I ended up writing ~80 memory files over 7 weeks, categorized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;user&lt;/strong&gt; — who I am, what I work on, what I already know&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;feedback&lt;/strong&gt; — rules I've enforced ("never push to main directly", "always chrome-verify fixes, not just curl")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;project&lt;/strong&gt; — state of ongoing work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reference&lt;/strong&gt; — where external resources live (dashboards, tokens, API docs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time I corrected Claude on the same thing twice, I saved a memory rule. Examples from my actual file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Never slow bot renders for SEO&lt;/code&gt; — fixed a bad server-side bot-detection idea&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;No lossy analytics filters&lt;/code&gt; — never exclude geographic data to "clean up" analytics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Wait-loop pattern&lt;/code&gt; — use &lt;code&gt;while pgrep&lt;/code&gt; not &lt;code&gt;until ... &amp;amp;&amp;amp; ! kill -0&lt;/code&gt; (the latter busy-waits)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;No arbitrary safeguards&lt;/code&gt; — don't add population floors to hide bad data, fix the data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Never substitute solutions&lt;/code&gt; — if user asks to fix X, fix X, don't silently solve by changing Y&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one came up repeatedly. Claude loves to pivot to an "easier" problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Hooks for guardrails
&lt;/h3&gt;

&lt;p&gt;Some rules can't be enforced by just asking nicely. For those, I used Claude Code's hook system to block certain actions entirely.&lt;/p&gt;

&lt;p&gt;Examples I ended up needing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block any &lt;code&gt;git commit --no-verify&lt;/code&gt; (never bypass pre-commit hooks)&lt;/li&gt;
&lt;li&gt;Block &lt;code&gt;rm&lt;/code&gt; on data files without explicit confirmation&lt;/li&gt;
&lt;li&gt;Warn on any &lt;code&gt;git push origin main&lt;/code&gt; — all changes must go through dev branch + staging first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hooks turned bad defaults into impossible-by-default. Worth the setup time.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Verification before declaring "done"
&lt;/h3&gt;

&lt;p&gt;Claude loves to say "fixed!" based on an API test passing. That's not the same as the feature actually working in a browser.&lt;/p&gt;

&lt;p&gt;I ended up with a rule: &lt;strong&gt;every UI-touching change gets Chrome-verified before being marked complete.&lt;/strong&gt; A Playwright script clicks through the real flow and screenshots the result. If the screenshot looks wrong, it's not done.&lt;/p&gt;

&lt;p&gt;Memory file: &lt;em&gt;"NEVER report a fix as done without Chrome visual verification."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before this rule: "fixed!" → deploy → user finds it broken → revert. After: "fixed, verified on &lt;code&gt;/cidade/rs/porto-alegre&lt;/code&gt;, panel shows 509K crimes, screenshot attached."&lt;/p&gt;

&lt;h2&gt;
  
  
  Specific bugs Claude caused
&lt;/h2&gt;

&lt;p&gt;Honest list. These are patterns; I saw each of them multiple times.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Let me also refactor while I'm here"
&lt;/h3&gt;

&lt;p&gt;Ask for a small bug fix, get back 400 lines of unrelated cleanup. Sometimes broke unrelated features.&lt;/p&gt;

&lt;p&gt;Memory rule: &lt;em&gt;"Don't add features, refactor, or introduce abstractions beyond what the task requires. Three similar lines is better than a premature abstraction."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Inventing API endpoints
&lt;/h3&gt;

&lt;p&gt;Asked Claude to "call the API to get X data." It wrote code calling &lt;code&gt;/api/thing&lt;/code&gt; that didn't exist. Tests failed. When pushed: "oh right, that endpoint doesn't exist. Let me write it first."&lt;/p&gt;

&lt;p&gt;Fix: when consuming an API, I started forcing it to read the actual source files first. &lt;code&gt;Read src/api/routes.py&lt;/code&gt;, then write the client. Slower, zero hallucinations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent feature deletion
&lt;/h3&gt;

&lt;p&gt;Twice Claude "fixed" a bug by removing the buggy feature. Asked to fix a map polygon render issue, got back code that just disabled polygons entirely. Clean green tests. Feature gone.&lt;/p&gt;

&lt;p&gt;Memory rule: &lt;em&gt;"NEVER remove features unless user explicitly says to. 'bugado' means fix, not remove."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Overclaiming completion
&lt;/h3&gt;

&lt;p&gt;The biggest pattern. "Done!" → I check → 2 of 4 items done, 2 untouched. Claude was genuinely under the impression it had finished.&lt;/p&gt;

&lt;p&gt;Fix: I stopped taking "done" at face value. Every "done" got a follow-up "walk me through what you changed file-by-file, and show me the diff for each." Usually the second response corrected the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude can't do (yet)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architect from scratch.&lt;/strong&gt; Given a vague brief like "build a data platform for X," Claude produces a generic Django blog. Needs strong direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know your domain.&lt;/strong&gt; Took months to teach it Brazilian bairro-name quirks — there's no substitute for local knowledge, and Claude will confidently generalize from Stack Overflow examples that don't apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember across sessions without help.&lt;/strong&gt; Compaction + context limits mean every session is partial amnesia. The scratchpad + memory system is the only thing that saved me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide what not to build.&lt;/strong&gt; Claude will happily implement bad feature ideas. Saying "no" is still human work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost breakdown (~$500)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Max subscription ($200/mo × 2)&lt;/td&gt;
&lt;td&gt;$400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hetzner dedicated server ($30/mo × 2)&lt;/td&gt;
&lt;td&gt;$60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resend email API (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$472&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For reference: a single senior contract dev week at market rates is $4-6K. I shipped this in 7 weeks of evenings + weekends for ~$472 in tools + hosting.&lt;/p&gt;

&lt;p&gt;The caveat: this is a solo project. I didn't have to coordinate with anyone, and nobody needed to review my work. That's a rare situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lines of code
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Lines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python backend (FastAPI + scrapers + ingest + APIs)&lt;/td&gt;
&lt;td&gt;23,559&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript frontend (Next.js + Leaflet)&lt;/td&gt;
&lt;td&gt;18,762&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MDX content (articles)&lt;/td&gt;
&lt;td&gt;6,165&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48,486&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;92 commits/day sounds insane. In practice most commits are small and auto-checkpointed. Real "unit of work" commits are maybe 20-30/day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wouldn't skip if doing it again
&lt;/h2&gt;

&lt;p&gt;In priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scratchpad files.&lt;/strong&gt; Save at least 10x their cost in "wait, what did we decide?" time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;/plan&lt;/code&gt; discipline.&lt;/strong&gt; Every non-trivial change gets a plan first. Boring, works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory rules for every recurring correction.&lt;/strong&gt; Rule of twos: if you correct Claude on the same thing twice, write it down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome-verify before declaring done.&lt;/strong&gt; API tests don't cover UI. UI bugs always happen on the last Friday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks for dangerous defaults.&lt;/strong&gt; Anything you really don't want — make it impossible by default, not just discouraged.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start the memory system earlier.&lt;/strong&gt; I was 2 weeks in before I realized it existed. Week 1 was slower than it needed to be.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spin up sub-agents sooner.&lt;/strong&gt; For a while I was doing everything sequentially in the main agent. Parallel work is a huge speed-up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the CLAUDE.md from day 1.&lt;/strong&gt; A per-project &lt;code&gt;CLAUDE.md&lt;/code&gt; explaining architecture and conventions prevents a lot of drift.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Crime Brasil is live at &lt;a href="https://crimebrasil.com.br" rel="noopener noreferrer"&gt;crimebrasil.com.br&lt;/a&gt;. It serves ~3M records across 497 municipalities, gets Google traffic for specific city and neighborhood queries, and was built almost entirely in a Claude Code harness.&lt;/p&gt;

&lt;p&gt;I'm happy to answer questions about the workflow. The answer to "can AI replace developers" is still no, but "can a developer with AI replace two developers without AI" is absolutely yes, and that's interesting enough.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow along on X at &lt;a href="https://x.com/Crime_Brasil" rel="noopener noreferrer"&gt;@Crime_Brasil&lt;/a&gt; if you want to see what ships next.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>python</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
