All checks were successful
Build and Test / build-and-test (push) Successful in 37s
Add a global, temporal ai_notes list appended to the classifier prompt (seeded once from no-PII defaults, documented in README), managed inline on a new AI tab with a read-only view of the assembled prompt. Every AI-run upload records the browser-round-tripped suggestion blob + model; misreads are derived (final field != AI guess) and reviewed one by one (image + per-field guess-vs-entered + notes-since), attributing which note fixed each or closing unresolved. Update SPEC (new section 10), DESIGN item 15, README, and changelog (0.0.3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
1.8 KiB
Go
69 lines
1.8 KiB
Go
package storage
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func classifyTestStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
s, err := Open(filepath.Join(t.TempDir(), "c.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { s.Close() })
|
|
return s
|
|
}
|
|
|
|
func TestClassifications_InsertListReview(t *testing.T) {
|
|
s := classifyTestStore(t)
|
|
insertReceipt(t, s, "r1")
|
|
|
|
if err := s.InsertClassification("r1", "haiku", `{"model":"haiku"}`, time.Now().UTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rows, err := s.ListUnreviewedClassifications()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(rows) != 1 || rows[0].ReceiptID != "r1" || rows[0].Model != "haiku" {
|
|
t.Fatalf("ListUnreviewedClassifications = %+v", rows)
|
|
}
|
|
if rows[0].FinalAmountCents != 100 { // insertReceipt sets 100 cents
|
|
t.Errorf("joined final amount = %d, want 100", rows[0].FinalAmountCents)
|
|
}
|
|
|
|
// Review it, crediting a note as the fix.
|
|
noteID, _ := s.AddNote("the fix")
|
|
if err := s.MarkClassificationReviewed("r1", "fixed", []int64{noteID}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rows, _ = s.ListUnreviewedClassifications()
|
|
if len(rows) != 0 {
|
|
t.Errorf("reviewed row still in unreviewed queue: %+v", rows)
|
|
}
|
|
got, err := s.GetClassification("r1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !got.Reviewed || got.Resolution != "fixed" || got.ReviewedAt == nil {
|
|
t.Errorf("review state not persisted: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestClassifications_DeletedReceiptDropsFromQueue(t *testing.T) {
|
|
s := classifyTestStore(t)
|
|
insertReceipt(t, s, "r1")
|
|
if err := s.InsertClassification("r1", "m", `{}`, time.Now().UTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.SoftDelete("r1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rows, _ := s.ListUnreviewedClassifications()
|
|
if len(rows) != 0 {
|
|
t.Errorf("soft-deleted receipt should not appear in review queue: %+v", rows)
|
|
}
|
|
}
|