Add a tags screen
Screen 8 lists every distinct tag -- the ones the index holds and the ones rules.toml names -- with how many transactions carry it and the rules that write it, each by file position with the transactions it wins. It is where near-duplicate tags and tags several rules feed show up, and a rule beaten by a more specific one reads (0) there as it does on the rules screen. A tag no rule names any more says the next retag clears it. The transfer label is not a tag and is never listed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("GET /api/report", s.read(s.report))
|
||||
|
||||
mux.HandleFunc("GET /api/rules", s.read(s.ruleList))
|
||||
mux.HandleFunc("GET /api/tags", s.read(s.tagList))
|
||||
mux.HandleFunc("POST /api/rules/preview", s.read(s.rulePreview))
|
||||
mux.HandleFunc("POST /api/rules", s.write(s.createRule))
|
||||
mux.HandleFunc("PUT /api/rules/{pos}", s.write(s.editRule))
|
||||
@@ -556,6 +557,55 @@ func (s *Server) ruleList(*http.Request) (any, error) {
|
||||
return map[string]any{"rules": rows}, nil
|
||||
}
|
||||
|
||||
type tagRule struct {
|
||||
Pos int `json:"pos"` // file position, as the rules screen numbers it
|
||||
Pattern string `json:"pattern"`
|
||||
Account string `json:"account"`
|
||||
Usage int `json:"usage"`
|
||||
}
|
||||
|
||||
type tagEntry struct {
|
||||
Tag string `json:"tag"`
|
||||
Transactions int `json:"transactions"`
|
||||
Rules []tagRule `json:"rules"`
|
||||
}
|
||||
|
||||
// tagList is every distinct tag — the ones the index holds and the ones
|
||||
// rules.toml names — with how many transactions carry it and the rules that
|
||||
// write it. A tag is only ever what a rule wrote, so the transfer label is
|
||||
// never among them, and a tag no rule names any more is one a retag will
|
||||
// clear.
|
||||
func (s *Server) tagList(*http.Request) (any, error) {
|
||||
txns, err := s.db.Transactions(store.Filter{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
counts := map[string]int{}
|
||||
for _, t := range txns {
|
||||
if t.RuleTag != "" {
|
||||
counts[t.RuleTag]++
|
||||
}
|
||||
}
|
||||
rules := map[string][]tagRule{}
|
||||
usage := s.engine.Usage(txns)
|
||||
for i, r := range s.engine.Rules() {
|
||||
rules[r.Tag] = append(rules[r.Tag], tagRule{Pos: i, Pattern: rulePattern(r), Account: r.Account, Usage: usage[i]})
|
||||
}
|
||||
tags, err := s.knownTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]tagEntry, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
rs := rules[tag]
|
||||
if rs == nil {
|
||||
rs = []tagRule{}
|
||||
}
|
||||
rows = append(rows, tagEntry{Tag: tag, Transactions: counts[tag], Rules: rs})
|
||||
}
|
||||
return map[string]any{"tags": rows}, nil
|
||||
}
|
||||
|
||||
// rulePattern renders whichever patterns a rule sets, labelled so a type rule
|
||||
// is not mistaken for a description one.
|
||||
func rulePattern(r config.Rule) string {
|
||||
|
||||
Reference in New Issue
Block a user