83 lines
2.4 KiB
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
|
|
}
|