Files
gogobee/internal/plugin/proddb_daily_report_test.go
prosolis 35fa2b8323 Adv 2.0 daily report: migrate to D&D-native render + unified activity source
Two longstanding warts surfaced when the user inspected today's TwinBee
daily report:

1. Active players were rendering "Acted today — no log recorded." The
   adventure_activity_log path only captures !rest now; harvest/zone/
   expedition action logging was lost during Phase R/L migration. The
   stale HasActedToday() check fired on fossil harvest_actions_used
   counters that nothing modern increments.

2. Per-player headline still rendered legacy four-skill line ("Combat
   Lv.X | Mining Lv.Y | Forage Lv.Z | Fishing Lv.W"). No D&D level,
   race, class, or HP — discoverability gap for the layer that's now
   canonical.

Fix both:

- adventure_activity_unified.go: new loadAdvDailyActivity(date) unions
  dnd_zone_run (zone runs touched today) + dnd_expedition_log
  (expedition activity rolled up per expedition) + legacy
  adventure_activity_log (mostly !rest). One source of truth.

- adventure_render.go: AdvPlayerDaySummary gains HasDnDChar/DnDLevel/
  DnDRace/DnDClass/HPCurrent/HPMax. New advPlayerHeadline helper
  renders "Lv.N Race Class — HP cur/max" when a dnd_character row
  exists, falls back to the legacy four-skill line plus an inline
  "(no D&D sheet — run !setup)" nudge for accounts that haven't
  completed setup yet. titleizeDnDToken("half_elf") → "Half-Elf".

- adventure_scheduler.go: postDailySummary swaps loadAdvLogsForDate
  for loadAdvDailyActivity. IsResting now derives from "len(acts)==0"
  rather than the fossil HasActedToday() counter check, so dead
  harvest_actions_used bytes can't trip the active-player branch.

- proddb_daily_report_test.go: renders the report against a copy of
  data/gogobee.db and prints it for visual confirmation. Verified
  output for 2026-05-09 shows real zone-run outcomes (Withdrew from
  Sunken Temple at room 1/6, etc.) instead of the previous "no log
  recorded" placeholder.

go vet + go test ./... clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:23 -07:00

102 lines
2.6 KiB
Go

package plugin
import (
"os"
"path/filepath"
"testing"
"time"
"gogobee/internal/db"
)
// TestProdDB_DailyReportPreview renders the Adv 2.0 daily summary against a
// copy of the prod DB and prints it. Manual smoke for the D&D-aware
// renderer.
func TestProdDB_DailyReportPreview(t *testing.T) {
src := "/home/reala-misaki/git/gogobee/data/gogobee.db"
if _, err := os.Stat(src); err != nil {
t.Skip("prod db not present")
}
dir := t.TempDir()
dst := filepath.Join(dir, "gogobee.db")
copyTestFile(t, src, dst)
db.Close()
if err := db.Init(dir); err != nil {
t.Fatalf("db.Init: %v", err)
}
t.Cleanup(db.Close)
bootstrapPlayerMetaFromLegacy()
chars, err := loadAllAdvCharacters()
if err != nil {
t.Fatalf("loadAllAdvCharacters: %v", err)
}
// Use the most recent activity date in the DB so we render against real
// data instead of an empty "today".
now := time.Now().UTC()
dateKey := now.Format("2006-01-02")
if acts, _ := loadAdvDailyActivity(dateKey); len(acts) == 0 {
// No activity today; walk back up to 7 days for a date that has rows.
for i := 1; i <= 7; i++ {
cand := now.AddDate(0, 0, -i).Format("2006-01-02")
if acts, _ := loadAdvDailyActivity(cand); len(acts) > 0 {
dateKey = cand
break
}
}
}
t.Logf("rendering report for date=%s", dateKey)
todayActs, _ := loadAdvDailyActivity(dateKey)
activityMap := make(map[string][]AdvDailyActivity)
for uid, e := range todayActs {
activityMap[string(uid)] = e
}
var players []AdvPlayerDaySummary
for _, c := range chars {
dispName, _ := loadDisplayName(c.UserID)
ps := AdvPlayerDaySummary{
DisplayName: dispName,
Level: c.CombatLevel,
MiningSkill: c.MiningSkill,
ForagingSkill: c.ForagingSkill,
FishingSkill: c.FishingSkill,
}
if dnd, _ := LoadDnDCharacter(c.UserID); dnd != nil {
ps.HasDnDChar = true
ps.DnDLevel = dnd.Level
ps.DnDRace = string(dnd.Race)
ps.DnDClass = string(dnd.Class)
ps.HPCurrent = dnd.HPCurrent
ps.HPMax = dnd.HPMax
}
acts := activityMap[string(c.UserID)]
if !c.Alive {
ps.IsDead = true
ps.DeathSource = c.DeathSource
ps.DeathLocation = c.DeathLocation
if c.DeadUntil != nil {
ps.DeadUntil = c.DeadUntil.Format("15:04") + " UTC"
}
} else if len(acts) == 0 {
ps.IsResting = true
ps.SummaryLine = "Stayed in town. The world's still here tomorrow."
} else {
last := acts[len(acts)-1]
ps.Activity = last.Activity
ps.Location = last.Location
ps.Outcome = last.Outcome
ps.SummaryLine = last.Summary
}
players = append(players, ps)
}
out := renderAdvDailySummary(dateKey, nil, TwinBeeRewardSummary{}, players, "")
t.Logf("\n%s", out)
}