This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
CaseJog is a local semantic memory for an independent appliance repair tech. You type the symptom in front of you, optionally pick the appliance you're actually holding, and it ranks your own past jobs that match: root cause, parts, labor time, price, and a source citation on every card. The whole thing runs on the bench. The case history never leaves the machine.
Who pays for this? The people who already pay for the adjacent thing. A one-person repair shop spends $29 to $80+ a month on tools like RepairDesk or an iFixit Pro subscription. Those are CRMs and manual libraries. None of them does the one trick that matters most once you've been on the bench a few years: finding your own past fix when the new customer describes it with completely different words.
That gap is the product. Picture a composite tech, call him Marcus, eleven years in, a notebook instead of a CRM. A microwave comes in, "door won't lock, it just clicks." Marcus burns forty minutes on the latch before he remembers he solved this exact fault a year ago on another brand, written down as "won't start with door shut." Same dead door-switch interlock. Different words. He'd already earned that fix once and paid for it again in lost time. (Marcus is a stand-in, not a real quoted user. I didn't have a technician to hand this to inside the window, so I'm naming that instead of inventing a testimonial.)
Keyword search can't rescue him, because the words never line up. "Won't spin," "drum stuck," and "no agitation" are one fault written three ways. What a tech needs is not a smarter diagnosis. It's his own past cases, ranked by how close they sit to the thing on the bench, and nothing more clever than that.
Demo
Live demo: casejog.onrender.com. Type a symptom, pick the appliance, and it ranks the real corpus live. MongoDB Atlas vector search runs on the backend; the open embedding model runs in the container. The first query after the service has been idle pays a one-time model-load cost. After that it is a couple of seconds against the free shared cluster.
The hero query is the one that started it. The tech is holding a microwave and types the door-lock symptom. Here is CaseJog ranking his own past jobs for it, each card showing the root cause, parts, labor, price, and a source case:
Here is what the dumb baseline (pure cosine, no idea what appliance is in his hand) actually returns:
== BASELINE (pure cosine, no filter) ==
0.805 JOB-0038 washing machine Door lock mechanism failed
0.800 JOB-0028 washing machine Door lock mechanism failed
0.799 JOB-0029 washing machine Door lock mechanism failed
0.794 JOB-0033 washing machine Door lock mechanism failed
0.788 JOB-0032 washing machine Door lock mechanism failed
Five washing machines. All wrong. "Door won't lock" is a louder phrase in the washer cases, so cosine drowns in them. Now the same query with the appliance filter fused into the search, the real API response, trimmed:
engine: filtered-hybrid count: 5
0.864 JOB-0218 microwave Door switch interlock faulty [cite JOB-0218 2025-02-23 $75]
0.863 JOB-0212 microwave Door switch interlock faulty [cite JOB-0212 2024-05-23 $75]
0.855 JOB-0215 microwave Door switch interlock faulty [cite JOB-0215 2025-08-04 $75]
0.852 JOB-0223 microwave Door switch interlock faulty [cite JOB-0223 2023-04-08 $75]
0.851 JOB-0219 microwave Door switch interlock faulty [cite JOB-0219 2024-02-01 $75]
Five microwave door-switch cases, each pointing at a real job with the price charged. This is the top-k the flat cosine loop cannot produce, because it has no notion of the appliance on the bench. Steady-state retrieval is 72ms (the first request pays a one-time model-load cost). The filter is the difference between the right shelf and the wrong one.
The exact-signal half of the hybrid shows up when an error code is in play. "fridge not cold, code 5E," filtered to fridge:
final=1.024 vec=0.874 boost=0.150 JOB-0098 code=5E Defrost heater failure, evaporator iced over
final=1.022 vec=0.872 boost=0.150 JOB-0101 code=5E Defrost heater failure, evaporator iced over
final=1.018 vec=0.868 boost=0.150 JOB-0102 code=5E Defrost heater failure, evaporator iced over
The boost=0.150 column is an exact 5E match firing on top of vector similarity. Meaning plus exact signal, which is what keeps the right case on top when a near-but-wrong case is just as close in plain language.
Did it beat the dumb baseline? (held-out, run once)
I built the pure-cosine baseline first and only kept the heavy path because it won. precision@k over 28 held-out symptoms, worded so they do not appear in the corpus, run once, never tuned:
k filtered-hybrid baseline-cosine
--- ---------------- ----------------
1 0.893 0.857
3 0.869 0.738
5 0.814 0.657
Filtered-hybrid wins at every k, and the gap grows as it reaches deeper: by k=5 the baseline has drifted onto cases close in meaning but wrong in cause, while the filter and the error-code boost hold the line. With n=28 the margins are suggestive, not a p-value, and I won't pretend otherwise. The direction is consistent and the reason is legible.
Code
CaseJog
A 100% local semantic memory for an independent appliance / small-electronics repair technician. Type a new symptom and instantly get a ranked list of your own past jobs that match — each with root cause, parts, time, and price — so you recall a fix you have seen before instead of re-diagnosing from scratch.
Output is always framed as your past cases, ranked — a recall aid a human verifies, never an autonomous diagnosis. Every result cites its source case (id, date, price): grounded recall.
Why local / open (the open-vs-closed thesis)
The private case corpus is the technician's hard-won edge and never leaves the
bench. Local open embeddings (fastembed, ONNX, CPU) make every query
$0, offline, and private. The embedding model is swappable in one place
(embed.py / the EMBED_MODEL env var) — the open-weight model is the mechanic
and it is replaceable…
How I Built It
symptom -> embed.py (fastembed, local) -> query vector
|
+-----------------------------------+
v v
baseline.py search.py
pure cosine, no filter Atlas $vectorSearch (store.py)
the dumb baseline + fallback + metadata filter inside the stage
+ hybrid re-rank (error_code + recency)
+ a source citation on every result
+----------------+------------------+
v
app.py / static/index.html
The open-source AI at the core is an open-weight embedding model, BAAI/bge-small-en-v1.5, run locally on CPU through fastembed on ONNX. The retrieval engine is MongoDB Atlas vector search. The discipline I kept throughout: embeddings and Atlas surface the candidates, code owns the ranking, and no model ever writes a fix. There is no generative model in the answer path. A card is always a real past job you can open and check.
Two pieces are where the open-weight argument stops being decorative:
-
The swappable
embed()seam. All embedding goes through one function, selected by a single env var (EMBED_MODEL). Change the model in one line and nothing else in the pipeline moves. The open model is the mechanic, and the mechanic is replaceable. -
The offline cosine fallback.
app.pyruns the Atlas retriever by default, but if Mongo is unreachable it drops to the in-memory cosine baseline and says so in the response. Pull the plug on the database and CaseJog still answers:
I scoped that fallback to Mongo connection errors on purpose, so a real bug surfaces instead of hiding behind the word "fallback."
The store runs the filter inside the $vectorSearch stage, not as a post-filter. That is what makes the microwave query correct, instead of correct only after discarding four wrong washers.
Why Does Open Innovation Matter?
The corpus is the business. A tech's pile of past jobs is his edge, and the moment it has to be uploaded to a cloud service to be searchable, that edge is now someone else's asset and every query is a line on a bill. CaseJog inverts all four of those: the embeddings run locally through an open model, the case data stays on the bench, every query is free and offline, and the ranking is a few hundred lines of Python you can read. The model is open and swappable. The data is private and stays home. A closed cloud API gives you the opposite on every count, and for a one-person shop whose whole advantage is the notebook, that is not a tradeoff worth making.
Two honest limits, because they are part of the design, not footnotes. First, I caught myself cheating the eval: my initial held-out set shared phrasings with the corpus, so both retrievers scored an inflated ~0.96, the retriever grading its own homework. I reserved held-out phrasings, regenerated, and re-ran once, and the numbers dropped to the honest figures above. Lower and real, and the head-to-head got clearer after the fix. Second, this is recall, not diagnosis, as a hard cap. CaseJog never says what's wrong. It shows which of your past jobs look closest and makes you open them. A fault genuinely new to your history returns the nearest old thing, which may be nothing like the answer, so "a human verifies" is the design, not a disclaimer.
I also tried a 2B decision model as a per-candidate confidence gate and deferred it. Smoke-tested locally first, it confidently mislabeled the near-but-wrong case the error-code boost already gets right, emitted two values dressed as probabilities, and cost about 20 seconds per pair on CPU, which would turn a sub-second search into a 100-second one. It would have made the demo slower and less correct, so it isn't in the code. I'd rather tell you that than claim a feature I don't have.
My Agent Session
The whole build ran through an AI coding agent over SSH on a small cloud box: scaffold, synthetic corpus, the Atlas-local vector index, the baseline-versus-hybrid comparison, and the eval, each step run for real with output captured in the repo's NOTES.md. The run that mattered most was the eval review, where the agent caught the phrasing leak described above and forced the honest re-run.
Prize Categories
-
Best Use of MongoDB Atlas: the appliance filter fused into
$vectorSearch, plus an exact error-code boost, returns the correct microwave door-switch cases, each citing its source job, on a query where pure cosine returns five wrong washing machines. The warrant is filter plus exact signal plus grounded citations, not corpus scale. - Best Use of Render: the live demo at casejog.onrender.com is a Render web service. It runs the open embedding model and talks to Atlas, with the model warmed at build time so the first query is not a cold load. The public endpoint is rate-limited per IP and caps query size, so an open, unauthenticated demo cannot be turned into a compute sink.
The product is built to run on the bench: one machine, open weights, the case data never leaving. The hosted demo exists so you can click it. Local and free is the version a one-person shop actually runs.


Top comments (0)