case/projects/batameta
01/04
Batameta Tecnologia·Founder · Fullstack·Dec 2025 - in productionIn production (4 apps live)

Batameta

Four-app fintech with a native Copilot that grades each ride offer green/yellow/red on the device, in real time.

4
apps in production
R$ 250k+
tracked driver earnings
60k+
km logged
1 founder + 1 traffic + 1 UI/UX
team

Batameta tells gig drivers exactly how much they need to earn today to actually pay their bills on time, and rates each ride offer in the second it arrives so they know whether to accept. The product spans four apps: a Next.js marketing site, a Next.js admin for ops, a React Native mobile (where the native Android Copilot lives) and a NestJS API on multi-schema PostgreSQL. It's in production with real drivers using it daily.

01

What it solves

Imagine a driver with a R$ 800 bill due on day 8. They try to plan by dividing it across the rest of the month - but the math is wrong, the working hours don't match, and the bill silently slips. Multiply that by recurring costs (rent, insurance, fuel) and the occasional non-recurring shock (a tire, a service), and most drivers can't actually tell whether they cleared their costs in any given week. Batameta turns the entire financial model - bills with their real due dates, recurring costs, working pattern, accumulated earnings - into one number: 'your goal today is R$ X.'

02

How a driver uses it day to day

Onboarding asks two things: which weekdays you work, and what your bills + recurring costs look like. From there: open the app at the start of the day, see the daily target as a single number; turn the Copilot on; drive. The moment Uber or 99 shows a ride offer, the Copilot grades it green/yellow/red on top of the screen, with R$/km, R$/hour, R$/min, total time and pickup → destination. Log a session at the end of the run; the engine recomputes tomorrow's target from what you actually earned today and what's left of the month.

4
apps in production
03

The goal engine

Monthly target = recurring + non-recurring + desired profit + bills postponed from previous months, weighted by their real due dates. Daily target = (monthly − already-earned) ÷ remaining working days, respecting the working pattern the driver picked. When the monthly target is hit, an extra-daily kicks in based on the driver's own average earning. The whole thing lands as a single push at the start of the day so the driver opens the app already knowing what to do.

04

Challenge: reading apps we can't modify

The Copilot has to read each Uber/99 ride offer the moment it appears, in real time, on the driver's phone. We don't own those apps. We can't ship code into them, can't use a public API, and we don't want screenshots leaving the device for privacy and cost reasons. Anything that makes the driver tap to evaluate is too slow - the offer expires.

05

Solution: native Android bridge

Built a native Kotlin module that React Native bridges into. An AccessibilityService watches Uber/99 in foreground, traverses the view tree, and falls back to ML Kit OCR when the tree is locked. Per-app parsers (UberParser, NinetyNineParser) pull price, distance, time, pickup, destination, rating. Evaluation runs 100% on-device against the driver's cached cost profile - the API only serves parser configs (cached 1h). Zero per-ride calls means privacy stays intact and the Copilot keeps working on flaky connections.

06

Challenge: trusting parsers on devices I'll never own

I test on my phone. Users have thousands of others - different Android versions, different manufacturers, OEM tweaks to the view tree. The Uber screen on a Xiaomi running Android 12 is not the same DOM as a Samsung running Android 14. How do I know the parser is actually getting it right out there?

07

Solution: anonymous telemetry pipeline

Every time a ride offer hits the Copilot, with the user's explicit consent, the device sends a minimal payload: the raw extracted screen data, the parsed output, app version, copilot module version, Android version, manufacturer, raw and parsed pickup/destination addresses. Fully anonymized - we have no way to map a payload back to a user. Comparing raw vs parsed at scale tells us exactly which devices a parser is missing on, and where to harden it next. The Copilot gets better the more it's used.

08

Challenge: every ride app changes its UI without warning

Uber updates a layout. 99 ships a redesign. iFood and InDrive look nothing alike. Hard-coding parsers against today's view tree means a broken Copilot in a week and an emergency app release every time a vendor pushes an update.

