update
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
---
|
||||
plan_id: 20260706-2046-import-paragonow
|
||||
title: Półautomatyczny import paragonów (AI parsowanie + uczone podpowiedzi kategorii)
|
||||
completed: 2026-07-06T22:30
|
||||
storage: plan-first
|
||||
quality_radar: 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.
|
||||
Reference in New Issue
Block a user