Anatoli Kot

Anatoli Kot

Software Engineer · Cybersecurity

software

DocApp

Mobile app (iOS + Android) that centralizes household bills and receipts — scan a document, let AI extract the details, review, and track from one dashboard. Now named Tiyuk, with all eight planned build phases done and EAS build configuration in place.

Role

Developer

Problem

Replaces scattered paper bills and receipts with a single organized, searchable digital system, using a provider-agnostic OCR/AI interface so no vendor is hardcoded.

Tech Stack

React Native (Expo)TypeScriptSupabaseSupabase Edge FunctionsZodExpo RouterClaude (Anthropic API)React QueryJest

How It Works

Capture DocPrivate StorageOCR / AI ExtractValidate (Zod)Review & Confirm
  1. 01A document is captured via camera, gallery, or PDF picker and uploaded to a private Supabase Storage bucket, hashed with SHA-256 to catch duplicates.
  2. 02A provider-agnostic DocumentProcessor interface calls a Supabase Edge Function that reads the private file and sends it to Claude for OCR and structured extraction — the API key lives only in that function, never in the app, and swapping AI vendors later wouldn't touch the rest of the app.
  3. 03Zod validates the returned JSON before the app uses it, then a review screen flags low-confidence fields for the user to correct and pre-fills the bill form — untrusted model output never gets written to Postgres directly.
  4. 04During review, the document is matched against existing bills by provider, amount and billing period, offering to mark an existing bill paid instead of creating a duplicate.
  5. 05Supabase Row Level Security scopes every query to the signed-in user, so bills and documents are private by construction, not just by app logic.

What I Learned

  • Building the OCR/AI integration behind a provider-agnostic interface first — starting with an honest mock — made it straightforward to wire in Claude through a server-side Edge Function later without touching the review flow, storage, or UI.
  • Validating AI output with Zod before it touches the database was a deliberate boundary: treat model output like any other untrusted input.
  • Row Level Security pushed authorization down into the database itself, instead of trusting every API call to remember to filter by user.
  • Supporting Hebrew alongside English meant designing the UI for RTL layout from the start, not retrofitting it after building everything left-to-right first.
  • A try/catch around a dynamic import doesn't catch a throw from the module's own init code — expo-notifications still crashed in Expo Go on Android until the app checked the runtime before importing it at all.
System Status
>_anatoli@portfolio|OS: PortfolioOS (Next.js 16.3.4)Kernel: React 19.2.8CPU: TurbopackProjects: 14Languages: TypeScript, JavaScript, Python, Java