Files
dobotai-platform/internal/store/postgres/coverage.go

83 lines
2.4 KiB
Go

package postgres
import (
"context"
"crypto/rand"
"fmt"
"github.com/jackc/pgx/v5"
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/coverage"
)
// SaveCoverageDecision implements coverage.Repository. It writes the decision
// and, for uncovered verdicts, atomically creates a pending support case in a
// single transaction. Both writes are idempotent via ON CONFLICT DO NOTHING.
func (s *Store) SaveCoverageDecision(ctx context.Context, d coverage.Decision) error {
if s == nil || s.pool == nil {
return coverage.ErrRepository
}
decisionID, err := newUUID()
if err != nil {
return fmt.Errorf("coverage decision id: %w", err)
}
tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
return fmt.Errorf("coverage tx begin: %w", err)
}
defer func() { _ = tx.Rollback(ctx) }()
var persistedDecisionID string
err = tx.QueryRow(ctx, `INSERT INTO coverage_decisions
(id, tenant_key, channel, turn_id, evaluator_version, verdict, reason_code, reference_count, top_score)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)
ON CONFLICT (tenant_key, channel, turn_id)
DO UPDATE SET id=coverage_decisions.id
RETURNING id`,
decisionID, d.TenantKey, d.Channel, d.TurnID,
d.EvaluatorVersion, d.Verdict, d.ReasonCode, d.ReferenceCount, d.TopScore).Scan(&persistedDecisionID)
if err != nil {
return fmt.Errorf("coverage insert decision: %w", err)
}
// Only create support case for uncovered verdicts.
if d.Verdict == coverage.VerdictUncovered {
caseID, err := newUUID()
if err != nil {
return fmt.Errorf("coverage case id: %w", err)
}
_, err = tx.Exec(ctx, `INSERT INTO support_cases
(id, tenant_key, channel, turn_id, coverage_decision_id, status, faq_export_status)
VALUES($1,$2,$3,$4,$5,$6,$7)
ON CONFLICT (tenant_key, channel, turn_id) DO NOTHING`,
caseID, d.TenantKey, d.Channel, d.TurnID, persistedDecisionID, "pending", faqExportStatus(s.faqExportEnabled))
if err != nil {
return fmt.Errorf("coverage insert support case: %w", err)
}
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("coverage tx commit: %w", err)
}
return nil
}
func faqExportStatus(enabled bool) string {
if enabled {
return "pending"
}
return "not_applicable"
}
// newUUID produces a 32-char hex id.
func newUUID() (string, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}
return fmt.Sprintf("%08x%04x%04x%04x%012x",
b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
}