09

Solution: Open/Closed core + hot-swap parsers

The Copilot core is closed - it knows nothing about specific apps. Each integration plugs in as a parser implementing a single contract (extract → validate → emit). Parser configs ship from the API and refresh hourly, so a UI break gets fixed without an app release. Adding Uber, 99, iFood, InDrive or the next ride app is a parser drop-in, never a core rewrite.

10

Backend platform

Event-driven NestJS + Prisma over PostgreSQL with 7 isolated schemas (customers, marketing, goals, finances, admin, payments, public) - bounded contexts get clean ownership boundaries. RabbitMQ decouples async work, MongoDB stores high-write event logs, OpenAI handles ambiguous address enrichment in a separate queue. Payments are agnostic-by-design: Stripe handles cards, AbacatePay handles BR-native PIX (cobrança + recurrence + webhooks). Both rails idempotent, reconciliation identical regardless of provider. Activation orchestrates Expo push, WhatsApp (WAHA), React Email + Resend, plus a referral/reward engine and a gamification module (badges, levels, streaks).

11

Trade-offs that hurt

Android-only: AccessibilityService doesn't have an iOS equivalent for reading other apps' screens. iOS users get the goal engine and the financial side, but not the Copilot - that's a real product gap. Permission ramp: AccessibilityService has serious privacy implications, so the onboarding has to take its time explaining what it does and doesn't read; some drivers bounce at that step. Telemetry trust: drivers have to opt in for the parser-quality pipeline; coverage on the smallest device niches is slower until adoption catches up. Multi-schema PostgreSQL is overkill at this scale today, but adding a domain becomes a schema operation instead of a re-architecture - I'd rather pay that complexity now than later.

decisions & tradeoffs
  • Why a native Android Copilot instead of in-app notifications?
    Kotlin AccessibilityService + ML Kit OCRAn offer evaluator has to read the Uber/99 screen the moment it appears without modifying their apps and without screenshots leaving the device. AccessibilityService is the only stack that gets us there. Local-only evaluation keeps it private and removes per-ride API costs entirely.
  • Why parser config server-side, evaluation client-side?
    Hot-swappable parsers, local evaluationUber/99 change UI without warning. Pulling parser configs from the API means we ship a fix without an app release. But evaluation stays local so user data never leaves the device and we don't pay rate-limit costs per ride.
  • Why dual-rail payments (Stripe + AbacatePay)?
    Stripe + AbacatePayStripe is the easy international card rail, but PIX dominates BR and recurring PIX via AbacatePay is dramatically cheaper than card processing for the driver persona. Both rails run webhook-driven with idempotency, so reconciliation is identical.
  • Why multi-schema PostgreSQL (7 schemas) over a single schema?
    Schema-per-bounded-contextCustomers, marketing, goals, finances, admin, payments and public live in their own schemas. Bounded contexts get clean ownership boundaries; backups, migrations and even per-domain access roles stay simple. Adding a domain is a schema operation, not a re-architecture.
  • Why an opt-in raw + parsed telemetry pipeline?
    Anonymous payload, raw alongside parsedI can't validate parser quality across an Android device ecosystem from one phone. Sending the raw screen data alongside the parsed output - anonymized and consented - lets us see at scale where parsing diverges, on which Android versions, manufacturers and app builds, so the Copilot improves the more it's used instead of degrading every time a vendor ships a layout.
  • Why an agnostic payment integration instead of locking in a single provider?
    Provider-agnostic adapterStripe and AbacatePay sit behind the same internal contract. Webhook handling and reconciliation know nothing about which provider produced an event. If pricing or availability changes for either, swapping is a config change, not a refactor - and a third rail (a future bank, a future PIX provider) is one adapter away.

Live in production with real drivers using it daily: R$ 250k+ in tracked earnings, 60k+ km logged, four apps in production, native Copilot in drivers' hands, agnostic dual-rail payments processing, ~76 Jest specs guarding the codebase, Open/Closed architecture ready for the next ride app to plug in.