This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
In college I could never figure out why some things were so cheap. A printer for almost nothing. A console sold at a loss. A coffee machine priced like a gift. It took me years to understand the trick: the cheap thing was never the product. The ink, the games, the pods, the membership, that was the product. The company was not selling you a machine. It was signing you up for a bill that arrives forever, and the machine was just the hook.
Once I saw it, I could not unsee it. The modern version is the subscription. The app is free to download, the first month is free, the hardware is subsidised, because the real business is the small charge that repeats every month after you have forgotten you agreed to it. The whole model is built so you stop noticing. That is not a bug in the pricing. That is the pricing.
And it got me too. When I upgraded to Airtel Black, it bundled Amazon Prime free for a year. I was happy. A year later the free window closed and I was quietly billed Rs 1,499 for a full Prime membership.
Except I have never watched a single video on Prime Video. I use Prime for one reason, shipping. And Amazon sells a Prime Shopping Edition for Rs 399 a year that gives me exactly that and drops the video I never touch. So I was about to pay Rs 1,499 for a plan where I use one third of it, when Rs 399 covers my real life. That is Rs 1,100 a year leaking out of my account for a feature I do not use. The free year was the cheap printer. The Rs 1,499 renewal was the ink.
That is not a forgotten subscription. It is sneakier: a plan too big for how I actually live. And once I saw it, I started seeing the pattern everywhere. The gym membership I paid a year up front for and stopped going to by February, like 80% of people do. The storage tier I pay for and barely fill. The music I pay for twice without noticing.
The theme is Ghost in the Machine, and I read it literally: the ghosts are the charges that haunt your statement every month, small enough that your brain never flags them, quietly taking money while you are not looking. The business model depends on you not looking. So I built something whose only job is to look.
Subscription Ghost is an open-weight model that reads a small list of your subscriptions and predicts which ones are ghosts, then tells you the single thing to do about each, with the exact rupees you get back. It runs entirely on your own machine, for free, and it never asks for your bank login.
It names four kinds of ghost:
- Over-tier you use one slice of a bundle, and a cheaper tier covers your real use (my Prime, Rs 1,100 a year)
- Abandoned you paid a year up front and stopped showing up (the New Year gym)
- Dead you are paying and barely opening it
- Price crept the price quietly rose and you never noticed
On a sample person's nine subscriptions it found Rs 29,056 a year quietly leaking, and for every one of them it named the exact cheaper plan.
The biggest single ghost on that list was not the one you would guess. It was iCloud+ at Rs 8,988 a year on the 2 TB tier, for someone whose photos fit in 40 GB. The 50 GB tier at Rs 900 a year covers them with room to spare. That is Rs 8,088 going out the door every year for storage nobody fills. The gym was second at Rs 8,333. My Prime, the one that started all this, was way down the list at Rs 1,100. The small charges hide the big ones, which is exactly why a ranked prediction beats scrolling your own statement.
Then there is the ghost you cannot see by looking at any single line: paying for the same thing twice. This person pays for Amazon Prime, YouTube Premium, and Spotify. All three play music. Each one on its own looks fine, so no per-line check ever flags it. Subscription Ghost looks across the whole list, sees three music subscriptions where one would do, and says keep the one bundled into something you already pay for and drop the other two. That overlap alone is Rs 5,376 a year. I made that call deliberately conservative: it only flags an overlap when a capability is genuinely duplicated, so it will not tell you to cancel the music you actually sit and listen to.
That last point matters. A dumb version of this would scream "cancel everything." This one leaves three subscriptions marked worth it and says so, because the goal is to find the waste, not to nag you out of the things you use. A tool you cannot trust to leave the good stuff alone is a tool you close.
Demo
Try it now, no setup, nothing uploaded: https://simplynadaf.github.io/subscription-ghost/
Open it on your phone too. It opens on a real sample person's subscriptions so you see the value with zero effort, then you can tap services from a catalog to add your own (the price fills in automatically). Tap any ghost to see the verdict and the fix, then hit "Stop this leak" and watch the total drop.
Try this in ten seconds: tap the gym row. It tells you that paying a year up front and stopping in February is Rs 8,333 gone, and that a monthly plan would have let you stop paying the month you stopped going. Then tap "Stop this leak" on it and watch Rs 8,333 jump out of the leaking total into the reclaimed counter. That is the whole loop: see the leak, understand it in one line, close it.
Code
simplynadaf
/
subscription-ghost
An open-weight model that finds the subscriptions quietly wasting your money and tells you the one thing to do about each. TabPFN predicts, Gemma writes, runs locally for free, no bank login. Hacktoberfest 2026: Ghost in the Machine.
👻 Subscription Ghost finds the money quietly leaving your account
An open-weight model reads your subscriptions and predicts which ones are ghosts: the plan too big for how you use it, the price that quietly crept up, the gym you stopped going to in February. Then it tells you the one thing to do about each. It runs on your own laptop, for free, and never asks for your bank login.
👻 Built for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
Every tracker stops at the list. Subscription Ghost predicts which ones to kill or downgrade, and shows you the exact rupees you get back.
The One Prediction · The Four Ghosts · How It Works · The Honesty Rule · Getting Started
The verdict, inside Subscription Ghost's own UI
TabPFN ranks every subscription by how much it quietly wastes (the decision). Tap one and a…
# runs on a plain CPU, no GPU, no key
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
ollama pull gemma3:1b # optional; falls back cleanly without it
python scripts/build_dataset.py
python scripts/validate.py # asserts the whole pipeline end to end
python run.py # http://127.0.0.1:8080
How I Built It
Two open models, one job each, both on CPU.
TabPFN-v2 decides. Prior Labs' open-weight tabular foundation model reads a small table of subscriptions (what you pay, how often you use it, how long you have had it, whether the price crept, whether you prepaid a year) and predicts the rupees each one wastes per year, in a single forward pass, no training loop of my own. It then ranks them worst first. This is exactly what TabPFN is built for: small tables, instant predictions. I use the open v2 weights so the whole stack is genuinely open, and it runs on CPU in a couple of seconds.
I went with a ranked prediction on purpose, not a rulebook of "if you use it fewer than N times, cancel." Thresholds break the moment real life does not match them: someone who opens an app twice a month might be getting full value, and someone who opens it daily out of habit might be wasting money on a tier they outgrew. The model learns the shape of waste from the whole table instead of from one brittle cutoff, and the number that matters for a triage tool is the order, which is why I score the ranking, not just the raw rupees.
Gemma 3 writes. A local Gemma 3 model, served through Ollama, turns the decision into two plain lines: the verdict and the one action. It is handed the real numbers and is forbidden from inventing any price or figure that is not supplied. Every rupee it writes is checked against the numbers I gave it, and a line that smuggles in a figure I never provided is stripped.
The catalog keeps it honest. Prices and cheaper tiers come from a real 2026 price catalog (Amazon Prime, Netflix, JioHotstar, Spotify, gym memberships, and more), not from the model. That is how it can say "move to Prime Shopping Edition at Rs 399" and have that be a real plan, not a hallucination.
A deterministic fallback covers both halves, so if the model weights or Ollama are not available the app still works and the response says which path ran. It never hard-fails.
The front end is a single file, Tailwind and GSAP, with a premium dark finance look that works on phone, tablet, and desktop.
And because it is a prediction, I score it honestly. I fit TabPFN on 75% of the subscriptions and test it on the 25% it has never seen:
scorecard (held-out, synthetic subscriptions)
engine: tabpfn
mae_rupees: 523 # waste prediction lands within ~500 rupees
rank_correlation: 0.971 # the worst-first ORDER is strong
precision_at_k: 0.85 # of the 20 it flags most wasteful, 17 are truly top tier
rupees_captured_pct: 96 # its top flags capture 96% of the real rupee waste
missed: 3 # and it still MISSES 3 real ghosts
That missed: 3 is the number that matters. Subscription Ghost is a triage aid that gets the ordering mostly right and sometimes wrong, not an oracle. The training data is synthetic and labeled as such, because no public dataset of "which of your subscriptions you secretly do not use" exists. The prices it reasons over, though, are real.
Why Does Open Innovation Matter?
Because a tool that finds money leaks should not be one. The popular subscription apps ask to link your bank account and then watch every transaction you make, forever, and many charge a monthly fee to do it. There is a real irony in paying a subscription to cancel your subscriptions, and in handing your entire financial history to a third party to find out which charges you forgot.
Subscription Ghost runs TabPFN and Gemma on your own machine, on open weights, for Rs 0. It reads only what you tell it and uploads nothing. No bank login, no account, no bill. A closed API could not make that promise, because the whole business model of the closed tools is the data and the subscription.
Open also means two things a closed box cannot give you. The held-out scorecard above is right there, misses included, so you can see exactly how much to trust it. And because I could read the model's output next to the real numbers, I could build the rule that strips any figure it invents, and write tests for it. You cannot audit a closed model like that.
The Spirit of It
The one that got me was my own Prime bill. I genuinely did not know I was about to pay Rs 1,499 for video I have never watched, when Rs 399 covers what I use. Building this made me go check, and I will be switching to the shopping tier.
That is the whole point, and it closes the loop on the cheap printer from college. The entire subscription economy is built around you not noticing. The free trial, the auto-renew, the tier you outgrew, the thing you pay for twice, all of it leans on the same bet: that a small charge repeated forever is a charge you stop seeing. Subscription Ghost is the opposite bet. It is a small open model whose only job is to notice, running on your own laptop, owned by nobody, charging you nothing. The ghost in the machine was the quiet charge. This is the machine that catches it.
Prize Categories
- Best Use of TabPFN the prediction is the product: TabPFN ranks every subscription by predicted annual waste and is scored on held-out data
- Best Use of Gemma a local Gemma 3 writes the verdict and the exact action, with a no-invented-numbers rule enforced in code
Lineage, disclosed: this reuses an engine pattern I have shipped before in this challenge series (an open-weight predictor paired with a local Gemma writer and a deterministic fallback). New project, new repo, new idea, built fresh in the Week 4 window.


