package db import ( "fmt" "time" ) type TierOutcomeStats struct { Tier string Total int Successes int Failures int MaxHashrate float64 } func (d *Database) InsertTierOutcome(agentID, fingerprintKey, tier string, ok bool, hashrate float64, phase string) error { okInt := 0 if ok { okInt = 1 } _, err := d.Exec( `INSERT INTO tier_outcomes (agent_id, fingerprint_key, tier, ok, hashrate, phase, created_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`, agentID, fingerprintKey, tier, okInt, hashrate, phase, ) return err } func (d *Database) UpsertAgentStrategyCache(agentID, fingerprintKey, strategyJSON string) error { _, err := d.Exec( `INSERT INTO agent_strategy_cache (agent_id, fingerprint_key, strategy_json, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(agent_id) DO UPDATE SET fingerprint_key = excluded.fingerprint_key, strategy_json = excluded.strategy_json, updated_at = CURRENT_TIMESTAMP`, agentID, fingerprintKey, strategyJSON, ) return err } func (d *Database) ListAgentStrategyFingerprints() (map[string]string, error) { rows, err := d.Query(`SELECT agent_id, fingerprint_key FROM agent_strategy_cache`) if err != nil { return nil, err } defer rows.Close() out := make(map[string]string) for rows.Next() { var agentID, fpKey string if err := rows.Scan(&agentID, &fpKey); err != nil { return nil, err } out[agentID] = fpKey } return out, rows.Err() } func (d *Database) AggregateTierOutcomes(fingerprintKey, goos string) ([]TierOutcomeStats, error) { stats, err := d.aggregateTierOutcomesWhere(`fingerprint_key = ?`, fingerprintKey) if err != nil { return nil, err } if len(stats) > 0 { return stats, nil } if goos == "" { return nil, nil } return d.aggregateTierOutcomesWhere(`fingerprint_key LIKE ?`, goos+"|%") } func (d *Database) aggregateTierOutcomesWhere(whereClause string, arg interface{}) ([]TierOutcomeStats, error) { query := fmt.Sprintf(` SELECT tier, COUNT(*) AS total, SUM(CASE WHEN ok = 1 THEN 1 ELSE 0 END) AS successes, SUM(CASE WHEN ok = 0 THEN 1 ELSE 0 END) AS failures, MAX(CASE WHEN ok = 1 THEN hashrate ELSE 0 END) AS max_hashrate FROM tier_outcomes WHERE %s GROUP BY tier`, whereClause) rows, err := d.Query(query, arg) if err != nil { return nil, err } defer rows.Close() var out []TierOutcomeStats for rows.Next() { var s TierOutcomeStats if err := rows.Scan(&s.Tier, &s.Total, &s.Successes, &s.Failures, &s.MaxHashrate); err != nil { return nil, err } out = append(out, s) } return out, rows.Err() } func (d *Database) PruneTierOutcomesOlderThan(cutoff time.Time) (int64, error) { res, err := d.Exec(`DELETE FROM tier_outcomes WHERE created_at < ?`, cutoff) if err != nil { return 0, err } return res.RowsAffected() }