<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>सतत</title><link>https://baali.muse-amuse.in/</link><description>Documenting my metamorphosis through code, circuits, and containers</description><atom:link href="https://baali.muse-amuse.in/rss.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:baali@muse-amuse.in"&gt;शांतनु&lt;/a&gt; 
&lt;a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/"&gt;
&lt;img alt="Creative Commons License BY-NC-SA"
style="border-width:0; margin-bottom:12px;"
src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png"&gt;&lt;/a&gt;</copyright><lastBuildDate>Fri, 07 Aug 2026 12:27:37 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>The Good, the Bad, the Ugly</title><link>https://baali.muse-amuse.in/posts/the-good-the-bad-the-ugly.html</link><dc:creator>शांतनु</dc:creator><description>&lt;blockquote&gt;
&lt;p&gt;
“I don't want whatever I want. Nobody does. Not really. What kind of
fun would it be if I just got everything I ever wanted just like that,
and it didn't mean anything? What then?”
&lt;/p&gt;

&lt;p&gt;
— Coraline
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div id="outline-container-org6affee3" class="outline-2"&gt;
&lt;h2 id="org6affee3"&gt;The Good:&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org6affee3"&gt;
&lt;p&gt;
LLMs are powerful. There are many novel ways of harnessing them. I got
Claude implement a feature that I don't think I could have done myself
within the time and budget constraints.
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;&lt;b&gt;Task&lt;/b&gt;&lt;/b&gt;: Implement AirPlay sender to allow ESP32-based &lt;a href="https://www.moodblocks.com/products/moodplay"&gt;MoodPlay&lt;/a&gt; to
stream content to any AirPlay compatible speaker.
&lt;/p&gt;

&lt;p&gt;
I was able to use &lt;a href="https://pyatv.dev/"&gt;pyatv&lt;/a&gt; from my Linux machine to AirPlay an MP3 file
to a HomePod mini. But if I targeted a Sonos speaker there was no
audio. The Sonos app showed that it was playing something.
&lt;/p&gt;

&lt;p&gt;
I had a starting point: an Open-source reference that worked and I
wanted to reproduce it with an ESP32. I created a sandboxed
environment for Claude on a Raspberry Pi4. I cloned repositories for
the reference, connected an ESP32-S3-DevKitC-1 to it, and set up
esp-idf so that Claude could flash the firmware and let it cook
(&lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;). It took some time and lots of
tokens but eventually I got the music streaming on HomePod mini.
&lt;/p&gt;

&lt;p&gt;
Next step: get the AirPlay working with Sonos. I did some digging
around. I was able to use AirPlay from an iOS device to Sonos. I
captured the metadata from the speaker and added it to the CLAUDE.md
file. Further, there is (audio) media server project, &lt;a href="https://owntone.github.io/owntone-server/"&gt;OwnTone,&lt;/a&gt; that
allows streaming local media using AirPlay. Claude confirmed that
using OwnTone it was able to stream the content to Sonos. Again we got
a functioning open source reference that Claude was able to port to
ESP32. &lt;b&gt;&lt;b&gt;Nicely done&lt;/b&gt;&lt;/b&gt;.
&lt;/p&gt;

&lt;p&gt;
In the end it was ~2600 lines of portable C. It is a prototype to
validate the idea and showcase a working setup/demo. I learned a lot
about setting up tools for Claude and letting it work: &lt;b&gt;&lt;b&gt;the
Good&lt;/b&gt;&lt;/b&gt;. That was the goal of the project: can we harness Claude for
this? But, I have zero understanding of the code itself. If there's a
bug in the code, I won't have any clue where to start debugging. And
that is a problem.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org2eb7c02" class="outline-2"&gt;
&lt;h2 id="org2eb7c02"&gt;The Bad:&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org2eb7c02"&gt;
&lt;p&gt;
This experience is from another project: &lt;a href="https://takearast.com/"&gt;Rast&lt;/a&gt;. I am using a PN532 NFC
module to read NFC cards with a Raspberry Pi:
&lt;/p&gt;


&lt;div id="org45e701d" class="figure"&gt;
&lt;p&gt;&lt;img src="https://baali.muse-amuse.in/galleries/NFC.webp" alt="nil"&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
I have one working unit for myself and I am remotely setting up
another one in &lt;a href="https://motionlab.berlin/"&gt;Motionlab Berlin&lt;/a&gt;. This NFC board supports three
communication modes— I2C, UART and SPI — and the mode is set using
jumper pins. I had it working locally and I was struggling to get the
same working on the remote setup. I could see the device under &lt;code&gt;/dev&lt;/code&gt;
, enabled the communication mode on RPi (&lt;code&gt;config.txt&lt;/code&gt;), and confirmed
the connection, but when I tried to read a card, it wouldn't
work. With support from &lt;a href="https://www.felixchristmann.com/"&gt;Felix&lt;/a&gt;, I kept on trying different modes but
without success. I shared images of my working RPi, logs, and errors
with Claude but beyond changing modes there was no progress. Claude
was now suggesting that I shift kernel branches:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
The real fix — downgrade the kernel on the broken Pi
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Look at the confidence of that phrasing: ugh. I started talking to
Felix and going through the wiring. Turns out I was using 5V for
&lt;code&gt;VCC&lt;/code&gt; on my system and Felix was using 3V. We switched to 5V and
voila, it worked. UART &lt;b&gt;&lt;b&gt;NEEDS&lt;/b&gt;&lt;/b&gt; a 5V VCC connection and SPI works
with 3V.
&lt;/p&gt;

