6.1 KiB
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_nameapp/Models/{ReceiptModel,ReceiptItemModel,ReceiptCategoryMapModel}.php- nowe modeleapp/Models/OperationModel.php-allowedFields+=receipt_idapp/Models/CategoryModel.php-path_labelwtree()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 nazwieapp/Config/Routes.php- grupareceipts/*app/Views/receipts/*.php,app/Views/layout/main.php- UI + nawigacja.env- komentarzopenai.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_dataNIE 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 bezext_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-codebasedla pełnego indeksu przed kolejnym większym planem.