Batch Analyse requests and survive opaque provider schema budgets
Analyse now classifies up to ten same-kind transactions per provider request: the registry and history travel once per batch, so a thousand-row backfill costs about a hundred paced requests instead of a thousand. The answer schema appears once — an array item carrying an enum-bound ref — because providers meter strict schemas by token cost: duplicating registry enums per row, or bounding arrays with minItems/maxItems that Gemini expands per element, rejects real registries with a bare HTTP 400. Row count, duplicate refs, duplicate tags and taxonomy bounds are all enforced server-side instead, and a request still rejected outright halves until accepted, remembering the working size for the run. Batch requests scale the HTTP budget by row count, chunk failures cannot abort a run whose later rows succeeded, and rows resolved against one snapshot share one minted merchant. Measured on a real 165-row month over a zero-data-retention route: 165 analysed, 152 proposals, 0 errors, 17 requests, under 8 minutes. Fresh installs default to google/gemini-3.8-flash, the model that demonstrably honors strict structured outputs over a ZDR route. Preview changes now carry counterparty, amount and currency, and the review list shows the amount with a counterparty fallback for banks that leave descriptions empty.
This commit is contained in:
@@ -54,6 +54,12 @@ func addSecret(secrets map[string]bool, value string) {
|
||||
// facts being classified, and configured private names. Counterparties and
|
||||
// stored transaction facts are deliberately not secrets.
|
||||
func redactor(d domain.Dataset, f domain.Facts, private []string) func(string) string {
|
||||
return redactorFacts(d, []domain.Facts{f}, private)
|
||||
}
|
||||
|
||||
// redactorFacts is the batch form: one filter whose secrets cover every row
|
||||
// sharing the request.
|
||||
func redactorFacts(d domain.Dataset, rows []domain.Facts, private []string) func(string) string {
|
||||
secrets := map[string]bool{}
|
||||
for _, a := range d.Accounts {
|
||||
addSecret(secrets, a.ID)
|
||||
@@ -63,8 +69,10 @@ func redactor(d domain.Dataset, f domain.Facts, private []string) func(string) s
|
||||
// sent as a field and its text is own-identity data, like PrivateNames.
|
||||
addSecret(secrets, a.DisplayName)
|
||||
}
|
||||
for _, value := range []string{f.ID, f.ExternalID, f.Fingerprint, f.CounterpartyIBAN} {
|
||||
addSecret(secrets, value)
|
||||
for _, f := range rows {
|
||||
for _, value := range []string{f.ID, f.ExternalID, f.Fingerprint, f.CounterpartyIBAN} {
|
||||
addSecret(secrets, value)
|
||||
}
|
||||
}
|
||||
for _, name := range private {
|
||||
addSecret(secrets, name)
|
||||
|
||||
Reference in New Issue
Block a user