&lt;p&gt;
Claude was helpful with testing additional commands and configs that I
was not aware of. But I also had some sense of when not to trust its
recommendation and keep doing what I was doing. Its confidence and
hallucinations were very telling: &lt;b&gt;&lt;b&gt;the Bad&lt;/b&gt;&lt;/b&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-orgecade70" class="outline-2"&gt;
&lt;h2 id="orgecade70"&gt;The Ugly:&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orgecade70"&gt;
&lt;p&gt;
This is from a third project that is putting together a data pipeline
and a user-facing dashboard built with &lt;a href="https://www.draxlr.com/"&gt;Draxlr&lt;/a&gt;. The dashboard graph
runs SQL queries and plots the result. The dashboard feature PR was
created using an LLM and I was reviewing it. The changeset was 1390
lines of SQL (&lt;b&gt;&lt;b&gt;the Ugly&lt;/b&gt;&lt;/b&gt;):
&lt;/p&gt;


&lt;div id="org121f85f" class="figure"&gt;
&lt;p&gt;&lt;img src="https://baali.muse-amuse.in/galleries/changeset.png" alt="nil"&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Review is a strong word. I tested it. Ran a bunch of queries
independently. Got an overall sense of how they contributed to the
dashboard and "approved" the PR with a comment that we should also
monitor the load on the postgres server when we visit the dashboard.
&lt;/p&gt;

&lt;p&gt;
After a few days, there was a &lt;code&gt;bugfix&lt;/code&gt; PR, from Claude:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;er_kg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;er_kg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"CO₂ Avoided (kg)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;er_kg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;er_kg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"CO₂ Avoided (kg)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
context: this query calculates the CO₂ emissions avoided. In the
original statement, &lt;code&gt;CASE WHEN er_kg &amp;gt; 0 THEN er_kg ELSE 0 END&lt;/code&gt; folds
a &lt;code&gt;NULL&lt;/code&gt; &lt;code&gt;er_kg&lt;/code&gt; — i.e missing data — into 0. that's a bug. Missing
data reads as &lt;i&gt;zero emissions avoided&lt;/i&gt;, when the dashboard should show
&lt;code&gt;NULL&lt;/code&gt; or &lt;code&gt;NA&lt;/code&gt; to say that data is missing.
&lt;/p&gt;

&lt;p&gt;
Claude's bugfix wrapped the sum in &lt;code&gt;NULLIF(..., 0)&lt;/code&gt;, so a total of 0
comes back as &lt;code&gt;NULL&lt;/code&gt;. It produces the right output, buts it's a patch
on the symptom rathen than a fix of the cause..
&lt;/p&gt;

&lt;p&gt;
I looked up the &lt;a href="https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-CASE"&gt;&lt;code&gt;CASE&lt;/code&gt;&lt;/a&gt; statement:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
If the ELSE clause is omitted and no condition is true, the result is null.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Delete the &lt;code&gt;ELSE 0&lt;/code&gt; and the &lt;code&gt;CASE&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt; for the missing row
on its own; &lt;code&gt;SUM&lt;/code&gt; skips &lt;code&gt;NULL&lt;/code&gt; — the exact behavior we want. Claude
had a better fix within one deletion, but it doubled down on its idea
and instead of re-reading the statement. It took me a moment to
realize this and I kept doubting myself. I ran a bunch of queries,
confirmed things and followed up with the "requested changes" to the
bugfix PR.
&lt;/p&gt;

&lt;p&gt;
It is very easy to fall into this trap and click "Approve" on a
PR. The LLM is delivering the functionality but I should really know
my craft to be able to discern their outputs. And in the first case I
mentioned I have no clue if the implementation is bad.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org2aa597d" class="outline-2"&gt;
&lt;h2 id="org2aa597d"&gt;Concluding point:&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org2aa597d"&gt;
&lt;p&gt;
In his book &lt;i&gt;Skin in the game&lt;/i&gt;, Nassim bhai talks about the Greek
phrase "Pathemata mathemata" — learning through pain. For me the pain
is learning by debugging. With Claude and other LLM tools I have a
feeling that I am getting away from that exercise and becoming
complacent.
&lt;/p&gt;

&lt;p&gt;
I'm not disciplined; I procrastinate and I rely on 'last-minute panic'
mode.
&lt;/p&gt;


&lt;div id="org5e3e825" class="figure"&gt;
&lt;p&gt;&lt;img src="https://baali.muse-amuse.in/galleries/last-minute.png" alt="nil"&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
Again, with LLM tools, I am leaning into my worst instincts. I rely on
them to build a feature within limited time and budget, and I end up
not learning or growing. There is an existential crises dimension to
all this too, If the any and all features can be done by these LLMs,
what am I for? Do I really want that? I think I got lucky catching
Claude's mistake with the query. I can't be a mere &lt;a href="https://gruhn.me/blog/2026-08-03/"&gt;meat proxy&lt;/a&gt; and I
want to be an active human in the loop. As these tools get more
powerful, I have to stay sharp, I don't know how yet, but that's the
only way I'll survive as a programmer.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>databases</category><category>embedded</category><category>llm</category><category>python</category><category>tools</category><guid>https://baali.muse-amuse.in/posts/the-good-the-bad-the-ugly.html</guid><pubDate>Sat, 01 Aug 2026 09:55:21 GMT</pubDate></item><item><title>Collecting user feedback</title><link>https://baali.muse-amuse.in/posts/collecting-user-feedback.html</link><dc:creator>शांतनु</dc:creator><description>&lt;p&gt;
I &lt;a href="https://baali.muse-amuse.in/posts/the-right-recipe.html"&gt;love&lt;/a&gt; simple basic things that come together to deliver a feature.
&lt;/p&gt;


&lt;p&gt;
I am working on a new &lt;a href="https://takearast.com/"&gt;product&lt;/a&gt; around guided meditation. I am learning
what makes a good meditation session and applying it to create lot of
sessions on the fly (shoutout to &lt;a href="https://www.ffmpeg.org/"&gt;ffmpeg&lt;/a&gt;, it rocks). While the hardware
is coming together at its own pace and has its own challenges (maybe I
will write a separate post around it), I wanted a quick way to share
these sessions with the right people and collect their comments and
feedback.
&lt;/p&gt;

