fix(costs): improve cost tracking performance and determinism

- Sort map keys before iteration in createCostDigestBead for deterministic
  output ordering in By Role and By Rig sections (bd-66z6a)
- Batch wisp IDs into single bd show call to fix N+1 query pattern in
  querySessionCostWisps (bd-3hqvs)
- Batch wisp deletion into single subprocess call in deleteSessionCostWisps
  (bd-i8zab)

Part of: bd-1wmwp

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
onyx
2026-01-09 00:25:53 -08:00
committed by beads/crew/giles
parent afff85cdff
commit 64b58b31ab

View File

@@ -1003,29 +1003,27 @@ func querySessionCostWisps(targetDate time.Time) ([]CostEntry, error) {
return nil, nil
}
// Get full details for each wisp to check event_kind and payload
// Batch all wisp IDs into a single bd show call to avoid N+1 queries
showArgs := []string{"show", "--json"}
for _, wisp := range wispList.Wisps {
showArgs = append(showArgs, wisp.ID)
}
showCmd := exec.Command("bd", showArgs...)
showOutput, err := showCmd.Output()
if err != nil {
return nil, fmt.Errorf("showing wisps: %w", err)
}
var events []SessionEvent
if err := json.Unmarshal(showOutput, &events); err != nil {
return nil, fmt.Errorf("parsing wisp details: %w", err)
}
var sessionCostWisps []CostEntry
targetDay := targetDate.Format("2006-01-02")
for _, wisp := range wispList.Wisps {
// Get full wisp details
showCmd := exec.Command("bd", "show", wisp.ID, "--json")
showOutput, err := showCmd.Output()
if err != nil {
continue
}
var events []SessionEvent
if err := json.Unmarshal(showOutput, &events); err != nil {
continue
}
if len(events) == 0 {
continue
}
event := events[0]
for _, event := range events {
// Filter for session.ended events only
if event.EventKind != "session.ended" {
continue
@@ -1075,17 +1073,27 @@ func createCostDigestBead(digest CostDigest) (string, error) {
if len(digest.ByRole) > 0 {
desc.WriteString("## By Role\n")
for role, cost := range digest.ByRole {
roles := make([]string, 0, len(digest.ByRole))
for role := range digest.ByRole {
roles = append(roles, role)
}
sort.Strings(roles)
for _, role := range roles {
icon := constants.RoleEmoji(role)
desc.WriteString(fmt.Sprintf("- %s %s: $%.2f\n", icon, role, cost))
desc.WriteString(fmt.Sprintf("- %s %s: $%.2f\n", icon, role, digest.ByRole[role]))
}
desc.WriteString("\n")
}
if len(digest.ByRig) > 0 {
desc.WriteString("## By Rig\n")
for rig, cost := range digest.ByRig {
desc.WriteString(fmt.Sprintf("- %s: $%.2f\n", rig, cost))
rigs := make([]string, 0, len(digest.ByRig))
for rig := range digest.ByRig {
rigs = append(rigs, rig)
}
sort.Strings(rigs)
for _, rig := range rigs {
desc.WriteString(fmt.Sprintf("- %s: $%.2f\n", rig, digest.ByRig[rig]))
}
desc.WriteString("\n")
}
@@ -1138,7 +1146,9 @@ func deleteSessionCostWisps(targetDate time.Time) (int, error) {
}
targetDay := targetDate.Format("2006-01-02")
deletedCount := 0
// Collect all wisp IDs that match our criteria
var wispIDsToDelete []string
for _, wisp := range wispList.Wisps {
// Get full wisp details to check if it's a session.ended event
@@ -1184,14 +1194,21 @@ func deleteSessionCostWisps(targetDate time.Time) (int, error) {
continue
}
// Delete using bd mol burn (for ephemeral wisps)
burnCmd := exec.Command("bd", "mol", "burn", wisp.ID)
if burnErr := burnCmd.Run(); burnErr == nil {
deletedCount++
}
wispIDsToDelete = append(wispIDsToDelete, wisp.ID)
}
return deletedCount, nil
if len(wispIDsToDelete) == 0 {
return 0, nil
}
// Batch delete all wisps in a single subprocess call
burnArgs := append([]string{"mol", "burn", "--force"}, wispIDsToDelete...)
burnCmd := exec.Command("bd", burnArgs...)
if burnErr := burnCmd.Run(); burnErr != nil {
return 0, fmt.Errorf("batch burn failed: %w", burnErr)
}
return len(wispIDsToDelete), nil
}
// runCostsMigrate migrates legacy session.ended beads to the new architecture.