<?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: Maui N</title>
    <description>The latest articles on DEV Community by Maui N (@mauitechhub).</description>
    <link>https://dev.to/mauitechhub</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%2F4185517%2F2ebc66ed-baba-468e-bb12-4deb4e4b95d8.jpg</url>
      <title>DEV Community: Maui N</title>
      <link>https://dev.to/mauitechhub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mauitechhub"/>
    <language>en</language>
    <item>
      <title>The 3am alarm nobody logged: building incident accountability for shops</title>
      <dc:creator>Maui N</dc:creator>
      <pubDate>Sat, 17 Oct 2026 03:18:01 +0000</pubDate>
      <link>https://dev.to/mauitechhub/the-3am-alarm-nobody-logged-building-incident-accountability-for-shops-42c4</link>
      <guid>https://dev.to/mauitechhub/the-3am-alarm-nobody-logged-building-incident-accountability-for-shops-42c4</guid>
      <description>&lt;p&gt;The alarm goes off at 3am. By the morning, three people can tell you three different things about what happened, and none of them can show you.&lt;/p&gt;

&lt;p&gt;That is the problem I built for this week.&lt;/p&gt;

&lt;h1&gt;
  
  
  The three facts that never live in the same place
&lt;/h1&gt;

&lt;p&gt;I spent twenty years in supermarket operations, most of it as a COO. Here is how an incident actually gets handled in a shop that has cameras:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The chiller alarm fires. Nobody hears it at 3am.&lt;/li&gt;
&lt;li&gt;At 7am someone notices the temperature log. They remember there was an alarm.&lt;/li&gt;
&lt;li&gt;They go to the DVR. Scrubbing back to 3am on a 16-channel recorder takes fifteen minutes if you know the machine and the timeline.&lt;/li&gt;
&lt;li&gt;They walk the floor and ask. "Did you close it?" "Yeah, I think that was me."&lt;/li&gt;
&lt;li&gt;That conversation is now the record. In someone's memory. Until next Tuesday.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the shop owns three separate things: &lt;strong&gt;video&lt;/strong&gt; on a recorder nobody scrubs, &lt;strong&gt;a name&lt;/strong&gt; in a conversation, and &lt;strong&gt;a time&lt;/strong&gt; that nobody wrote down.&lt;/p&gt;

&lt;p&gt;Every food-safety audit I have sat through turns on the same question: &lt;em&gt;who closed it, and when?&lt;/em&gt; And the honest answer in most shops is a shrug with a name attached.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I built
&lt;/h1&gt;

&lt;p&gt;An incident accountability system. It makes those three facts one row in a database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alarm fires
    |
    v
open  -&amp;gt;  cut the video clip of the event        (proof)
          record the time it fired                (the clock starts)
    |
    v
close -&amp;gt;  record WHO closed it                   (the accountable person)
          record WHEN                             (how long it ran)
          generate a one-line summary             (what a manager reads)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. It is small on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 incidents.py init

python3 incidents.py open &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--camera&lt;/span&gt; chiller-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reason&lt;/span&gt; &lt;span class="s2"&gt;"temperature above 8C"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clip-from&lt;/span&gt; /tmp/fake-camera.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clip-seconds&lt;/span&gt; 8

python3 incidents.py close &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--id&lt;/span&gt; 2 &lt;span class="nt"&gt;--by&lt;/span&gt; &lt;span class="s2"&gt;"Staff A"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--note&lt;/span&gt; &lt;span class="s2"&gt;"door resealed, stock checked"&lt;/span&gt;

python3 incidents.py report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--clip-from&lt;/code&gt; takes anything ffmpeg reads: an &lt;code&gt;rtsp://&lt;/code&gt; URL from a real camera, or a file. For the demo I cut from a generated test source, because I do not have a Dahua on my desk.&lt;/p&gt;

&lt;h1&gt;
  
  
  The report, unedited
&lt;/h1&gt;

&lt;p&gt;I ran two incidents, closed one, and left the other open. This is the actual terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;======================================================================
  INCIDENT REPORT            2026-10-17