&lt;p&gt;
My requirements were clear: clean UX that allows users to tell us
their name and what they thought of the session. For users, I didn't
want them to create account, authenticate etc. For backend, I didn't
want to over-engineer it. A central place where I can see all the
comments, a notification when I get a new feedback.
&lt;/p&gt;

&lt;p&gt;
Enter: Google Forms. Here are the steps I did:
&lt;/p&gt;

&lt;ol class="org-ol"&gt;
&lt;li&gt;created a google form with the questions&lt;/li&gt;
&lt;li&gt;published it and got the public URL&lt;/li&gt;
&lt;li&gt;I used a &lt;a href="https://github.com/tienthanh214/googleform-autofill-and-submit#extract-information"&gt;tool&lt;/a&gt; to get "&lt;code&gt;entries&lt;/code&gt;" that represented my questions in the form&lt;/li&gt;
&lt;li&gt;build a modal, custom UI that was consistent with my app (&amp;lt;iframe&amp;gt;
embedding was not needed):
&lt;img src="https://baali.muse-amuse.in/galleries/feedback-ui.png" alt="nil"&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
A POST (on backend using python &lt;code&gt;requests&lt;/code&gt; or from javascript using
a &lt;code&gt;fetch&lt;/code&gt; call) request with all entries (questions) and their
values. That's it. Response is registered, a new line is added to
the linked spreadsheet
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;   const GOOGLE_FORM_URL = 'https://docs.google.com/forms/d/&amp;lt;formID&amp;gt;/formResponse';
   const ENTRY = {
     name:     'entry.1122112211',
     comment:  'entry.7766177172',
     segments: 'entry.1121121218',
     version:  'entry.178019201',
   };
   const formData = new FormData();
   formData.append(ENTRY.name,     name);
   formData.append(ENTRY.comment,  comment);
   formData.append(ENTRY.segments, segments);
   formData.append(ENTRY.version,  modal.dataset.version);

   try {
     await fetch(GOOGLE_FORM_URL, {
       method: 'POST',
       mode:   'no-cors',   // Google Forms doesn't allow CORS; submission still works
       body:   formData,
     });
&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;I enabled email notification for new responses&lt;/li&gt;
&lt;/ol&gt;

&lt;div id="org9e3cda5" class="figure"&gt;
&lt;p&gt;&lt;img src="https://baali.muse-amuse.in/galleries/feedback.png" alt="nil"&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
When I first read about it from Claude and Stackoverflow I didn't
believe it. In the past whenever I have tried to use google products,
it was a pain, setting up authentication, configuration, permissions
etc, it was always a PAIN. I thought Google would never allow a simple
&lt;code&gt;POST&lt;/code&gt; to register a response. But they do. Whenever a user leaves a
comment, I get an email alert, I get neat breakdown of all the
feedback on google forms response section
&lt;/p&gt;

&lt;p&gt;
It is such a joy to stumble across this solution that just
works. Plus, I was casually talking about it with &lt;a href="https://punchagan.muse-amuse.in/"&gt;Punch&lt;/a&gt;, it was TIL
for him too, he loved it too and that also made me happy 😊.
&lt;/p&gt;</description><category>google</category><category>project</category><category>python</category><category>spreadsheets</category><category>tools</category><guid>https://baali.muse-amuse.in/posts/collecting-user-feedback.html</guid><pubDate>Thu, 14 May 2026 03:16:53 GMT</pubDate></item><item><title>Elevating the recipe</title><link>https://baali.muse-amuse.in/posts/use-jsonb-instead.html</link><dc:creator>शांतनु</dc:creator><description>&lt;p&gt;
This is a follow-up on an earlier post: &lt;a href="https://baali.muse-amuse.in/posts/the-right-recipe.html"&gt;The right recipe&lt;/a&gt;. There I used
Github actions to push data to a google spreadsheet that would update
a looker studio dashboard.
&lt;/p&gt;

&lt;p&gt;
The data we were handling didn't scale with google spreadsheet: both
ingesting and accessing got slower. And we needed feature around data
access controls, onboarding new clients. So we migrated to Supabase (a
Postgres-based platform) and this is the new pipeline:
&lt;/p&gt;

&lt;ol class="org-ol"&gt;
&lt;li&gt;Hourly API pulls, store JSON response in a postgres table&lt;/li&gt;
&lt;li&gt;Flatten part of JSON, subset of key value pairs into another table
(&lt;a href="https://www.postgresql.org/docs/current/rules-materializedviews.html"&gt;materialized view&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A dashboard connected to this view&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Infrastructure on Supabase is managed using SQLAlchemy models, I use
Alembic for migrations, and Github actions for automation. Everything
is version controlled with separate development and production
environments.
&lt;/p&gt;

&lt;p&gt;
New pipeline, new challenges. As the data grew, refreshing the
materialized view started taking longer and longer. Github action
failed as the query to flatten the data took longer and exceeded
Supabase's timeouts. This is the query that I was running:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;json_dump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'time'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;json_dump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;energey&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;telemetry_table&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;telemetry_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json_dump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
We would adjust timeouts on Supabase roles and in a week or two the
run would fail again. With close to 35k entries, it took almost 20
seconds to refresh the view.
&lt;/p&gt;

&lt;p&gt;
At some point adjusting timeout didn't cut and I started looking into
the query (&lt;a href="https://www.postgresql.org/docs/current/sql-explain.html"&gt;explain analyze&lt;/a&gt;). Just parsing the &lt;code&gt;JSON&lt;/code&gt; column was
biggest factor. And as I extracted any field it would also increase
the execution time.
&lt;/p&gt;

&lt;p&gt;
Here is result from not including JSON column in query:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PLAN&lt;/span&gt;
&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;telemetry_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;6843&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;31629&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;014&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;774&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;31424&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Planning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;230&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="n"&gt;Execution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;178&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Here is result from using the &lt;code&gt;JSON&lt;/code&gt; column, filtering &lt;code&gt;null&lt;/code&gt; rows and
extracting fields:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PLAN&lt;/span&gt;
&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;telemetry_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;8181&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;31471&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;524&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;11074&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;447&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;26012&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;json_dump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Removed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5412&lt;/span&gt;
&lt;span class="n"&gt;Planning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;249&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="n"&gt;Execution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11079&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Regular search and llm both suggested me to use &lt;a href="https://www.postgresql.org/docs/current/datatype-json.html"&gt;&lt;code&gt;JSONB&lt;/code&gt;&lt;/a&gt; instead of
&lt;code&gt;JSON&lt;/code&gt; and GIN indexes. I created a backup table and altered the
column to &lt;code&gt;JSONB&lt;/code&gt; and that reduced the refresh time to less than 2
seconds. 10x improvement:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PLAN&lt;/span&gt;
&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;experimental_telemetry_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10696&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;43026&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;753&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;313&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;37030&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;json_dump&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'total'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Removed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6212&lt;/span&gt;
&lt;span class="n"&gt;Planning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;778&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="n"&gt;Execution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;931&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;380&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
This was good enough for us and I didn't need GIN index. I am not
filtering rows based on specific fields from the &lt;code&gt;json&lt;/code&gt; data.
&lt;/p&gt;

&lt;p&gt;
I will work on it more as we onboard new clients. We know the
limitation of existing system: despite the best efforts we still have
some manual steps for onboarding a new client. We have &lt;a href="https://supabase.com/docs/guides/database/functions"&gt;functions&lt;/a&gt; tied
to the project. The data is time-series and there are other well
suited databases specially meant for it. API response &lt;code&gt;json&lt;/code&gt; varies
for each client. Lets see how the project and solution (recipe)
evolves.
&lt;/p&gt;</description><category>analytics</category><category>data-engineering</category><category>databases</category><category>etl</category><category>python</category><guid>https://baali.muse-amuse.in/posts/use-jsonb-instead.html</guid><pubDate>Fri, 06 Feb 2026 09:57:15 GMT</pubDate></item><item><title>Readable code</title><link>https://baali.muse-amuse.in/posts/readable-code.html</link><dc:creator>शांतनु</dc:creator><description>&lt;div id="outline-container-org2343141" class="outline-2"&gt;
&lt;h2 id="org2343141"&gt;Background&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org2343141"&gt;
&lt;p&gt;
I am revisiting Clojure by &lt;a href="https://github.com/Quartz/bad-data-guide"&gt;cleaning up and handling bad-data&lt;/a&gt;. Data is
in a CSV file. There are two aspects to the problem - return clean
rows and log/find bad ones.
&lt;/p&gt;

&lt;p&gt;
This reminded me of a Clojure project I worked on in 2020 that handled
drop shipping of orders from a third-party website. We would receive
orders in CSV files, create these orders on our store, and respond
with order tracking details in CSV format. Our store API worked with
JSON data, so I had to handle the data transformation from CSV to JSON
and back.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgc18c57b" class="outline-2"&gt;
&lt;h2 id="orgc18c57b"&gt;Problem: Handle the transition of data from flat to nested form&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orgc18c57b"&gt;
&lt;p&gt;
I was learning Clojure while working on that project, and I wrote the
following function to flatten nested order data:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;single-row-per-sku&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;"Convert consolidated processed orders to rows containing each SKU"&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;merge &lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dissoc &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
It works—I wrote unit tests to make sure it did. But it is dense and
&lt;span style="color: red;"&gt;unreadable&lt;/span&gt;. I can't
even recall how I explained this section during code review.
&lt;/p&gt;

&lt;p&gt;
I used &lt;a href="https://www.braveclojure.com/introduction/"&gt;Clojure for the Brave and True&lt;/a&gt; for learning the language. It
was a fun and refreshing read on introducing core concepts. The
function that I wrote doesn't do justice to the quality of the
content.
&lt;/p&gt;

&lt;p&gt;
Brave Clojure also has this relevant quote from &lt;a href="https://www.braveclojure.com/do-things/"&gt;Alan Perlis&lt;/a&gt;:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
It is better to have 100 functions operate on one data structure than
10 functions on 10 data structures.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
I came from Python background. I also didn't know about &lt;a href="https://clojure.org/guides/threading_macros"&gt;threading
macros&lt;/a&gt; in Clojure when I wrote above function. The quote really made
sense to me once I &lt;a href="https://baali.muse-amuse.in/posts/clojure-keeping-the-code-readable.html"&gt;learned&lt;/a&gt; about threading.
&lt;/p&gt;

&lt;p&gt;
As I am revising Clojure concepts I am writing a lot of small function
and testing them using the &lt;a href="https://clojure.org/guides/repl/introduction"&gt;REPL&lt;/a&gt;. In the same vein I feel following is
more readable equivalent:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;get-address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dissoc &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;get-items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;:items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;flatten-items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get-items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;merge &lt;/span&gt;&lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;single-row-per-sku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map &lt;/span&gt;&lt;span class="nv"&gt;flatten-items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nv"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Small functions. Threading macros. Names that make sense. Easy to read
and functional. I don't think it's the final version-the &lt;a href="https://web.archive.org/web/20190112093304/http://people.cs.uchicago.edu/~wiseman/humor/large-programs.html"&gt;ballad&lt;/a&gt;
continues as I keep learning.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>clojure</category><category>discipline</category><category>etl</category><category>learning</category><category>python</category><category>tools</category><guid>https://baali.muse-amuse.in/posts/readable-code.html</guid><pubDate>Fri, 30 Jan 2026 09:22:20 GMT</pubDate></item><item><title>Getting started with powerful abstract tools: Emacs, Vim</title><link>https://baali.muse-amuse.in/posts/powerful-tools-emacs-vim-et-al.html</link><dc:creator>शांतनु</dc:creator><description>&lt;div id="outline-container-orga187f93" class="outline-2"&gt;
&lt;h2 id="orga187f93"&gt;Foundation: Text Editors&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orga187f93"&gt;
&lt;p&gt;
In his book The Dip, Seth Godin talks about snowboarding:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
Snowboarding is a hip sport. It’s fast, exciting and reasonably
priced; and it makes you look very cool. So why are there so few
snowboarders? Because learning the basic skills constitutes a
painful Dip. It takes a few days to get the hang of it, and,
during those few days, you’ll get pretty banged up. It’s easier to
quit than it is to keep going.
&lt;/p&gt;

&lt;p&gt;
The brave thing to do is to tough it out and end up on the other
side–getting all the benefits that come from scarcity. The mature
thing to do is not even to bother starting to snowboard because you’re
probably not going to make it through the Dip. And the stupid thing to
do is to start, give it your best shot, waste a lot of time and money,
and quit right in the middle of the Dip.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
In the programming world, editors are an important basic skill-they
are like the snowboard in this analogy. I would often read about vim
and emacs. In college, I tried them a couple of times and got
&lt;a href="https://stackoverflow.com/questions/11828270/how-do-i-exit-vim"&gt;stuck&lt;/a&gt;. &lt;code&gt;vim&lt;/code&gt;'s swap file prompt was scary. When &lt;code&gt;git&lt;/code&gt; opened vim's
side-by-side buffer to resolve merge conflicts, I would start
sweating.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orga343144" class="outline-2"&gt;
&lt;h2 id="orga343144"&gt;Finding the Spark&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orga343144"&gt;
&lt;p&gt;
Both &lt;a href="https://www.vim.org/"&gt;&lt;code&gt;VIM&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.gnu.org/software/emacs/"&gt;&lt;code&gt;Emacs&lt;/code&gt;&lt;/a&gt; are powerful and ubiquitous text editors and
both are infamous for their steep learning curves. On my own, picking
up such a tool was intimidating and frustrating. Here is what did the
trick for me: watch a power user use them. It really hits you when
this happens in person. I saw someone using org-mode in &lt;code&gt;emacs&lt;/code&gt;, its
table editor, exporting it in Markdown, HTML, and PDF, and then he
used it to create a presentation using &lt;a href="https://github.com/yjwen/org-reveal"&gt;org-reveal&lt;/a&gt;, I was blown away
🤯. Later another friend of mine was using &lt;code&gt;emacs&lt;/code&gt; and &lt;a href="https://www.gnu.org/software/tramp/"&gt;&lt;code&gt;tramp&lt;/code&gt;&lt;/a&gt; to make
code adjustments on a live server (avoid doing that) 🫡. During
another pairing session, my colleague was using &lt;code&gt;vim&lt;/code&gt;, navigating
code, renaming variables with visual mode, and running macros-it was
magic 🤹🏽. These moments made these editors cool for me-simple yet
very pliable and that gave me enough escape velocity to push through
the steep learning curve.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orga4fc1eb" class="outline-2"&gt;
&lt;h2 id="orga4fc1eb"&gt;The Learning Curve&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orga4fc1eb"&gt;
&lt;p&gt;
These editors are abstract tools that carry certain labels: "hard",
"for smart people only", "set in their own ways". I struggled with two
things when trying to pick them up. When I was working on a problem, I
didn't want to get distracted by my editor, its mode, buffers, opening
and closing etc. Second, I didn't consider myself smart and I kept my
distance (thinking I was being mature) from tools that carried that
impression.
&lt;/p&gt;

&lt;p&gt;
But once I realized editors were a fundamental skill, I realized I
should invest the time, don't make it (the editor) the last thing. I
kept retrying, just like how I would approach a boss fight in a
game. I didn't scare that easy in the next round, I noticed patterns,
found my corner, got comfortable and started attacking. It grew on
me. I acquired the taste. The same approach works for all
crafts. Later, as I worked on new problem, these editors didn't get in
the way-they provided the structure that will helped me focus.
&lt;/p&gt;

&lt;p&gt;
I was lucky to work with such power users, and they allowed me the
space to ask stupid questions. I still nag them about their setup when
we meet in person. I use &lt;code&gt;Emacs&lt;/code&gt;. I can't program without org-mode,
magit, tramp et al. I am slowly working my way through its LSP support
and configuring and tweaking things, one language at a time. I've
tried other editors, I kept looking for these features and so far my
attempts to install plugins/addons haven't worked. I keep returning to
&lt;code&gt;Emacs&lt;/code&gt;. As the landscape evolves with AI agents and MCP, I will
explore whether I can integrate them with &lt;code&gt;Emacs&lt;/code&gt; or if I'll need to
adopt a more contemporary editor.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>emacs</category><category>tools</category><guid>https://baali.muse-amuse.in/posts/powerful-tools-emacs-vim-et-al.html</guid><pubDate>Tue, 13 Jan 2026 15:35:00 GMT</pubDate></item><item><title>One Firmware for all ESP32 Variants: Solving Pin Mapping Pain</title><link>https://baali.muse-amuse.in/posts/untangle-the-wires.html</link><dc:creator>शांतनु</dc:creator><description>&lt;p&gt;
For my embedded project, I needed &lt;a href="https://www.nxp.com/docs/en/nxp/data-sheets/PN532_C1.pdf"&gt;NFC card&lt;/a&gt; functionality for WiFi
onboarding and user interactions. When integrating NFC functionality
into ESP32 projects, pin mapping became a major pain point as I was
working with different ESP32 boards.
&lt;/p&gt;

&lt;p&gt;
&lt;a href="https://github.com/adafruit/Adafruit-PN532/blob/master/examples/ntag2xx_read/ntag2xx_read.ino"&gt;Adafruit's arduino library example&lt;/a&gt; sets up the NFC reader in
following manner:
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Using the SPI breakout, define the pins for SPI communication.&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_SCK  (2)&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_MOSI (3)&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_SS   (4)&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_MISO (5)&lt;/span&gt;
&lt;span class="n"&gt;Adafruit_PN532&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nfc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PN532_SCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PN532_MISO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PN532_MOSI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PN532_SS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
These pins are placeholders and they would vary for each ESP32 variant
and I had several: &lt;a href="https://randomnerdtutorials.com/esp32-pinout-reference-gpios/"&gt;ESP-WROOM-32&lt;/a&gt;, &lt;a href="https://randomnerdtutorials.com/getting-started-esp32-c3-super-mini/"&gt;ESP32-C3 Super Mini&lt;/a&gt;, &lt;a href="https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-devkitc-1/user_guide_v1.1.html#hardware-reference"&gt;S3&lt;/a&gt;, &lt;a href="https://wiki.seeedstudio.com/xiao_esp32s3_getting_started/"&gt;XIAO ESP32S3&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
I tested the reader across all these variants, different modes: &lt;code&gt;SPI&lt;/code&gt;,
&lt;code&gt;I2C&lt;/code&gt;, wiring and reading different NFC cards.
&lt;/p&gt;

&lt;p&gt;
First challenge was to figure out the mapping of pins. I found that
&lt;a href="https://github.com/espressif/arduino-esp32/tree/master/variants"&gt;espressif's arduino support&lt;/a&gt; is the best resource for it. For each
board the file &lt;a href="https://github.com/espressif/arduino-esp32/blob/master/variants/uPesy_esp32c3_mini/pins_arduino.h"&gt;&lt;code&gt;pins_arduino.h&lt;/code&gt;&lt;/a&gt; gives us the right pins. For the
&lt;a href="https://randomnerdtutorials.com/getting-started-esp32-c3-super-mini/"&gt;ESP32-C3 Super Mini&lt;/a&gt; board, the above example NFC reader code would
become:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_SCK  (4)&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_MOSI (6)&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_SS   (7)&lt;/span&gt;
&lt;span class="cp"&gt;#define PN532_MISO (5)&lt;/span&gt;
&lt;span class="n"&gt;Adafruit_PN532&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nfc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PN532_SCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PN532_MISO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PN532_MOSI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PN532_SS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
But as I iterated through these ESP32 variants it became quite a pain
adjusting those pins in the firmware and flashing it. Eventually I
realized what would simplify this: I could have a single firmware that
will work with everything:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Adafruit_PN532&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;nfc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SCK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MISO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MOSI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
These pins, &lt;code&gt;SCK&lt;/code&gt;, &lt;code&gt;MISO&lt;/code&gt; are just like another constant:
&lt;a href="https://docs.arduino.cc/language-reference/en/variables/constants/ledbuiltin/"&gt;&lt;code&gt;LED_BUILTIN&lt;/code&gt;&lt;/a&gt;, each Arduino &lt;a href="https://baali.muse-amuse.in/posts/esp32-zephyr-and-blinky.html"&gt;board&lt;/a&gt; that doesn't use default pin &lt;code&gt;13&lt;/code&gt;,
defines it in their &lt;code&gt;pins_arduino.h&lt;/code&gt; header. Similarly all the
variants have their own respective &lt;code&gt;pins_arduino.h&lt;/code&gt; file with right
pins. As I select the board inside Arduino IDE, these variables are
always available. Further this pattern isn't limited to NFC readers,
all the SPI peripheral using those pins would work.
&lt;/p&gt;</description><category>arduino</category><category>diy</category><category>embedded</category><category>esp32</category><category>esp-idf</category><category>FOSS</category><category>iot</category><category>zephyr</category><guid>https://baali.muse-amuse.in/posts/untangle-the-wires.html</guid><pubDate>Sat, 16 Aug 2025 05:07:10 GMT</pubDate></item><item><title>CPR for a Docker container</title><link>https://baali.muse-amuse.in/posts/cpr-for-a-docker-container.html</link><dc:creator>शांतनु</dc:creator><description>&lt;div id="outline-container-org7289c63" class="outline-2"&gt;
&lt;h2 id="org7289c63"&gt;Background&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org7289c63"&gt;
&lt;p&gt;
Picture this: You're experimenting with a Docker container, enable a
feature, and suddenly your container is stuck in an endless restart
loop. In a panic, you run &lt;code&gt;docker compose down&lt;/code&gt; and watch everything
disappear.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgf3c53f0" class="outline-2"&gt;
&lt;h2 id="orgf3c53f0"&gt;What You Should Do Instead&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orgf3c53f0"&gt;
&lt;p&gt;
&lt;b&gt;&lt;b&gt;Don't lose the container&lt;/b&gt;&lt;/b&gt;, no matter how broken it is. Create a new
image from the container using &lt;code&gt;docker&lt;/code&gt; commit: &lt;code&gt;docker commit
&amp;lt;container-id&amp;gt; broken-image&lt;/code&gt; (you get container id using &lt;code&gt;docker ps
-a&lt;/code&gt; command)
&lt;/p&gt;

&lt;p&gt;
From here there are a couple of things you could do. I was using
&lt;code&gt;Dockerfile&lt;/code&gt; to build and setup things. Modify it to use broken image
as base and install the missing dependencies:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;FROM broken-image:latest
RUN pip install &amp;lt;package-name&amp;gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
And with this in place I can restart the &lt;code&gt;docker compose&lt;/code&gt; stack.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org763f826" class="outline-2"&gt;
&lt;h2 id="org763f826"&gt;Complicated changes&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org763f826"&gt;
&lt;p&gt;
In case damage to your container is more severe there will be
additional steps to revive the container.
&lt;/p&gt;
&lt;ol class="org-ol"&gt;
&lt;li&gt;Create image from your broken container: &lt;code&gt;docker commit &amp;lt;container-id&amp;gt; broken-image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a new container from this broken image with a different
&lt;code&gt;entrypoint&lt;/code&gt; command (default command would restart the broken stack):
&lt;code&gt;docker run -it --entrypoint=/bin/bash broken-image:latest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Adjust or remove your changes, install missing dependencies, run
migrations, what have you&lt;/li&gt;
&lt;li&gt;Create a new image from this container that has all the fixes:
&lt;code&gt;docker commit &amp;lt;container-id-with-bash-running&amp;gt; fixed-image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;
Start a new container using this &lt;code&gt;fixed-image&lt;/code&gt; and default command
that brings up your original stack. In my case I removed &lt;code&gt;build&lt;/code&gt;
step from my &lt;code&gt;docker-compose.yml&lt;/code&gt; file and pointed it to the new
image:
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;   app:
     image: fixed-image:latest
&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Be careful with running faulty containers. I was really confused
balancing &lt;code&gt;docker compose&lt;/code&gt; commands and direct &lt;code&gt;docker&lt;/code&gt; commands. I
noticed that &lt;code&gt;docker compose&lt;/code&gt; (re)start command would also (re)start
all broken/existing containers and I had to manually remove them to
be able to get the complete stack running again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
With this approach I was no longer building a new Docker
image. Instead I used &lt;code&gt;docker compose&lt;/code&gt; to start container using
&lt;code&gt;fixed-image&lt;/code&gt; and default command.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org92dbe5b" class="outline-2"&gt;
&lt;h2 id="org92dbe5b"&gt;Summary:&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org92dbe5b"&gt;
&lt;ul class="org-ul"&gt;
&lt;li&gt;Always test changes in development first&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;docker commit&lt;/code&gt; with your container before making risky changes&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>debugging</category><category>docker</category><category>tools</category><guid>https://baali.muse-amuse.in/posts/cpr-for-a-docker-container.html</guid><pubDate>Sat, 14 Jun 2025 10:35:55 GMT</pubDate></item><item><title>The right recipe</title><link>https://baali.muse-amuse.in/posts/the-right-recipe.html</link><dc:creator>शांतनु</dc:creator><description>&lt;p&gt;
A colleague reached out to me for a pairing session on his &lt;a href="https://www.carbon2h.com/"&gt;project&lt;/a&gt;. I
explained upfront that my hourly rate far exceeds most LLM
subscription costs. He understood — while LLMs have their place, he
needed help clarifying requirements and making real progress.
&lt;/p&gt;

&lt;p&gt;
His existing setup: several machines pushing data to cloud, a &lt;a href="https://lookerstudio.google.com/gallery"&gt;looker
studio dashboard&lt;/a&gt; connected to a placeholder google spreadsheet. To
start, he wanted to make this dashboard live.
&lt;/p&gt;

&lt;p&gt;
His requirements:
&lt;/p&gt;
&lt;ol class="org-ol"&gt;
&lt;li&gt;A live dashboard&lt;/li&gt;
&lt;li&gt;Python based: his comfort zone&lt;/li&gt;
&lt;li&gt;Daily data pulls: sufficient for non-temporal data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
The solution was obvious, I have used it &lt;a href="https://github.com/baali/nps-d"&gt;before&lt;/a&gt;: A GitHub action that
runs daily, pulls cloud data and updates his spreadsheet — instantly
making his dashboard live. I understand that I am contributing to the
Github actions phenomena:
&lt;/p&gt;


&lt;div id="org889fe46" class="figure"&gt;
&lt;p&gt;&lt;img src="https://baali.muse-amuse.in/galleries/actions.jpg" alt="nil"&gt;
&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;
The trickiest parts? Wrestling with Google's Sheets API authentication
and configuring GitHub &lt;a href="https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-secrets-in-a-workflow"&gt;secrets&lt;/a&gt; properly. But once those pieces
clicked, everything flowed. Four one hour focused pairing sessions,
that's it. He got a working solution plus he understood all the moving
parts to continue experimenting on his own.
&lt;/p&gt;

&lt;p&gt;
Four hours, serverless, an elegant solution. This is what going back
to first principles looks like.
&lt;/p&gt;</description><category>project</category><category>python</category><category>tools</category><guid>https://baali.muse-amuse.in/posts/the-right-recipe.html</guid><pubDate>Fri, 30 May 2025 08:03:09 GMT</pubDate></item><item><title>Setting Up Zephyr for ESP32: A Blinky LED Journey</title><link>https://baali.muse-amuse.in/posts/esp32-zephyr-and-blinky.html</link><dc:creator>शांतनु</dc:creator><description>&lt;p&gt;
I recently started working with a client on an &lt;a href="https://docs.zephyrproject.org/latest/boards/espressif/esp32_devkitc_wroom/doc/index.html"&gt;ESP32&lt;/a&gt; based
project. Having prior experience with Zephyr on Nordic boards, I
decided to leverage Zephyr's Espressif support for this project. While
the documentation mentions the classic "blinky" example, I noticed it
lacked the necessary device overlay file for my specific board.
&lt;/p&gt;

&lt;div id="outline-container-org1a9d33e" class="outline-2"&gt;
&lt;h2 id="org1a9d33e"&gt;Finding the Right Example&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org1a9d33e"&gt;
&lt;p&gt;
Fortunately, I discovered that the &lt;a href="https://docs.zephyrproject.org/latest/samples/basic/blinky_pwm/README.html"&gt;"PWM Blinky"&lt;/a&gt; example included
sample overlay files I could reference. After compiling the example, I
encountered this error when attempting to flash the device:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;([Errno 5] could not open port /dev/ttyUSB0: [Errno 5] Input/output error: '/dev/ttyUSB0')
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Running stat &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; revealed that the device belonged to the
&lt;code&gt;dialout&lt;/code&gt; group. I fixed the permission issue by adding my user to
this group:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo usermod -a -G dialout $USER
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
After rebooting to apply the new permissions, &lt;code&gt;west flash&lt;/code&gt; worked perfectly.
&lt;/p&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgb5ee3da" class="outline-3"&gt;
&lt;h3 id="orgb5ee3da"&gt;Creating the missing overlay file&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-orgb5ee3da"&gt;
&lt;p&gt;
The basic &lt;a href="https://docs.zephyrproject.org/latest/samples/basic/blinky/README.html"&gt;Blinky&lt;/a&gt; documentation explains the need for an overlay file
that points to the correct GPIO pin. Based on the PWM Blinky example,
I discovered my board (&lt;code&gt;esp32_devkitc_wroom&lt;/code&gt;) has an onboard blue LED
connected to &lt;code&gt;pwm_led_gpio0_2&lt;/code&gt;. I created following overlay file:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/ {
    aliases {
        led0 = &amp;amp;myled0;
    };
    leds {
        compatible = "gpio-leds";
        myled0: led_0 {
            gpios = &amp;lt;&amp;amp;gpio0 2 GPIO_ACTIVE_LOW&amp;gt;;
        };
    };
};
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgd907882" class="outline-3"&gt;
&lt;h3 id="orgd907882"&gt;Success&lt;/h3&gt;
&lt;div class="outline-text-3" id="text-orgd907882"&gt;
&lt;p&gt;
With the overlay in place(&lt;code&gt;samples/basic/blinky/boards/esp32_devkitc_wroom_procpu.overlay&lt;/code&gt;),
I compiled the project and it worked:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;west build -p always -b esp32_devkitc_wroom/esp32/procpu samples/basic/blinky
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
After flashing the board, there it was, blinking blue LED - a small
victory that is still the first step and I keep revisiting it.
&lt;/p&gt;

&lt;p&gt;
Next up: implementing the project's actual functionality, including
WiFi provisioning, ePaper display integration, and LED ring controls.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>arduino</category><category>esp32</category><category>iot</category><category>zephyr</category><guid>https://baali.muse-amuse.in/posts/esp32-zephyr-and-blinky.html</guid><pubDate>Tue, 08 Apr 2025 11:49:10 GMT</pubDate></item><item><title>Resolution / सलटारा </title><link>https://baali.muse-amuse.in/posts/resolution.html</link><dc:creator>शांतनु</dc:creator><description>&lt;p&gt;
Continuing from the &lt;a href="https://baali.muse-amuse.in/posts/assumptions.html"&gt;last post&lt;/a&gt;. The fact that Python interpreter didn't
catch the regex pattern in tests but threw &lt;code&gt;compile&lt;/code&gt; error on staging
environment was very unsettling. Personally I knew that I am missing
something on my part and I was very reluctant on blaming the language
itself. Turns out, I was correct 🙈.
&lt;/p&gt;


&lt;p&gt;
I was looking for early feedback and comments on the post and &lt;a href="https://github.com/punchagan"&gt;Punch&lt;/a&gt;
was totally miffed by this inconsistency and specially the conclusion
I was coming to
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
Umm, I'm not sure I'm happy with the suggestion that I should check
every regex I write, with an external website tool to make sure the
regex itself is a valid one. "Python interpreter ka kaam main karoon
abhi?" :frown:
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
He quickly compared the behavior between JS(compiler complianed) and
Python(did nothing). Now that I had full attention from him there was
no more revisiting this at some later stage. We started digging. He
confirmed that the regex failed with &lt;code&gt;Python2&lt;/code&gt; but not with
&lt;code&gt;Python3.11&lt;/code&gt; with a simple command
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker run -it python:3.11 python -c 'import re; p = r"\s*+"; re.compile(p); print("yo")'
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
I followed up on this and that was the mistake on my part. My local
python setup was &lt;code&gt;3.11&lt;/code&gt;, I was using this to run my tests and staging
environment was using &lt;code&gt;3.10&lt;/code&gt;. When I ran my tests within containerized
setup, similar to what we used on staging, Python interpreter rightly
caught faulty regex:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;re.error: multiple repeat at position 11
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Something changed between python &lt;code&gt;3.11&lt;/code&gt; and &lt;code&gt;3.10&lt;/code&gt; release. I looked
at the &lt;a href="https://www.python.org/downloads/release/python-3110/"&gt;release logs&lt;/a&gt; and noticed this new feature &lt;a href="https://github.com/python/cpython/pull/31982"&gt;atomic grouping&lt;/a&gt; and
possessive quantifier. I am not sure how to use it or what it does
&lt;a href="https://learnbyexample.github.io/python-regex-possessive-quantifier/"&gt;1&lt;/a&gt;. But with this feature, python &lt;code&gt;3.11&lt;/code&gt; regex pattern: &lt;code&gt;r"\s*+"&lt;/code&gt; is a
valid one. I was using this to run tests locally. On staging we had
python &lt;code&gt;3.10&lt;/code&gt;, and with it interpreter threw an error.
&lt;/p&gt;

&lt;p&gt;
&lt;b&gt;Lesson learned&lt;/b&gt;:
&lt;/p&gt;

&lt;p&gt;
I was testing things by running things locally. Don't do that. Setup a
stack and get is as close to the staging and production as
possible. And always, always run tests within it.
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
You pull an end of a mingled yarn,
&lt;/p&gt;

&lt;p&gt;
to sort it out,
&lt;/p&gt;

&lt;p&gt;
looking for a resolution but there is none,
&lt;/p&gt;

&lt;p&gt;
it is just layers and layers.
&lt;/p&gt;
&lt;/blockquote&gt;</description><category>docker</category><category>learning</category><category>python</category><category>regex</category><category>tests</category><guid>https://baali.muse-amuse.in/posts/resolution.html</guid><pubDate>Fri, 08 Mar 2024 10:11:47 GMT</pubDate></item></channel></rss>