Files
finansePRO/.paul/plans/20260706-2046-import-paragonow/SUMMARY.md
T
2026-07-12 19:21:35 +02:00

6.1 KiB

plan_id, title, completed, storage, quality_radar
plan_id title completed storage quality_radar
20260706-2046-import-paragonow Półautomatyczny import paragonów (AI parsowanie + uczone podpowiedzi kategorii) 2026-07-06T22:30 plan-first degraded

Summary: Półautomatyczny import paragonów

Objective

Umożliwić wgranie paragonu (jpg/png, pdf, json), automatyczne odczytanie pozycji przez AI (OpenAI), dopasowanie ich do kategorii wydatków z uczonymi podpowiedziami, przegląd/korektę przez użytkownika i zaksięgowanie jako operacje pogrupowane po kategorii (1 operacja expense / kategoria). Nic nie trafia do operations bez zatwierdzenia; podpowiedzi kategorii nigdy nie księgują się automatycznie.

What Was Built

Area Result
Baza 3 tabele: receipts, receipt_items (+ display_name), receipt_category_map; kolumna operations.receipt_id (FK SET NULL)
Modele ReceiptModel (withCounts), ReceiptItemModel (forReceipt), ReceiptCategoryMapModel (normalize/suggest/remember)
Parser AI App\Libraries\ReceiptParser — OpenAI Responses API (/v1/responses + input_file), model o4-mini, prompt świadomy kolumn paragonu, dwie nazwy (name surowa + display ładna)
Kontroler Receipts (upload→parse→review→confirm→history→file→delete) + trasy receipts/*
Widoki receipts/{new,review,index,show}.php; przegląd z edytowalnymi nazwami/kwotami i podglądem grupowania na żywo; link „Import paragonów" w nawigacji
Kategorie CategoryModel::tree() rozszerzony o path_label („Nadrzędna → Podrzędna") — jednoznaczność w podglądzie

Files Modified

  • app/Database/Migrations/2026-07-06-2000{01..05}_*.php - schemat modułu + display_name
  • app/Models/{ReceiptModel,ReceiptItemModel,ReceiptCategoryMapModel}.php - nowe modele
  • app/Models/OperationModel.php - allowedFields += receipt_id
  • app/Models/CategoryModel.php - path_label w tree()
  • app/Libraries/ReceiptParser.php - warstwa OpenAI (Responses API, o4-mini, name+display)
  • app/Controllers/Receipts.php - pełny przepływ, walidacja bez finfo, transakcja, uczenie na surowej nazwie
  • app/Config/Routes.php - grupa receipts/*
  • app/Views/receipts/*.php, app/Views/layout/main.php - UI + nawigacja
  • .env - komentarz openai.model (domyślnie o4-mini)

Acceptance Criteria Results

Criterion Status Evidence
AC-1 Upload jpg/png/pdf/json Pass /receipts/new HTTP 200; walidacja rozszerzeń bez finfo; plik w writable/uploads/receipts/
AC-2 AI parsuje pozycje + kategorie Pass Responses API + o4-mini odczytał realny PDF (15 poz., suma = total 68,74); błąd API → status failed + error_msg
AC-3 Nauczone dopasowanie ma priorytet i tylko podpowiada Pass suggest() (surowa nazwa) > propozycja AI; brak zapisu do operations przed confirm
AC-4 Przegląd grupuje po kategorii + edycja Pass Edytowalne nazwa/kwota, podgląd sum na żywo, path_label rozróżnia kategorie
AC-5 Zatwierdzenie: 1 operacja/kategoria + uczenie Pass Grupowanie sum → OperationModel::insert z receipt_id; remember(surowa_nazwa, kategoria); blokada podwójnego księgowania; transakcja DB
AC-6 Historia + podgląd skanu Pass /receipts HTTP 200; Receipts::file serwuje spoza public/ pod filtrem auth

Verification Results

Check Result Notes
php -l (wszystkie pliki) Pass Zero błędów składni
Self-check normalize+grupowanie Pass assert OK
Migracja produkcyjna Pass DDL 3 tabel + receipt_id + display_name zastosowane na zdalnej bazie; wpisy w migrations (batch 3 i 4)
UAT /receipts, /receipts/new Pass HTTP 200 po zalogowaniu (curl)
UAT realny paragon (PDF) Pass Użytkownik potwierdził: pozycje/kwoty poprawne (poza wierszem rabatu — korekta ręczna), ładne nazwy OK

Quality Radar Results

Status: degraded (brak indeksu codebase-memory-mcp; jscpd/ast-grep wyłączone polityką)

  • New risks: OCR paragonów niedeterministyczny na wierszach rabatu/gęstym druku — świadomie kryty edytowalnymi kwotami (pół-automat). Koszt tokenów OpenAI per import (o4-mini rozumujący).
  • Resolved risks: zapis operacji tylko przez OperationModel (bez duplikacji walidacji); klucz OpenAI z .env, nie logowany.
  • Deferred risks: pełna indeksacja codebase-memory-mcp ($paul-map-codebase); normalizacja nazw = dokładne dopasowanie (rozbudować gdy za wąska).
  • Raw outputs: .paul/codebase/{impact_map,quality_risks,tooling_status}.md

Deviations

  • Mechanizm AI zmieniony w trakcie: chat-completions file_data NIE dostarczał treści PDF (model halucynował) → przełączono na Responses API /v1/responses + input_file.
  • Model: plan zakładał gpt-4o-mini; okazał się za słaby na gęsty druk → domyślnie o4-mini (rozumujący, najlepszy OCR + rabaty, tańszy niż gpt-4o). Testy: o4-mini suma pozycji = total co do grosza; gpt-4o/4.1 gorzej.
  • Bez finfo: hostido ma wyłączone fileinfo → własna nazwa pliku + MIME z rozszerzenia, walidacja bez ext_in.
  • Dwie nazwy pozycji (dodane na życzenie): name (surowa, klucz mapowania) + display_name (ładna, do wyświetlania/opisu).
  • PDF = obraz (brak warstwy tekstowej) → OCR modelu, nie ekstrakcja tekstu.
  • Migracje stosowane ręcznie na zdalnej bazie (deploy hostido nie uruchamia spark migrate).

Key Decisions / Patterns

  • Przy paragonach liczy się rozumowanie modelu, nie rozmiar (o4-mini > gpt-4o/4.1).
  • Pół-automat: AI proponuje, człowiek weryfikuje/koryguje kwoty i nazwy przed księgowaniem.
  • Uczenie na surowej nazwie z paragonu (stabilny klucz między wizytami), wyświetlanie po ładnej.
  • Jedno źródło prawdy operacji — OperationModel; receipt_id łączy operacje z paragonem (FK SET NULL).

Follow-up

  • Opcjonalnie: zapamiętywanie ładnej nazwy w receipt_category_map (spójny display przy powtórkach).
  • Rozważyć limit/licznik kosztów OpenAI, jeśli wolumen wzrośnie.
  • $paul-map-codebase dla pełnego indeksu przed kolejnym większym planem.