{"slug": "four-birthdays-two-family-dinners-and-one-extremely-expensive-december", "title": "Four Birthdays, Two Family Dinners, and One Extremely Expensive December", "summary": "A developer built Ledgerly, a Telegram bot that combines Google's Gemma model for reading receipt photos with deterministic code for summing line items, then forecasts when a family's budget will run out based on upcoming calendar events. The bot addresses Turkish receipt formatting (1.234,56) by having code verify totals rather than trusting the model, and it asks the user when a date is unclear instead of guessing. In a demo set to December 1, the bot predicted a budget shortfall around December 19, ahead of four birthdays and a Yalda dinner for twelve.", "body_md": "*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*\n\nDecember 19, last year. I am in the supermarket line, holding a very large watermelon.\n\nIn two days, twelve people come to our place for Yalda, the longest night of the year.\n\nI open my bank app. I close it. I open it again, as if the number might change the second time.\n\nIt does not.\n\nPayday is January 1. I do the math. Then I stop doing the math.\n\nThis is That Day. Every family has one.\n\n*🧱 The bot is less dramatic. It calls That Day \"Wall Day\": the day the budget runs out.*\n\n**TL;DR:** Ledgerly is a Telegram bot for lazy families. Send it receipt photos and family dates. It tells you when the money will run out, while you can still change it.\n\n*Sped up: I tell the bot the family events, ask for `/forecast`, then ask about a gift. The chart shows the likely path, the range and the budget line.*\n\n*A note before the story: to keep my family private, I changed the names, the birthdays and the money. The demo bot uses a sample year of spending I made for the demo. Money is in Turkish lira (₺). The bot, the screenshots and every test number are real.*\n\nMy friend Ryan has typed every receipt into a spreadsheet, by hand, for years. Ryan has never had That Day.\n\nSo I tried Ryan's method. I lasted four days.\n\nSome days I had more than 30 receipts. Skip three days and you are looking at a pile of a hundred. That is when you quietly give up.\n\nRyan's method works. It just needs a Ryan. He is the tortoise in the old story: slow and steady, every day. I am only slow. So I wanted a bot to be steady for me.\n\nI looked back at December. The supermarket was normal. The calendar was not.\n\nFour birthdays, one of them my nephew Arman's. Yalda Night at our place, for twelve people. We stay up late with watermelon, pomegranates and poems. One more family dinner.\n\nGifts, cakes, and two big grocery runs in one week. By That Day, the money was already at the cake shop.\n\nI could not cut any of it. You do not cancel a nephew's birthday.\n\nMy wife knew every one of these dates in November.\n\nMy bank app knows I paid at a supermarket. It does not know it was a watermelon for Yalda. And it only looks back.\n\n**Families spend by the calendar. I did not need to type more. I needed to see the future, and I was too lazy to type even the past.**\n\nSo I asked a lazy person's question: why not give this job to an AI?\n\nThe first version was not even an app. It was a folder of photos and one instruction file for Claude Code.\n\nThen I wanted it on my phone, at the checkout, with the receipt still warm. And I thought: other lazy people might want this too. So for this challenge, I started again from zero, with a new repo.\n\nTurkish receipts write `1.234,56`. Read it the wrong way, and a ₺1,234 birthday cake costs ₺1.23. On a long receipt, it looks believable. It is very wrong.\n\nNow I send the photo to a Telegram bot and put my phone away. That is my whole job.\n\nGemma reads it. Then plain code adds up the items, because of that cake. Only then is the receipt filed.\n\n*Code checked the sum. Gemma only read it.*\n\nWhen it cannot read the date, it just asks.\n\n*No clear date, no guessing. \"Yesterday\" is a fine answer.*\n\nSame photo twice? The bot says: \"You already sent me this one 🦥\"\n\nDecember has not come yet. I am too lazy to wait, so I set the demo bot's clock to December 1, payday.\n\nIn October, my wife gave me the December list early. I passed it to the bot, the way I would tell a friend:\n\n*\"Arman's birthday is Dec 14. We're hosting Yalda for 12 people.\"*\n\nThen I ask for `/forecast`. It gives me the reply at the top of this post.\n\nMy wife reads it over my shoulder. \"Yalda,\" she says. \"Of course it is Yalda.\"\n\nThe bot says Wall Day comes around December 19. Last year I found out on that day, in the supermarket line. This year I ask first:\n\n*\"Can I afford a ₺5,000 gift for Arman?\"*\n\nA ₺5,000 gift for Arman on Dec 14 moves Wall Day from Dec 19 to Dec 17. Likely spending becomes ₺47,400 of your ₺30,000 budget.\n\nSo Arman's gift gets a little smaller. The watermelon stays the same size.\n\nThe demo bot only answers me, because spending data is private. Your own copy is one button and three API keys away (Telegram, Gemini, TabPFN):\n\nA Telegram bot for people who are too lazy to track their spending.\n\nSend it a photo of a receipt. **Gemma** reads it, and plain code checks that the items add\nup before anything is filed. Tell it about family events in plain chat: \"Arman's birthday is\nDec 14.\" **TabPFN** reads the ledger and the calendar and tells you **Wall Day**: the day the\nbudget runs out, while you can still change it.\n\n*The demo copy on Dec 1, sped up. A made-up household: every amount is made up.*\n\nEach person runs their own bot and their own database. One bot = one household.\n\n`/newbot`).\nMIT license. 165 tests, plus 5 live checks with real Gemma and TabPFN. The README shows where every measured number in this post came from.\n\nEach tool has one job. Gemma 4 on Google AI Studio reads. Plain Python checks. TabPFN, through the Prior Labs API, looks ahead. Render runs the bot, and Sentry watches it.\n\nOn the way, I learned five things that work outside this project too.\n\n**1. Let the model read. Let code do the math.**\n\nGemma turns photos into rows, and chat into small commands like \"add this event\". Language models are great readers and shaky accountants. So the sum check is plain Python:\n\n```\nif abs(sum(item.amount for item in items) - total) > 0.01:\n    status = \"⚠️ needs check\"  # after one retry\n```\n\nThat is the whole fix for the ₺1.23 cake. Gemma never predicts money.\n\n**2. With a tiny table, use a model that learns at prediction time.**\n\nI have about two months of real receipts, around 60 days. Most models need far more data and a training run.\n\nTabPFN was never trained on my receipts. It was pretrained on a huge number of made-up tables. Now it reads my small table and predicts in one go. It is a bit like an LLM that learns from examples in a prompt.\n\nOne row is one day:\n\n| Day | Days to payday | Birthday in next 3 days | Guests | Spent | \n|---|---|---|---|---|\n| Nov 26 | 5 | no | 0 | ₺600 | \n| Dec 13 | 19 | yes | 0 | ? | \n| Dec 21 | 11 | no | 12 | ? | \n\nPast rows have the answer. Future rows have a question mark. TabPFN fills in the question marks.\n\n```\nmodel.fit(past_days, spent_per_day)  # fit() only stores the table. No weights change.\n```\n\n**3. Only use what you will know on the day you predict.**\n\n\"Spent in the last 7 days\" is a useful column. It is also a trap. On December 1, I cannot know what I will spend from December 13 to 19.\n\n| Column | Known on Dec 1 for Dec 20? | \n|---|---|\n| Days to payday | Yes | \n| 12 guests for Yalda tomorrow | Yes, I told the bot | \n| Spent in the last 7 days | No. So I freeze it at the Dec 1 value | \n\nA backtest hides past days and asks the model to guess them. Skip this rule, and the backtest looks great while the real forecast fails.\n\n**4. A range turns a guess into a decision.**\n\nTabPFN gives three numbers for each day: low, likely and high.\n\n```\nlow, likely, high = model.predict(next_days, output_type=\"quantiles\", quantiles=[0.1, 0.5, 0.9])\n```\n\nWall Day is the first day where money already spent plus the likely days passes the budget. For the demo family, that is ₺30,000. The high numbers give \"maybe as early as\".\n\n\"Can I afford it?\" adds ₺5,000 on Arman's birthday to the same daily forecast and finds Wall Day again. One number is a guess. Two forecasts side by side are a choice.\n\nI also gave TabPFN an opponent: a typical day, the middle value of my past days. I hid two separate two-week stretches of my real spending. Then both guessed the total.\n\n|  | TabPFN | A typical day | \n|---|---|---|\n| Error on the two-week total (lower is better) | 19.3% | 24.5% | \n| Days inside the low-to-high range | 92.9% | no range | \n\nOn average, TabPFN was closer on the total, and the total is what Wall Day needs. It won one test by a lot and lost the other by a little. Day by day, the typical day was slightly better. My real data has no family events, so quiet weeks are easy for a typical day. Two tests is a small test.\n\nAlways give your model an opponent. Otherwise you cannot tell if it helps.\n\n**5. Trace one real turn before you guess where the time goes.**\n\nEvery Telegram message becomes one Sentry trace, with steps for Gemma, the tool call and, when it runs, TabPFN.\n\nMy first `/forecast` on Render took 37 seconds. TabPFN's step took 34 of them. The trace showed the wait came before TabPFN's first API call. It was Python libraries loading on the free server's small CPU.\n\nSo now the libraries load when the bot starts. The forecast is computed in the background after every new receipt or event. On an awake bot, TabPFN's step takes 3 to 9 seconds, and the answer is ready before you ask.\n\n*After the fix: TabPFN in the background, on the demo family's year.*\n\nHere is the Arman question, after the fix.\n\nGemma took 1.59 seconds and 1,149 + 37 tokens to understand the question. The forecast tool took 0.07 seconds, because the forecast was already waiting.\n\nAnd the cost: $0. Gemma, TabPFN, Render and Sentry all run on free plans.\n\nNo prompts and no images go to Sentry.\n\nRender's free plan puts the bot to sleep after 15 quiet minutes. This morning, after 23 quiet minutes, a test `/forecast` reached the sleeping bot. The first reply came after 43 seconds. The forecast came after 77. Sentry shows where the time went: right after a wake-up, TabPFN's step took 29 seconds again. I have not fixed this yet. Next, I will keep the last forecast in Postgres, so a wake-up does not wait for TabPFN.\n\nTelegram resends a message when the bot is slow. So the bot says \"got it\" at once, does the work after, and skips repeats. After a nap, it also says sorry for the wait. On a plan that sleeps, answer first and work after.\n\nOne gotcha: leave out `plan: free` in `render.yaml`, and Render picks a paid plan. The same `render.yaml` behind the Deploy button passes Render's Blueprint check. My copy was deployed from it through Render's API in about 4 minutes.\n\n*My copy on Render: one free web service and one free Postgres.*\n\nReceipts are private. They show where you shop, what you eat and whose birthday is next week.\n\n`GEMMA_PROVIDER=ollama` on a machine you control, and Gemma reads your receipts there.\nWith open weights, you choose where your receipts are read.\n\nTyping one receipt by hand takes me about a minute. Sending a photo takes about five seconds. The bot needs about ten more. On a busy day with 30 receipts, that is half an hour of typing, or under three minutes of photos. Ryan: still typing.\n\nI wrote the story, the spec and the first receipt rules. Claude Code wrote most of the code, ran the tests and deployed the bot through Render's API. A review pass found a real bug. The chat agent moved last year's Arman birthday and Yalda dinner to this year. I tested the bot in Telegram and recorded the demo.\n\n`render.yaml` and a Deploy button give each family its own copy, with its own data. My I am still lazier than Ryan. I do not type a single receipt.\n\n**But it is only the first of December, and I already know when That Day is coming.**\n\nIt may still come. It will not be a surprise.\n\n**Ryan still types every receipt by hand. He says it is relaxing.**\n\nWhich month is your family's That Day? Tell me in the comments.", "url": "https://wpnews.pro/news/four-birthdays-two-family-dinners-and-one-extremely-expensive-december", "canonical_source": "https://dev.to/asiboy6789/four-birthdays-two-family-dinners-and-one-extremely-expensive-december-5cga", "published_at": "2026-10-26 06:07:00+00:00", "updated_at": "2026-10-26 06:12:44.234510+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "large-language-models", "ai-products"], "entities": ["Ledgerly", "Telegram", "Gemma", "Claude Code"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/four-birthdays-two-family-dinners-and-one-extremely-expensive-december", "markdown": "https://wpnews.pro/news/four-birthdays-two-family-dinners-and-one-extremely-expensive-december.md", "text": "https://wpnews.pro/news/four-birthdays-two-family-dinners-and-one-extremely-expensive-december.txt", "jsonld": "https://wpnews.pro/news/four-birthdays-two-family-dinners-and-one-extremely-expensive-december.jsonld"}}