{"slug": "7-5-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest", "title": "7.5% Cashback Isn’t a Price. I Built TripStack to Read the Rest.", "summary": "A developer built TripStack, an MIT-licensed hotel-cost comparison prototype that uses local Gemma (gemma:2b via Ollama) to extract promotional terms into inspectable conditions, then feeds them to a deterministic pricing engine that separates upfront payment, estimated cashback, and estimated net cost. In the demo, Gemma mislabeled a $60 cashback cap as a fixed amount; the hybrid parser overrode it with rule-based values, logged the discrepancy, and the reviewed offer changed estimated net cost from $731.88 to $712.75 on simulated data. The client validates structured JSON with Pydantic, treats promotion text as untrusted input, and includes an unvalidated Amadeus adapter.", "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\n“Earn 7.5% cashback.”\n\nThat sounds like a number you can subtract from a hotel bill. Then you read the next sentence: maximum reward $60. Minimum spend $200. Taxes excluded. Cannot be combined with coupon codes.\n\nNow compare it with another hotel offering an instant discount. Which costs less? Which requires less money today? Does either stay put you near the part of town you actually wanted?\n\nA headline rate gives you homework.\n\nI built **TripStack**, a hotel-cost comparison prototype that uses local Gemma to help turn promotional terms into conditions you can inspect, then passes those conditions to a deterministic pricing engine.\n\nThe interface separates **upfront payment**, **estimated cashback**, and **estimated net cost**. You can plan multiple destinations, filter stays by location, review an offer’s source evidence, and preview its effect before applying it.\n\nFor *Slow and Steady*, the task I wanted to take off someone’s plate was the repeated cross-checking: remembering a cap, finding an exclusion, comparing a delayed reward with an immediate discount, and doing it again for the next city.\n\nThe user still makes the decision. The app organizes the evidence and does the arithmetic.\n\n**About the demo:** hotel records, quotes, coordinates, and promotion terms are simulated examples. Gemma inference, source checks, geographic filtering, and price calculations actually run. The displayed savings are not evidence of real market savings. An Amadeus adapter is included, but I have not validated production quote retrieval.\n\nThe recording starts with five nights in Boston. I inspect the recommendation, analyze a cashback offer with Gemma, review the evidence, and apply it. Then I add five nights in New York and repeat the comparison.\n\nOn the Boston demo snapshot, the estimated net cost changes from **$731.88 to $712.75** after applying the reviewed offer. That **$19.13 difference is a calculation on demo data**, not a claim about a currently bookable hotel or a guaranteed payout.\n\nApplying a promotion does not fetch a fresh hotel price. Both sides of the preview use the same quote snapshot, so the difference reflects the promotion calculation rather than a moving baseline.\n\n*This screenshot shows a separate three-city example. Demo labels and budget warnings remain visible.*\n\n[GitHub: michelleeyin/tripstack](https://github.com/michelleeyin/tripstack)\n\nThe application code is MIT-licensed. The repository includes the demo fixtures, prompts, pricing engine, tests, and local setup instructions. Gemma is an open-weight model with its own license; the application’s MIT license does not replace it.\n\nIn the recording, Gemma returns `fixed_amount=60.0` for an offer whose sixty dollars is a **cashback cap**.\n\nSixty is present in the source. Its role is wrong.\n\nThat is a more interesting failure than a completely invented number: checking whether a number occurs in the text would not be enough.\n\nIn the hybrid parser, numeric values come from explicit rule matches. Gemma supplies other extracted conditions, with explicit source exclusions taking precedence. The discrepancy is recorded, the incorrect fixed-amount assignment is replaced with the rule parser’s empty value, and sixty remains the cap.\n\nThe warning is visible in the extraction record. It is part of the result, not something hidden to make the demonstration look cleaner.\n\nTripStack runs **Gemma through Ollama**, with `gemma:2b` as the default. I used local CPU inference on a 16 GB Mac for the working demonstration. I did not fine-tune the model.\n\nThe client requests structured JSON, validates the response with Pydantic, limits input and output length, and records inference timing and token counts when available. The prompt treats promotion text as untrusted data and tells the model not to follow instructions embedded inside it.\n\nThe extraction path also checks source numbers and dates. Unsupported or unresolved benefits are excluded from reported savings. If inference fails, the app records a rule-parser fallback instead of calling the result a successful Gemma extraction.\n\nThe three parsing modes make that division visible:\n\n| Mode | Numeric fields | Other conditions | \n|---|---|---|\n| Hybrid | Rule matches from the source | Model-assisted extraction, with explicit source conditions taking precedence | \n| AI | Model output, subject to source checks | Model output, subject to validation | \n| Rules | Rule parser | Rule parser | \n\nThese checks reduce specific failure modes. They do not prove that every interpretation is correct. A source excerpt can support a number without establishing a traveler’s eligibility.\n\nThat is why review remains visible, and why checking **“I reviewed the source terms”** does not upgrade an estimate into a verified benefit.\n\nThe model has a narrower responsibility than “find the cheapest trip.” It proposes structured conditions that the rest of the system can examine.\n\nThe pricing engine uses Python `Decimal`, with amounts rounded to cents and rates retained to six decimal places.\n\n```\nUpfront payment    = room rate + taxes + mandatory fees − instant discounts\nEstimated cashback = eligible spend × rate, subject to the offer cap\nEstimated net cost = upfront payment − estimated cashback\n```\n\nThere is no second, approximate reward calculator inside the optimizer. Promotion selection prices candidate sets through the same engine used to display the bill.\n\nThat matters when the user changes priorities. In a controlled $100 example, a $50 cashback offer gives a lower estimated net cost, while a $30 instant discount gives a lower upfront payment. Both can be sensible choices. The selected objective must determine which one wins.\n\nA regression also caught a quieter mistake: quantizing a rate like money turned `0.075` into `0.08`. On $1,000, that changes $75 into $80. Rates and amounts now have separate precision rules.\n\nAnother distinction is between an estimated benefit and a verified one. An unverified instant discount can appear in an estimated calculation, but it must not reduce the cost calculated using verified benefits only.\n\nThose distinctions are easy to lose behind one attractive total. TripStack keeps them separate.\n\nA cheap room outside the user’s chosen radius is not an eligible substitute.\n\nTripStack applies geographic constraints before ranking prices. It uses Haversine distance around a named downtown reference or user-supplied coordinates. Missing hotel coordinates fail an active radius filter. Savings baselines use the same location and refund constraints as the recommendation.\n\nThose distances are straight-line measurements, not walking times. A typed place name is a label, not an automatic address lookup.\n\nIf no stay meets the constraint, the app should say so rather than quietly recommend somewhere else.\n\nThe main flow has three views:\n\nThe first result shows the decision-relevant numbers. Detailed calculations, alternatives, and model settings sit behind expandable sections.\n\nI wanted someone to understand the recommendation without reading a diagnostic report—and still be able to open that report when a number looked wrong.\n\nThe current suite has **109 passing tests**, covering pricing, eligibility, extraction checks, model failures, geography, and page interactions.\n\nAutomated model tests use mocked responses. Actual local inference was checked separately and appears in the recording.\n\nI also installed the project in a clean virtual environment and exercised the demo search in the browser. That validates the installation and workflow, not production travel data.\n\nEven the diagnostics distinguish three states: the server is reachable, the model is installed, and an inference request has actually succeeded. Those are different claims.\n\nFor this project, running the model locally changes what I can inspect and control.\n\nThe offer text goes to my local Ollama instance rather than a hosted model API. I can change the prompt and output schema, inspect raw extraction records, switch installed models, and compare model-assisted parsing with the rule-only path.\n\nOnce dependencies and model weights are installed, local extraction and fixture-based comparisons do not need a hosted inference service. Live hotel retrieval would still need network access.\n\nThere is no per-request hosted-model bill in this setup, although local compute and electricity still have costs.\n\nThere are trade-offs, too: model downloads, memory use, startup time, and hardware compatibility become my responsibility. CPU mode helped make the demonstration work on my laptop; it did not make those constraints disappear.\n\nKeeping the model and validation code locally inspectable makes the correction in the demo reproducible. Someone else can examine the prompt, replace the model, or challenge a pricing assumption.\n\nThe open-weight model makes local inference possible. The open application code lets others inspect what happens to its answer.\n\nThe largest unfinished piece is real quote retrieval. I would validate the existing provider integration, establish its coverage and price semantics, and clearly distinguish sandbox responses from production quotes before presenting TripStack as a live comparison service.\n\nI would also expand evaluation beyond short English examples to longer promotions with membership, regional, and payment-card conditions. The small historical benchmark in the repository is a debugging aid, not a general accuracy claim.\n\nI have not measured user time savings or validated the prototype in real bookings. The current build demonstrates a narrower workflow: read a promotion locally, inspect its conditions, and compare its effect on a fixed set of quotes.\n\nA traveler should be able to stop thinking about a cap or an exclusion without losing the ability to check it.\n\nThat is the kind of laziness I wanted to make possible.\n\n**Best Use of Gemma** — local Gemma inference through Ollama for promotion-term extraction, paired with source checks, explicit fallback records, and a review-before-apply workflow.", "url": "https://wpnews.pro/news/7-5-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest", "canonical_source": "https://dev.to/michelleeeeeeeee-yin/75-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest-3fha", "published_at": "2026-10-26 00:05:15+00:00", "updated_at": "2026-10-26 00:12:39.358902+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-tools", "ai-agents"], "entities": ["TripStack", "Gemma", "Ollama", "Pydantic", "Amadeus", "michelleeyin"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/7-5-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest", "markdown": "https://wpnews.pro/news/7-5-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest.md", "text": "https://wpnews.pro/news/7-5-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest.txt", "jsonld": "https://wpnews.pro/news/7-5-cashback-isnt-a-price-i-built-tripstack-to-read-the-rest.jsonld"}}