El Guarén: bank discounts in restaurants
A web app that consolidates the restaurant discounts published by 16 card issuers, verifies each one against the bank's own page and makes them searchable by day, cuisine, distance and card.
The problem
Banks and retail cards publish restaurant discounts, but each one does it on its own page, in its own format, with its own rules: some only apply on certain days, some cap the cashback, some work only with one specific card. To know where it's worth eating today you have to open sixteen different sites and read the fine print on each.
The real problem isn't collecting the data, it's that it expires silently. A bank pulls a deal from its page and no record remains: the discount keeps circulating in listings and screenshots, and you find out at the table, when they don't honour it.
I built the app to solve both: consolidate the offer in one place and, above all, publish only what can be verified against the source.
What is published
Discounts are spread very unevenly across issuers, and that changes which card is worth having.
Eight of the sixteen issuers. The other eight add up to 117 discounts between them.
The flow, from the bank page to the app
Each piece does one thing and can be run on its own. Once a month, after extraction, everything else is built and published with one command.
- 01ExtractionOne extractor per issuer. Some read the bank site’s public API, others need a browser because the page answers with an anti-bot challenge, and one comes from a monthly benefits PDF.
- 02Verification against the sourceFor issuers that allow it, every deal is checked on its bank’s page and anything returning 404 is dropped. Anything captured with a browser is only valid for the month it was captured, and a file from another month is not published.
- 03ConsolidationThe same restaurant arrives under different names from several issuers. It is unified, deduplicated, and manual corrections are applied, each with its reason written down.
- 04EnrichmentGeocoding of every branch (1,148 of 1,285 with an exact address), a photo from the restaurant’s own site, menu, opening hours and cuisine.
- 05Publishing with brakesThe deploy stops by itself if an issuer lost more than 40% of its discounts, if a JSON came out empty or invalid, or if anything shaped like a credential slipped into the published folder.
- 06Usage and feedbackOnce live, the app collects opinions from people who went and counts what gets looked at, to know which data is worth improving next month.
The rule that governs the whole project
On its first day live, the app showed a 40% Thursday discount at a restaurant, valid until the end of the month according to the original listing. The bank had already pulled it from its page. I went, asked for the discount, and they didn't honour it.
That produced the one rule that isn't negotiable: only publish what the bank's page shows today. It isn't enough that the expiry date hasn't passed. Every discount stores the day it was seen at the source, and the detail sheet shows it next to the percentage.
It's an uncomfortable decision, because it deletes deals that are probably still alive and leaves the app with less content than it could have. But the value of this data is that you can use it in front of a waiter, and for that a false positive costs far more than a gap.
From a catalogue to a decision
A list of 787 discounts doesn't answer the question that actually matters: which card is worth having? That question mixes three things the person knows (how often they eat out, how much they spend each time, what they earn) with two that live in the data (how many places each card works at, and how big the discount is) and one you have to go and find separately (each card's monthly fee and minimum income).
The result is an estimated monthly saving per issuer, against its cost, and a one-line verdict: which card is worth it, how much it saves per month, how many places it works at and what it costs. The page states explicitly that this is an estimate and not financial advice, and discards cards the person won't qualify for.
What the people who went contribute
There's one piece of data no bank page publishes: whether the discount was actually honoured. Only the person who went knows that, so each restaurant sheet lets you leave a rating, tags and a short comment, with no account and no sign-up.
Opening a public form to the internet means solving abuse before features. Opinions live in a SQL database at the edge, and each one passes several filters: the request has to come from the page itself, the text is validated and rejected if it contains links, emails or phone numbers, and there are per-connection and per-restaurant daily limits. Limits are stored as a fingerprint derived from the IP, never the full IP, and they expire on their own.
The average only appears from three opinions onwards, so a single one doesn't define a venue's rating.
Built for the phone
This gets used standing up, on the street, deciding where to walk in. So it was designed for the phone first, and after launch I reviewed it at twelve screen sizes, with a separate accessibility review.
The GPS location is used to sort by distance and never leaves the phone. There are no accounts and no cookies.
What I took from it
- Freshness is part of data quality. A correct value that is no longer current is a wrong value. Storing the verification date next to each record, and showing it, changed the whole project.
- Validations belong in the process, not in your head. When an extractor breaks, it usually returns fewer rows without raising an error. That's why publishing stops by itself when an issuer loses 40% of its discounts from one month to the next.
- Deduplicating entities with no shared key is the slow work. Sixteen sources write the same restaurant in sixteen ways, and the manual corrections, each with its reason written down, ended up mattering as much as the code.
- Published identifiers don't change. Shared links and opinions hang off them: improving the data can't break what is already out there.