Backend and APIs

C sharp, .NET, and SQL powering collections, fraud, and servicing workflows.

C sharp API for collections QA

public sealed record QaScoreRequest(
    string AccountNumber,
    string AgentId,
    int    Score,
    string Category,
    DateTimeOffset CallDateUtc);

[ApiController]
[Route("api/qa-scores")]
public class QaScoresController : ControllerBase
{
    private readonly QaScoreService _service;
    private readonly ILogger<QaScoresController> _logger;

    public QaScoresController(
        QaScoreService service,
        ILogger<QaScoresController> logger)
    {
        _service = service;
        _logger = logger;
    }

    [HttpPost]
    public async Task<IActionResult> Create(QaScoreRequest request)
    {
        if (!ModelState.IsValid)
        {
            return ValidationProblem(ModelState);
        }

        await _service.StoreAsync(request);
        return CreatedAtAction(nameof(GetByAccount),
            new { accountNumber = request.AccountNumber }, null);
    }
}

Repository pattern with Dapper

public sealed class QaScoreRepository
{
    private readonly IDbConnection _connection;

    public QaScoreRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public Task<int> InsertAsync(QaScoreEntity entity)
    {
        const string sql = @"
INSERT INTO QaScores (
    AccountNumber,
    AgentId,
    Score,
    Category,
    CallDateUtc,
    CreatedUtc)
VALUES (
    @AccountNumber,
    @AgentId,
    @Score,
    @Category,
    @CallDateUtc,
    SYSUTCDATETIME());";

        return _connection.ExecuteAsync(sql, entity);
    }
}

SQL and data modeling

Clean, indexed tables designed for reporting and downstream analytics.

Table design

CREATE TABLE Collections.QaScores (
    QaScoreId       INT IDENTITY(1,1) PRIMARY KEY,
    AccountNumber   VARCHAR(20)  NOT NULL,
    AgentId         VARCHAR(16)  NOT NULL,
    Score           TINYINT      NOT NULL CHECK (Score BETWEEN 0 AND 100),
    Category        VARCHAR(32)  NOT NULL,
    CallDateUtc     DATETIME2(0) NOT NULL,
    CreatedUtc      DATETIME2(0) NOT NULL DEFAULT SYSUTCDATETIME(),
    CreatedBy       VARCHAR(32)  NOT NULL DEFAULT SYSTEM_USER
);

CREATE INDEX IX_QaScores_AccountDate
    ON Collections.QaScores (AccountNumber, CallDateUtc DESC);

Reporting view

CREATE VIEW Reporting.QaScoresDaily AS
SELECT
    CAST(CallDateUtc AS DATE)  AS CallDate,
    AgentId,
    COUNT(*)                   AS TotalCalls,
    AVG(CAST(Score AS DECIMAL(5,2))) AS AvgScore
FROM Collections.QaScores
GROUP BY CAST(CallDateUtc AS DATE), AgentId;