Use compact classification IDs and extend preview lifetime
This commit is contained in:
@@ -2,7 +2,6 @@ package classification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -32,28 +31,34 @@ func TestBatchClassifiesEveryRowInOneRequest(t *testing.T) {
|
||||
calls := 0
|
||||
c := mockClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
var req struct {
|
||||
Messages []struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if json.NewDecoder(r.Body).Decode(&req) != nil || len(req.Messages) != 2 {
|
||||
w.WriteHeader(400)
|
||||
prompt := decodeClassificationPrompt(t, r)
|
||||
if len(prompt.Transactions) != 2 {
|
||||
t.Errorf("batch prompt missing transactions: %+v", prompt.Transactions)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var prompt struct {
|
||||
Transactions []struct{ Ref, Counterparty, Amount, Currency string } `json:"transactions"`
|
||||
category := categoryRefForPath(t, prompt.Categories, normalize(domain.CategoryPath(d, "cat_food")))
|
||||
merchant, tag := "", ""
|
||||
for _, candidate := range prompt.Merchants {
|
||||
if candidate.Name == "coffee house" {
|
||||
merchant = candidate.ID
|
||||
}
|
||||
}
|
||||
if json.Unmarshal([]byte(req.Messages[1].Content), &prompt) != nil || len(prompt.Transactions) != 2 {
|
||||
t.Errorf("batch prompt missing transactions: %s", req.Messages[1].Content)
|
||||
for _, candidate := range prompt.Tags {
|
||||
if candidate.Name == "daily" {
|
||||
tag = candidate.ID
|
||||
}
|
||||
}
|
||||
if merchant == "" || tag == "" {
|
||||
t.Error("batch prompt lost Coffee House or Daily")
|
||||
}
|
||||
for _, row := range prompt.Transactions {
|
||||
if row.Amount == "" || row.Currency != "EUR" {
|
||||
t.Errorf("row %s lost amount or currency: %+v", row.Ref, row)
|
||||
}
|
||||
}
|
||||
reply(w, `{"transactions":[{"ref":"r1","merchant_id":"mer_coffee","new_merchant":null,"category_id":"cat_food","tag_ids":["tag_daily"],"confidence":"high"},`+
|
||||
`{"ref":"r2","merchant_id":null,"new_merchant":"Kleins Backstube","category_id":"cat_food","tag_ids":[],"confidence":"medium"}]}`)
|
||||
reply(w, `{"transactions":[{"ref":"`+prompt.Transactions[1].Ref+`","merchant_id":null,"new_merchant":"Kleins Backstube","category_id":"`+category+`","tag_ids":[],"confidence":"medium"},`+
|
||||
`{"ref":"`+prompt.Transactions[0].Ref+`","merchant_id":"`+merchant+`","new_merchant":null,"category_id":"`+category+`","tag_ids":["`+tag+`"],"confidence":"high"}]}`)
|
||||
})
|
||||
results := c.ClassifyBatch(context.Background(), []domain.Facts{f1, f2}, d)
|
||||
if calls != 1 {
|
||||
@@ -71,6 +76,8 @@ func TestBatchClassifiesEveryRowInOneRequest(t *testing.T) {
|
||||
if second.NewMerchant == nil || second.NewMerchant.Name != "Kleins Backstube" ||
|
||||
!reflect.DeepEqual(second.NewMerchant.Aliases, []string{"Kleins Backstube"}) ||
|
||||
second.Enrichment.MerchantID != second.NewMerchant.ID ||
|
||||
second.Enrichment.CategoryID != "cat_food" ||
|
||||
len(second.Enrichment.TagIDs) != 0 ||
|
||||
second.Enrichment.Classification.Confidence != "medium" {
|
||||
t.Fatalf("second row lost: %+v", second)
|
||||
}
|
||||
@@ -80,8 +87,10 @@ func TestBatchClassifiesEveryRowInOneRequest(t *testing.T) {
|
||||
func TestBatchIsolatesInvalidRows(t *testing.T) {
|
||||
f1, f2, d := batchRows()
|
||||
c := mockClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
reply(w, `{"transactions":[{"ref":"r1","merchant_id":null,"new_merchant":null,"category_id":"cat_food","tag_ids":[],"confidence":"high"},`+
|
||||
`{"ref":"r2","merchant_id":null,"new_merchant":null,"category_id":"cat_forged","tag_ids":[],"confidence":"high"}]}`)
|
||||
prompt := decodeClassificationPrompt(t, r)
|
||||
category := categoryRefForPath(t, prompt.Categories, normalize(domain.CategoryPath(d, "cat_food")))
|
||||
reply(w, `{"transactions":[{"ref":"`+prompt.Transactions[0].Ref+`","merchant_id":null,"new_merchant":null,"category_id":"`+category+`","tag_ids":[],"confidence":"high"},`+
|
||||
`{"ref":"`+prompt.Transactions[1].Ref+`","merchant_id":null,"new_merchant":null,"category_id":"c999999","tag_ids":[],"confidence":"high"}]}`)
|
||||
})
|
||||
results := c.ClassifyBatch(context.Background(), []domain.Facts{f1, f2}, d)
|
||||
if results[0].Err != nil || results[0].Proposal.Enrichment.CategoryID != "cat_food" {
|
||||
@@ -96,8 +105,10 @@ func TestBatchIsolatesInvalidRows(t *testing.T) {
|
||||
func TestBatchSharesOneMintedMerchant(t *testing.T) {
|
||||
f1, f2, d := batchRows()
|
||||
c := mockClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
reply(w, `{"transactions":[{"ref":"r1","merchant_id":null,"new_merchant":"REWE","category_id":"cat_food","tag_ids":[],"confidence":"high"},`+
|
||||
`{"ref":"r2","merchant_id":null,"new_merchant":"REWE","category_id":"cat_food","tag_ids":[],"confidence":"high"}]}`)
|
||||
prompt := decodeClassificationPrompt(t, r)
|
||||
category := categoryRefForPath(t, prompt.Categories, normalize(domain.CategoryPath(d, "cat_food")))
|
||||
reply(w, `{"transactions":[{"ref":"`+prompt.Transactions[0].Ref+`","merchant_id":null,"new_merchant":"REWE","category_id":"`+category+`","tag_ids":[],"confidence":"high"},`+
|
||||
`{"ref":"`+prompt.Transactions[1].Ref+`","merchant_id":null,"new_merchant":"REWE","category_id":"`+category+`","tag_ids":[],"confidence":"high"}]}`)
|
||||
})
|
||||
results := c.ClassifyBatch(context.Background(), []domain.Facts{f1, f2}, d)
|
||||
if results[0].Err != nil || results[1].Err != nil {
|
||||
@@ -144,19 +155,8 @@ func TestBatchSplitsOnProviderSchemaRejection(t *testing.T) {
|
||||
calls, oversized := 0, 0
|
||||
c := mockClient(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
var req struct {
|
||||
Messages []struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if json.NewDecoder(r.Body).Decode(&req) != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
var prompt struct {
|
||||
Transactions []struct{ Ref string } `json:"transactions"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(req.Messages[1].Content), &prompt)
|
||||
prompt := decodeClassificationPrompt(t, r)
|
||||
category := categoryRefForPath(t, prompt.Categories, normalize(domain.CategoryPath(d, "cat_food")))
|
||||
if len(prompt.Transactions) > 2 {
|
||||
oversized++
|
||||
w.WriteHeader(400)
|
||||
@@ -164,7 +164,7 @@ func TestBatchSplitsOnProviderSchemaRejection(t *testing.T) {
|
||||
}
|
||||
answers := make([]string, 0, len(prompt.Transactions))
|
||||
for _, row := range prompt.Transactions {
|
||||
answers = append(answers, `{"ref":"`+row.Ref+`","merchant_id":null,"new_merchant":null,"category_id":"cat_food","tag_ids":[],"confidence":"high"}`)
|
||||
answers = append(answers, `{"ref":"`+row.Ref+`","merchant_id":null,"new_merchant":null,"category_id":"`+category+`","tag_ids":[],"confidence":"high"}`)
|
||||
}
|
||||
reply(w, `{"transactions":[`+strings.Join(answers, ",")+`]}`)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user