凌晨 3 點無人記錄的警報:為商店建立事故問責系統
The 3am alarm nobody logged: building incident accountability for shops
作者為商店建立本地事故問責系統,把警報影片、觸發時間及關閉人員整合記錄到同一資料列。系統以 SQLite 和本地影片檔保存資料,並透過 Ollama 在本機運行 qwen2.5-coder:7b 生成摘要;資料不會離開店舖,毋須帳戶、註冊或 API key。目前只有終端機報告,沒有手機程式或警報功能,關閉人員由人手輸入。
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.
That is the problem I built for this week.
The three facts that never live in the same place
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:
- The chiller alarm fires. Nobody hears it at 3am.
- At 7am someone notices the temperature log. They remember there was an alarm.
- 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.
- They walk the floor and ask. "Did you close it?" "Yeah, I think that was me."
- That conversation is now the record. In someone's memory. Until next Tuesday.
So the shop owns three separate things: video on a recorder nobody scrubs, a name in a conversation, and a time that nobody wrote down.
Every food-safety audit I have sat through turns on the same question: who closed it, and when? And the honest answer in most shops is a shrug with a name attached.
What I built
An incident accountability system. It makes those three facts one row in a database.
alarm fires
|
v
open -> cut the video clip of the event (proof)
record the time it fired (the clock starts)
|
v
close -> record WHO closed it (the accountable person)
record WHEN (how long it ran)
generate a one-line summary (what a manager reads)
That is it. It is small on purpose.
Run it
python3 incidents.py init
python3 incidents.py open \
--camera chiller-1 \
--reason "temperature above 8C" \
--clip-from /tmp/fake-camera.mp4 \
--clip-seconds 8
python3 incidents.py close \
--id 2 --by "Staff A" \
--note "door resealed, stock checked"
python3 incidents.py report
--clip-from takes anything ffmpeg reads: an rtsp:// 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.
The report, unedited
I ran two incidents, closed one, and left the other open. This is the actual terminal output:
======================================================================
INCIDENT REPORT 2026-10-14
======================================================================
STILL OPEN: 1 CLOSED: 1
── NEEDS ATTENTION ──
#1 chiller-1 temperature above 8C
open since 2026-10-14T23:35:14+00:00 (0h 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.
NOT CLOSED BY ANYONE 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.
And each row points at a real 8-second and 5-second clip on disk, not a timestamp to go hunting for:
-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
Why it runs locally, and why that is not a detail
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 open-weight model running locally through Ollama — qwen2.5-coder:7b, Apache 2.0, 4.7 GB, on the box in the back office.
That is not a technical preference. It is the reason a small shop can use this at all.
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.
The local model writes the summary in one line, warm in about twelve seconds:
Fridge door ajar at deli-2 for 0 minutes; closed by Staff A; door resealed, stock checked.
If the model is down, the incident still saves. It falls back to a deterministic template. The record matters more than the prose — a missing summary I can regenerate, a missing incident row I cannot.
What I got wrong along the way
Two things worth writing down, because they cost me time:
The model call timed out on the first run. I had a 90-second timeout. The first call of the day loads a 4.7 GB model off disk, which on this 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.
The demo names. 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 Staff A and Staff B only. If your demo data can ever escape into a post, it is production data.
What it does not do
- No mobile app. The report is a terminal command. A shop owner is not going to SSH anywhere, so this is the gap between "works" and "sellable".
- No alerting. It records that nobody closed the alarm. It does not yet chase anybody, which is arguably the more valuable half.
- No face or badge recognition. 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.
Why this is a side hustle and not a toy
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 "chiller-1, 3:12am, open 47 minutes, closed by nobody" in a form an auditor will accept.
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.
I built it because I have sat in the meeting where this question was asked and watched three people guess.
Code: incidents.py, one file, stdlib plus ffmpeg. Open-weight model via Ollama.
來源:DEV Community · dev.to