======================================================================

  STILL OPEN: 1      CLOSED: 1

  ── NEEDS ATTENTION ──
    #1   chiller-1    temperature above 8C
         open since 2026-10-14T23:35:14+00:00  (51h ago)  NOT CLOSED BY ANYONE
         evidence: /home/ubuntu/workspace/hf26-week2/clips/incident-0001.mp4

  ── CLOSED, with who and when ──
    #2   deli-2          0 min   by Staff A      23:35
         evidence: /home/ubuntu/workspace/hf26-week2/clips/incident-0002.mp4
         Fridge door ajar at deli-2 for 0 minutes; closed by Staff A; door
         resealed, stock checked.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;NOT CLOSED BY ANYONE&lt;/code&gt; is the line that matters. Everything else in that report is bookkeeping. That one line is the thing a shop owner cannot currently get without walking the floor and asking.&lt;/p&gt;

&lt;p&gt;And each row points at a real 8-second and 5-second clip on disk, not a timestamp to go hunting for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rw-r--r-- 1 ubuntu ubuntu 545953 Oct 14 23:35 incident-0001.mp4
-rw-r--r-- 1 ubuntu ubuntu 343248 Oct 14 23:35 incident-0002.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Why it runs locally, and why that is not a detail
&lt;/h1&gt;

&lt;p&gt;The whole thing is a SQLite file and a folder of clips on the store's own machine. The summary line is generated by an &lt;strong&gt;open-weight model running locally&lt;/strong&gt; through Ollama — &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt;, Apache 2.0, 4.7 GB, on the box in the back office.&lt;/p&gt;

&lt;p&gt;That is not a technical preference. It is the reason a small shop can use this at all.&lt;/p&gt;

&lt;p&gt;Footage of your staff and a log of who did and did not close the chiller alarm is a document about identifiable people. Putting that in a vendor's cloud is a decision a shop owner should not have to make to get a temperature report. So I did not make them make it. Nothing leaves the building. There is no account, no signup, no API key.&lt;/p&gt;

&lt;p&gt;The local model is the default. There is also an optional hosted fallback (&lt;code&gt;INCIDENT_MODEL_MODE=api&lt;/code&gt;) for a deployment that would rather not run a model at all — one line in the environment, and the same code path serves both. I built it that way because "you must run a 4.7 GB model" is a real objection from a shop with one old PC, and an architecture that can only answer it with a shrug is not finished.&lt;/p&gt;

&lt;p&gt;The local model writes the summary in one line, warm in about twelve seconds:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fridge door ajar at deli-2 for 0 minutes; closed by Staff A; door resealed, stock checked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the model is down, the incident still saves. It falls back to a deterministic template. &lt;strong&gt;The record matters more than the prose&lt;/strong&gt; — a missing summary I can regenerate, a missing incident row I cannot.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I got wrong along the way
&lt;/h1&gt;

&lt;p&gt;Two things worth writing down, because they cost me time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model call timed out on the first run.&lt;/strong&gt; I had a 90-second timeout. The first call of the day loads a 4.7 GB model off disk, which on a small ARM box takes well over 90 seconds. Warm, the identical call is about 12 seconds. The fallback caught it and the incident saved correctly, which is the only reason it was a surprise and not a data loss. The timeout is 240s now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I also put the model on the wrong machine at first.&lt;/strong&gt; I ran it on a small always-on VM with 11 GB of RAM. Loading a 4.7 GB model there filled the box and froze it, which took a storefront down with it. The lesson is not "local models are bad" — it is that local inference belongs on the machine that owns the data and has the memory for it. That is the shop's back-office box, or a box you sized for it deliberately. Not the smallest thing you happen to have running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The demo names.&lt;/strong&gt; I had real staff names from my own records sitting in a seed file next to placeholder names. That is a trap: a screenshot in a blog post, and a fabricated name beside a real one reads as a real record. The demo uses &lt;code&gt;Staff A&lt;/code&gt; and &lt;code&gt;Staff B&lt;/code&gt; only. If your demo data can ever escape into a post, it is production data.&lt;/p&gt;

&lt;h1&gt;
  
  
  What it does not do
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No mobile app.&lt;/strong&gt; The report is a terminal command. A shop owner is not going to SSH anywhere, so this is the gap between "works" and "sellable".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No alerting.&lt;/strong&gt; It records that nobody closed the alarm. It does not yet chase anybody, which is arguably the more valuable half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No face or badge recognition.&lt;/strong&gt; It records who closed it because a human typed their name. That is a trust model, not a verification model, and I am not going to blur those together.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why this is a side hustle and not a toy
&lt;/h1&gt;

