P R O J E K T YK O N T A K T

[08]

BANKING TRACKER

TIER A · ROK 2026 · STATUS: DZIAŁA · JĘZYKI: TYPESCRIPT

Integracja regulowanego API bankowego, OCR paragonów i reguły uczące się

[FIG. 1] MISJA

Integruję regulowane API bankowe (Open Banking, PSD2, Enable Banking) w trybie wyłącznie odczytu i buduję na nich produkt, który realnie prowadzi domowe finanse: transakcje i salda z banku (Pekao) płyną same, a nowe wpisy lądują w skrzynce do „podpisania” jednym tapem. Kategoryzację projektuję jako ścieżkę eskalacji człowiek → reguły → model: każda moja ręczna korekta tworzy regułę, więc AI przejmuje pracę dopiero wtedy, gdy nauczy się z moich decyzji, i zostawia mi wyjątki. Zdjęcie paragonu rozbijam OCR-em na modelu wizyjnym: zdjęcie ląduje w prywatnym Blobie, a model dzieli je na pozycje z cenami przy zamkniętej liście 13 grup produktowych. Model dobieram do zadania pod kątem kosztu, nie prestiżu, i umiem tę decyzję uzasadnić — przy tysiącach paragonów różnica w cenie wywołania decyduje o tym, czy funkcja w ogóle ma sens. Dostarczam to jako PWA instalowaną na telefonie z pominięciem sklepu, z uwierzytelnianiem własnym PIN-em i JWT (świadomie wymieniłem wpiętego już Clerka — dla aplikacji jednoosobowej własne rozwiązanie jest prostsze i bezpieczniejsze w utrzymaniu). Tokeny bankowe są szyfrowane AES-256-GCM i nigdy nie leżą plaintextem, a na wszystkim siedzi budżet, cele, subskrypcje i rozliczenia z ludźmi. Działa na produkcji, zweryfikowane end-to-end na ~478 realnych transakcjach; pod spodem Next.js 16, Drizzle + Neon Postgres, 20 tabel i 40 Server Actions.

[FIG. 2] ARCHITEKTURA

najedź na blok, by zobaczyć opis

[FIG. 3] WYZWANIA

[+][CH-01]

Dokumentacja Enable Banking kłamała trzykrotnie, a każde kłamstwo odkrywał dopiero żywy bank. Po pierwsze: API przyjmuje snake_case małymi literami (valid_until, redirect_url), gdy dokumentacja sugerowała camelCase — a odpowiedź /auth zwraca pole url, nie authorizationUri z docs, więc klient czyta defensywnie wszystkie warianty. Po drugie: przy paginacji continuation_key bank zwraca 422, jeśli nie powtórzy się TYCH SAMYCH parametrów date_from — pierwsza wersja wysyłała parametry alternatywnie i kolejne strony padały. Po trzecie: Pekao odrzuca żądanie 90 dni historii błędem 400 — rozwiązałem to adaptacyjnym schodzeniem 90/60/30/14/7 dni, aż bank zaakceptuje zakres. Wisienka regulacyjna: PSD2 pozwala limitować nieinicjowane przez klienta pobrania do 4 dziennie, co auto-sync wyczerpywał od rana — przekazanie realnego IP i User-Agenta użytkownika jako nagłówków PSU legalnie zmienia klasyfikację żądania na inicjowane przez klienta i znosi limit, a IP jest walidowane regexem, zanim poleci do banku.

[+][CH-02]

Pierwsza wersja logowania PIN-em miała „blokadę po 4 próbach” i wyglądała rozsądnie — dopóki nie przeprowadziłem na własnym kodzie formalnego audytu bezpieczeństwa (20 findingów w czterech poziomach). Audyt wykrył dwa poważne defekty naraz: licznik prób był nieatomowy — SELECT, +1 w JavaScripcie, UPDATE — więc N równoległych żądań czytało ten sam stan i zapisywało 1, co pozwalało wyścigiem obejść lockout i brute-force'ować całą przestrzeń 10 000 PIN-ów partiami; a „poprawna” trwała blokada okazała się gorsza od ataku, bo ktokolwiek wyśle 4 błędne PIN-y, zamienia aplikację w cegłę wymagającą redeployu (self-DoS). Model docelowy: inkrementacja licznika atomowo po stronie bazy, porównanie PIN-u w czasie stałym (timingSafeEqual) i rosnący backoff 5 s → 5 min zamiast blokady — atakujący czeka wykładniczo, właściciel po pomyłce chwilę. Każdy finding audytu ma id cytowane w naprawach, więc trasa finding→fix jest w pełni audytowalna.

[+][CH-03]

Automatyczna kategoryzacja od pierwszego dnia byłaby albo głupia, albo droga — zamiast tego zaprojektowałem drogę „człowiek → reguły → AI”. Najpierw podpisuję transakcje ręcznie, ale każda korekta tworzy regułę („Żabka → Codzienne”), więc znane sklepy szybko schodzą same; AI wchodzi na końcu i podpisuje według wyuczonych reguł, a do mnie trafiają tylko wyjątki. System zdobywa zaufanie stopniowo — dokładnie w tempie, w jakim na nie zapracował.

[+][CH-04]

Saldo konta kłamie: nie uwzględnia czynszu, który zejdzie za tydzień, niezapłaconych subskrypcji ani pieniędzy zaklepanych na cele. Rozdzieliłem pieniądze na trzy warstwy — konta realne (salda z banku), koperty budżetowe (kontrola wydatków) i cele/rezerwy (nietykalne) — a „naprawdę dostępne” to salda minus zobowiązania i środki celowe. Jedna liczba, której mogę zaufać przy kasie, okazała się najtrudniejszym modelem danych w całej aplikacji.

[FIG. 4] WARSTWA AI

AI wchodzi tam, gdzie boli: Claude Haiku 4.5 (vision, przez Vercel AI Gateway) czyta zdjęcia paragonów na pozycje z cenami, a kategoryzacja transakcji zmierza ścieżką „człowiek → reguły → AI” — model przejmuje podpisywanie według reguł wyuczonych z moich korekt. Wybrałem Haiku świadomie: OCR paragonu to zadanie proste i częste, więc decyduje koszt pojedynczego wywołania, nie maksymalna inteligencja modelu.

[FIG. 2A] PARAGON → POZYCJE

MLEKO 2%4,99
CHLEB ŻYTNI7,50
MASŁO EXTRA8,99
JABŁKA 1KG5,49

PARAGON CZEKA NA SKAN

  • ŻABKA −23,50
  • ORLEN −180,00
  • NETFLIX −43,00

każdą transakcję podpisujesz ręcznie — inbox rośnie

[FIG. 6] STACK & LINKI

NEXT.JS 16REACT 19DRIZZLE ORMNEON POSTGRESENABLE BANKING (PSD2)VERCEL AI GATEWAYCLAUDE HAIKU 4.5 (VISION OCR)RECHARTSJOSE (JWT+PIN)PWAVERCEL
KOMPETENCJE W TYM PROJEKCIE: OCR · VISION · PSD2 · AI GATEWAY · NEXT.JS