01Overview
TankIt is a personal fuel management app for drivers in Austria and France. The core loop is simple: snap a photo of a fuel receipt, let the app read it automatically, and watch your history, efficiency, and spending build up over time.
On top of that, a real-time price finder surfaces the cheapest station near you, and home screen widgets bring key numbers to a glance.
A privacy-first iOS app that turns fuel receipts into spending insights, finds the cheapest nearby station in real time, and stays out of the cloud.
02Privacy
Privacy isn’t a feature, it’s a constraint that shapes every other decision. Concretely:
- All ML inference (OCR, rectangle detection, LLM extraction) runs entirely on-device.
- No analytics SDK, no crash reporter that phones home, no user accounts.
- Receipt images are stored locally on your iPhone, never transmitted.
- The only network calls leave the device for the two government price APIs — and only your GPS coordinate goes with them.
- You can export the entire dataset (CSV + ZIP of receipts) at any time. Nothing to lock in.
03On-device intelligence
OCR — Apple Vision framework
VNRecognizeTextRequest runs at .accurate level, with language correction tuned per use case:
- Receipt text → language correction enabled, languages
["fr-FR", "de-AT", "en-US"]. - Odometer photos → language correction disabled so digit sequences aren’t mangled into words.
Structured extraction — LLM + regex pipeline
After OCR produces raw text, ExtractionService runs a two-stage pipeline:
- LLM path (Foundation Models, when available): Apple’s
FoundationModelsframework (LanguageModelSession) with a@Generablestructured output type — the on-device model returns typed fields directly, no string parsing.@Guideannotations steer each field toward the expected format. - Regex fallback: A hand-crafted multilingual parser for receipts from French, Austrian, and German stations — covering labelled patterns (
Prix/L,Preis/L,GESAMTSUMME,NET À PAYER) and free-form layouts. A derived-value step closes the price triangle: if any one of {total, volume, price/L} is missing but the other two are present, it calculates the third.
Receipt image correction
ReceiptCorrectionView wraps VNDetectRectanglesRequest to find the receipt boundary, then applies a perspective warp. Available both immediately after scanning and retrospectively from the fill history.
04External APIs
The only outbound traffic from TankIt: two official government fuel price registries. No third-party data brokers, no scraping.
e-control.at
Federal energy regulator’s public price API. Internal fuel codes (E95, Diesel, LPG) map to SUP, DIE, GAS.
data.economie.gouv.fr
French government’s open fuel-price dataset, queried via ODS v2.1 spatial filter — up to 20 stations within 5 km in one request.
data.economie.gouv.fr/api/explore/v2.1/…/prix-des-carburants-en-france-flux-instantane-v2Routing — FuelPriceRouter
A shared router abstracts both APIs behind a single fetch(coordinate:countryCode:fuelType:) call. It:
- Auto-detects the relevant country from GPS coordinates.
- Caches results for 10 minutes with a ~1 km grid quantisation key.
- Persists the cheapest station to the App Group
UserDefaultsso widgets always show fresh data without their own network call. - Triggers a
WidgetCenter.shared.reloadTimelinesafter each successful fetch.
Apple Maps — navigation deep-link
The price finder uses maps://?daddr=lat,lon&dirflg=d to open turn-by-turn navigation with zero additional SDK dependencies.
05Tech stack
Language & UI
- Swift 6 — strict concurrency throughout (
@MainActor,Sendable, structured tasks). - SwiftUI — 100% declarative UI; the only UIKit view controller is the camera viewfinder.
- WidgetKit — four widget types (small + medium for both price finder and efficiency).
Persistence
- SwiftData — model layer (
FuelEntry,Vehicle) with@Modelannotations, relationship rules, and reverse-index queries. - UserDefaults (App Group) — shared container
group.com.tankit.appfor passing live data between the main app and widgets without a server round-trip.
Intelligence
- Apple Vision — on-device OCR & rectangle detection.
- FoundationModels — on-device LLM for structured field extraction, with a regex fallback when the framework isn’t available.
- StoreKit 2 — modern transaction-listener subscription model.
06Architecture highlights
Everything stays on device
All ML inference (OCR, rectangle detection, LLM extraction) runs entirely on-device. No analytics SDK, no crash reporter that phones home, no user accounts.
Subscriptions, the modern way
SubscriptionService uses Product.products(for:), Transaction.currentEntitlements, and a Transaction.updates listener for real-time entitlement changes.
Partial-fill efficiency
A common bug in fuel trackers: partial fills break L/100 km calculations. efficiencyPairs(from:) accumulates volumes across consecutive partial fills and only closes the cycle at the next full fill.
No third-party archiver
ExportService ships a hand-written ZIP builder (STORED method, no compression) in ~80 lines of pure Swift. JPEGs are already compressed — STORED keeps it dependency-free.
Widgets stay fresh
The router persists the cheapest station to the App Group UserDefaults, then calls WidgetCenter.shared.reloadTimelines. Widgets render fresh data without making their own network request.
Quiet on the network
Price queries are cached for 10 minutes against a quantised ~1 km grid key. Same neighbourhood, same fuel, no duplicate request.
07Platform & requirements
- iOS 18+, iPhone only. On-device LLM extraction kicks in where Foundation Models is available; a regex parser handles the rest.
- Xcode 16 / Swift 6.
- Location permission for price finder (optional — manual address search available).
- Camera permission for receipt scanning (optional — manual entry available).