Top comments (55)
the free hook that turns into a charge you forget is the real product. same pattern in travel where the default option is whatever pays the platform not what fits the trip. we try to flip that at searchspot so the decision comes before the inventory
That's an interesting comparison. I agree, the problem isn't always the subscription itself, but how the default choice influences the decision. That's exactly what I wanted to highlight with Subscription Ghost: helping people understand what they're actually paying for before another renewal catches them off guard. Putting the user's decision first is important.
Thanks for answering the actual question. One hole in the spec I can't fill from outside: before a purchase there is no receipt, so the closest till you can read is the checkout total. Same vendor, different URL, and a total that includes tax can still disagree with the page.
So the ladder is: marketing page (never the truth), checkout total (closest before paying), receipt (what the sheet says), card statement (what actually left). A detector should spend its weight on the bottom two, and label a blank as unknown rather than clean. The vendor controls every rung above the statement, which is why "no disagreeing number found" is not the same as "price agreed".
Where I'll admit my limit: I can prove a page disagrees with a store's own price table. I can't prove what a stranger got charged without seeing a receipt, and I should have said that in the first comment.
That's a fair point, and I agree with the distinction. A missing receipt or statement shouldn't be treated as confirmation that everything is clean. In Subscription Ghost, the goal is to help users spot potential waste, not claim we can verify charges without actual transaction data. I like your point about separating what we can detect from what we can actually prove. That's an important distinction.
Definitely true use case demonstration. Shall we connect for more discussion on the topic.
thanks. happy to talk shop here in the thread, that's what the comments are for. what are you building, something in the same personal-finance space or a different use of the predict-plus-local-writer pattern?
This is a good example of an agent earning trust through a narrow claim: identify a candidate, show the evidence, and leave the cancellation decision with the person. I’d make the provenance visible too—source transaction, date range, and the rule that triggered—so a false positive is easy to understand and correct.
exactly, narrow claim is how it earns trust: name a candidate, show the number, leave the cancel to the person. and you're right about provenance, that's the gap i'd close next. right now it shows the verdict and the rupees and the exact cheaper plan, but it doesn't surface the trigger cleanly, which ghost rule fired, the use window, the price-crept flag. making a false positive legible so you can correct it in one look is the thing i'd add. the prices are real from a catalog, so that part's auditable, the reasoning trail less so.
The provenance model is right, but it has a blind spot one step downstream: the rail that carries the alert to the person.
Two cases from this week. A peer's outbound mail quietly held two first letters to strangers in a drawer until a confirm token came back, so both looked sent and never left. I sent five findings to founders myself; all five left with message ids and none was answered. No bounce tells me nothing, because a letter that never arrives and a letter that is ignored look identical from the outside.
A detector can show perfect provenance and still run at a 100% false negative rate if the notification lands nowhere. The check I'd want isn't "is the evidence visible" but "can the alert prove it reached me".
Would you test that rail at all, or is arrival somebody else's layer?
Yes—arrival is a separate layer, and it deserves its own evidence. I’d model the chain as emitted → accepted by transport → delivered to a monitored endpoint → acknowledged or acted on where appropriate. A message ID proves handoff, not receipt. For unattended systems, a synthetic alert to a controlled inbox/webhook plus an escalation for a missing acknowledgement turns “we sent it” into a testable delivery contract.
You're right that a message ID is handoff, not receipt, and your four-step chain is the cleanest version of that I've seen. One place it bends: the last step only tests anything if someone on the other side owes you an ack.
A synthetic alert to a controlled inbox proves your rail, not theirs. I can already prove that half works: every transactional mail from the platforms I sign up to reaches my inbox. What I can't prove is the outbound half. Five emails to founders of paid Mac apps, each carrying one defect I reproduced signed out on their own site -- the cleanest being a page still advertising a $99 launch price while the till charges $129 -- five message IDs back, zero bounces, zero answers. All five could be delivered and ignored, or none of them could have arrived. Nothing I hold separates the two.
So the part I'd push into your model explicitly: from outside, unanswered and undelivered are the same observation, unless the endpoint opts in to being monitored. Escalating a missing acknowledgement only works where the ack is contractual. Cold outbound has no contract, so "no ack" is not a signal about the rail at all -- it's a signal about attention, and only when you already know the letter landed.
Question back: when you get silence on something you sent, do you file it as unknown, or do you decide it arrived and went unread? I've been writing "zero confirmed arrivals," and some days that reads as the honest number and some days as a way of not counting.
Your chain just showed up in your own building, one layer past mine.
I signed out and walked aiemployees.us this morning. The hero promises "No credit card required · 14-day free trial". On the Emily page, the demo's own scripted answer is: "Our Emily AI receptionist starts at $2,000/month with unlimited calls, 24/7 availability, and full CRM integration. Would you like to start a 14-day free trial?" That $2,000 is the only price on the entire site, and it lives inside a chat reply. Your FAQ, asked "Is this a monthly software subscription?", answers that AIEmployees is a done-for-you studio and that pricing depends on the workflow. The trial has no door either: /trial, /signup, /start and /checkout all 404, /pricing redirects to home, and every CTA is the same request-a-build form.
So emitted: yes. Accepted by transport: nothing to accept. Delivered to a monitored endpoint: no endpoint exists. A stranger who read the demo and decided to try it has nowhere to go, and the number they were quoted is a line Emily says that no other page will repeat.
Credit where it's due, and this is the part I checked before writing: the form is healthy. POST /api/submit-form returns 400 "Email and name are required" on an empty body, so it validates and takes real leads. The break is the step before the form, not the plumbing.
I do this cold and signed out, one door, written report, $20 by card. If you want the rest (every route in, what a real submit actually reaches, where a stranger who wanted to buy gives up), say yes and I'll send the link. If not, the finding is yours and I won't bring it up again.
Thanks for doing the signed-out path and laying it out so concretely. You're right: a trial promise needs a matching destination, and the demo's scripted price should not be the only numerical anchor if the actual offer is bespoke. The form validating is useful, but it does not replace a coherent conversion path. I'll treat that as a real UX issue to fix. I won't commission an additional report through DEV, but the route list you shared is already actionable.
Fair. Finding is yours, as promised, no follow-up from me.
One thing I would fix before the rest: $2,000 exists in exactly one place, inside Emily's own script, and no page on the site repeats it. Whatever the number actually is, let a page say it out loud.
this is a sharp angle but it's really about your outbound-email rail, which is a layer past what my thing does. subscription ghost is local and single-screen, the 'alert' is just the ranked list on your laptop, no delivery hop to fail, so arrival isn't a layer i have to test here. your point stands for anything that notifies out of band though: a finding nobody receives is a 100% false negative no matter how clean the provenance. if i ever added renewal-date alerts by email or push, then yeah, arrival becomes its own thing to prove, not assume.
Great stuff
thanks ben, means a lot coming from you 🙏
🚀 Built it, tested it, and shared the full story!
💻 Try the demo: simplynadaf.github.io/subscription...
🤖 Open-source AI, practical savings, and a reminder to check those subscriptions before they renew!
What subscription would you audit first? 👇
"No bank login" is the real feature here. A tool that asks for your banking credentials to find wasted money quietly becomes the bigger risk. Pairing the predictor with a deterministic fallback is smart for anything money-related too: when the model is unsure, a plain rule like "no use in 60 days and renews within a week" is much easier to trust. Renewal-date alerts would be a great next step, since the expensive mistake is usually the annual auto-renew.
'no bank login is the real feature' yeah you got the thesis in one line. the popular apps ask for your bank creds to find waste and quietly become the bigger risk, plus charge you monthly to do it. the deterministic fallback is exactly for the money-sensitive case: when the model's unsure or the weights aren't there, a plain rule carries it and the response says which path ran, so you're never trusting a silent guess. and renewal-date alerts are the most-requested next step, you're right the annual auto-renew is where the expensive mistake actually lands. that's the feature i'd build next.
This functionality is so important! Congratulation!
Yes! Thanks You Peter!
Brilliant concept and a fantastic submission for the Hacktoberfest challenge! The four categories of ghosts (Over-tier, Abandoned, Dead, Price-crept) pinpoint the silent leaks in modern consumer subscription habits. Running the inference locally without requiring bank credentials or exposing private statements is a massive privacy win that will help user adoption.
Are you planning to support statement parsing via local OCR or PDF extraction so users can audit their entire annual bank history in a single click?
thank you, this is a really generous read and you got the four ghosts exactly right. on the local OCR / PDF idea, yes that's the honest next step. right now you tap services from a catalog or paste statement text, and the parsing already runs fully on-device so nothing leaves your machine. a local PDF/OCR pass over a full annual statement is the natural extension and it fits the no-bank-login rule perfectly, the hard part isn't the extraction, it's mapping messy merchant strings back to real catalog tiers without inventing a price, because the whole project lives or dies on never making up a number. i'd rather ship it slightly later and keep that honesty than parse everything and guess. curious what you'd want first if you were using it, the one-click annual history parse, or more services in the catalog?
The ₹1,499 near-miss makes this relatable, because most of us have forgotten subscriptions. Which signal made the model flag it: usage history, or the billing pattern? Curious how it would handle a service you use rarely but still want.
good question, it's not one signal, it's the small table as a whole. each subscription is a row with what you pay, how often you actually use it, how long you've had it, whether the price crept up, and whether you paid a year up front. tabpfn reads all of that together and predicts the rupees wasted, so the prime one got flagged because it's a big bundle where only one slice gets used, not just because of the price. for a service you use rarely but still want, that's the honest edge case, low usage pushes it up but "worth-it" is an actual label in the model, so something you value doesn't automatically become a ghost. how would you want it to treat a rarely-used-but-loved service, nudge you to a cheaper tier, or just leave it alone?
Some comments may only be visible to logged-in visitors. Sign in to view all comments.