&lt;p&gt;Count the shops on your street with a walk-in chiller, a DVR, and no incident log. It is most of them. They already have the camera. They already have the alarm. What they do not have is the sentence &lt;em&gt;"chiller-1, 3:12am, open 47 minutes, closed by nobody"&lt;/em&gt; in a form an auditor will accept.&lt;/p&gt;

&lt;p&gt;The install is a script and a folder. There is no monthly infrastructure cost, which means there is no reason to charge monthly. That is the business: a one-off setup, not a subscription.&lt;/p&gt;

&lt;p&gt;I built it because I have sat in the meeting where this question was asked and watched three people guess.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code: &lt;code&gt;incidents.py&lt;/code&gt;, one file, stdlib plus ffmpeg. Open-weight model via Ollama.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built 18 free n8n workflows. Here is the one that saved me the most time.</title>
      <dc:creator>Maui N</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:16:26 +0000</pubDate>
      <link>https://dev.to/mauitechhub/i-built-18-free-n8n-workflows-here-is-the-one-that-saved-me-the-most-time-ph3</link>
      <guid>https://dev.to/mauitechhub/i-built-18-free-n8n-workflows-here-is-the-one-that-saved-me-the-most-time-ph3</guid>
      <description>&lt;h1&gt;
  
  
  I built 18 free n8n workflows. Here is the one that saved me the most time.
&lt;/h1&gt;

&lt;p&gt;Most automation advice is written for people who already have their stack sorted. This is&lt;br&gt;
not that. This is the specific workflow I reach for first, and why it survives contact with&lt;br&gt;
real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every automation project dies at the same place: the source data is messier than the demo&lt;br&gt;
assumed. A CSV with three date formats. A webhook that sends a string where you expected a&lt;br&gt;
number. An API that returns 200 with an error body inside it.&lt;/p&gt;

&lt;p&gt;You can either write defensive parsing everywhere, which is unmaintainable, or normalise&lt;br&gt;
once at the edge, which is what actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape that works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook / Cron trigger
        |
        v
  Normalise node      &amp;lt;- the only node that knows your data is ugly
        |
        v
  Validate + branch   &amp;lt;- bad rows go to a side branch, not a crash
        |
        v
  Do the work
        |
        v
  Log the outcome     &amp;lt;- so the next failure is diagnosable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things in that diagram are the whole lesson:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One normalise step, at the edge.&lt;/strong&gt; Not scattered through the flow. It takes whatever&lt;br&gt;
arrived and emits a known shape. Everything downstream can then assume that shape and stay&lt;br&gt;
readable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad data goes sideways, not down.&lt;/strong&gt; A branch that catches invalid rows and writes them&lt;br&gt;
somewhere you can look at turns a silent data loss into a list you can fix. The failure&lt;br&gt;
mode you want is a row in a table, not a workflow that quietly processed 980 of 1,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log the outcome.&lt;/strong&gt; One row per run: timestamp, count in, count out, count rejected. When&lt;br&gt;
something looks wrong a month later, that table answers the question in seconds instead of&lt;br&gt;
an afternoon of re-running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Two things, both learned the hard way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not build the alerting first.&lt;/strong&gt; I have watched people spend a week on a notification&lt;br&gt;
stack before the workflow has processed a single real record. Get the happy path working on&lt;br&gt;
real data, then add alerts for the failure you have actually seen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not over-abstract early.&lt;/strong&gt; A sub-workflow that handles four cases you have not&lt;br&gt;
encountered is a guess. Duplicate twice, then extract when the third caller appears and you&lt;br&gt;
can see what the real interface is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflows themselves
&lt;/h2&gt;

&lt;p&gt;There are 18 of them, free, no signup: &lt;a href="https://pdf.mauitechhub.com/workflows/" rel="noopener noreferrer"&gt;pdf.mauitechhub.com/workflows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They cover the boring parts that are genuinely annoying to rebuild - parsing, normalising,&lt;br&gt;
retrying, logging, deduplicating. Import them, read them, change them. That is what they are&lt;br&gt;
for.&lt;/p&gt;

&lt;p&gt;If you hit one that does not do what its description says, tell me. That is a bug and I want&lt;br&gt;
to know.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
