Files
box-box/internal/store/coverage.go

118 lines
3.3 KiB
Go

package store
import (
"database/sql"
"fmt"
)
// CoverageEntry represents the coverage status for a single dataset of a session.
type CoverageEntry struct {
Status string
ErrorMsg string
RowCount int
UpdatedAt string
}
// SessionCoverageRow represents a joined session coverage record for reporting.
type SessionCoverageRow struct {
MeetingKey int
MeetingName string
SessionKey int
SessionName string
Dataset string
Status string
ErrorMsg string
RowCount int
UpdatedAt string
}
// UpsertCoverage inserts or updates a session coverage record.
func (s *Store) UpsertCoverage(sessionKey int, dataset string, status string, rowCount int, errMsg string) error {
_, err := s.db.Exec(`
INSERT INTO session_coverage (session_key, dataset, status, row_count, error_msg, updated_at)
VALUES (?, ?, ?, ?, ?, datetime('now'))
ON CONFLICT(session_key, dataset) DO UPDATE SET
status = excluded.status,
row_count = excluded.row_count,
error_msg = excluded.error_msg,
updated_at = excluded.updated_at
`, sessionKey, dataset, status, rowCount, nullString(errMsg))
if err != nil {
return fmt.Errorf("upsert coverage: %w", err)
}
return nil
}
// GetSessionCoverage fetches the coverage statuses for all datasets of a given session.
func (s *Store) GetSessionCoverage(sessionKey int) (map[string]CoverageEntry, error) {
rows, err := s.db.Query(`
SELECT dataset, status, row_count, error_msg, updated_at
FROM session_coverage
WHERE session_key = ?
`, sessionKey)
if err != nil {
return nil, fmt.Errorf("get session coverage: %w", err)
}
defer rows.Close()
coverage := make(map[string]CoverageEntry)
for rows.Next() {
var dataset string
var entry CoverageEntry
var errMsg sql.NullString
if err := rows.Scan(&dataset, &entry.Status, &entry.RowCount, &errMsg, &entry.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan session coverage: %w", err)
}
entry.ErrorMsg = errMsg.String
coverage[dataset] = entry
}
return coverage, rows.Err()
}
// GetSeasonCoverage returns coverage records for all sessions of a given year.
func (s *Store) GetSeasonCoverage(year int) ([]SessionCoverageRow, error) {
rows, err := s.db.Query(`
SELECT
m.meeting_key,
m.meeting_name,
s.session_key,
s.session_name,
COALESCE(c.dataset, '') as dataset,
COALESCE(c.status, 'pending') as status,
COALESCE(c.error_msg, '') as error_msg,
COALESCE(c.row_count, 0) as row_count,
COALESCE(c.updated_at, '') as updated_at
FROM sessions s
JOIN meetings m ON s.meeting_key = m.meeting_key
LEFT JOIN session_coverage c ON s.session_key = c.session_key
WHERE m.year = ?
ORDER BY m.date_start ASC, m.meeting_key ASC, s.date_start ASC, s.session_key ASC
`, year)
if err != nil {
return nil, fmt.Errorf("get season coverage query: %w", err)
}
defer rows.Close()
var coverageRows []SessionCoverageRow
for rows.Next() {
var r SessionCoverageRow
var errMsg sql.NullString
if err := rows.Scan(
&r.MeetingKey,
&r.MeetingName,
&r.SessionKey,
&r.SessionName,
&r.Dataset,
&r.Status,
&r.ErrorMsg,
&r.RowCount,
&r.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan season coverage row: %w", err)
}
r.ErrorMsg = errMsg.String
coverageRows = append(coverageRows, r)
}
return coverageRows, rows.Err()
}