DEV Community

Cover image for Detecting Sales Anomalies with ML.NET β€” Razor Dashboard + SQL Integration
reshma p
reshma p

Posted on

Detecting Sales Anomalies with ML.NET β€” Razor Dashboard + SQL Integration

πŸ” Introduction

In this post, I’ll Walk through how I built an intelligent anomaly detection dashboard using ML.NET, SQL Server, and Razor Pages. The goal: flag unusual sales patterns across stores and organizations using DetectIidSpike

πŸ§ͺ Problem Statement
Sales data can fluctuate β€” but how do we know when it’s abnormal?
Using ML.NET’s DetectIidSpike, we can identify sudden spikes in sales values and surface them in a dashboard with filters, counts

🧱 Tech Stack
β€’ ML.NET TimeSeries: DetectIidSpike for anomaly detection
β€’ SQL Server View: vw_SalesAnomaly as the data source
β€’ Razor Pages: UI with dropdown filters and summary counts
β€’ LINQ: Dynamic filtering by Store and Or

🧠 ML.NET Pipeline

var pipeline = mlContext.Transforms.DetectIidSpike(
                    outputColumnName: nameof(SalesOutput.Prediction),
                    inputColumnName: nameof(SalesInput.Sales),
                    confidence: 95,
                    pvalueHistoryLength: 30)
                     .Append(mlContext.Transforms.CopyColumns("OrgName", "OrgName"))
                    .Append(mlContext.Transforms.CopyColumns("StoreName", "StoreName"));
Enter fullscreen mode Exit fullscreen mode
var results = mlContext.Data.CreateEnumerable<SalesOutput>(transformed, reuseRowObject: false)
                        .Select((p, index) => new AnomalyPoint
                        {
                            Index = index,
                            Score = p.Prediction[1],
                            PValue = p.Prediction[2],
                            IsAnomaly = p.Prediction[0] == 1,
                            OrgName = p.OrgName,
                            StoreName = p.StoreName
                        }).ToList();
                return results;
Enter fullscreen mode Exit fullscreen mode

This detects spikes in the Sales column and retains metadata for filtering

πŸ—‚οΈ Data Source

SELECT 
CAST(Sales AS REAL) AS Sales,
CAST(Multiplier AS REAL)AS Multiplier,
Severity,
OrgName,
StoreName 
FROM vw_SalesAnomaly
Enter fullscreen mode Exit fullscreen mode

The view aggregates sales metrics across stores and organizations.

πŸ“Š Razor Dashboard Logic

if (!string.IsNullOrEmpty(SelectedStore))
            {
                results = results.Where(x => x.StoreName == SelectedStore).ToList();
            }
            if (!string.IsNullOrEmpty(SelectedOrg))
            {
                results = results.Where(x => x.OrgName == SelectedOrg).ToList();
            }
            AnomalyCount = results.Count(x=>x.IsAnomaly);
            OrgCount = results.Where(r => r.IsAnomaly).Select(r => r.OrgName).Distinct().ToList().Count();
            StoreCount = results.Where(r => r.IsAnomaly).Select(r => r.StoreName).Distinct().ToList().Count();
            StoreList = results.Select(x => x.StoreName).ToList();
            OrgList = results.Select(x => x.OrgName).ToList();
            TotalRecords = results.Distinct().ToList().Count();
Enter fullscreen mode Exit fullscreen mode

🎨 Razor UI Highlights

πŸ” Alert Summary Pane


<div class="alert alert-strong-danger d-flex align-items-center justify-content-between mb-3">
  <div class="alert-box">
    <span class="label">No.of Anomaly</span>
    <div class="icon-line">
      <i class="bi bi-eye-fill"></i>
      <span>@Model.AnomalyCount</span>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode
  • Custom alert styling with .alert-strong-danger
  • Bootstrap Icons for visual cues
  • Dynamic counts for anomalies, affected orgs, and store

🧭 Interactive Filters


<form class="form-group">
    <label for="SelectedOrg">Org Name</label>
    <select id="SelectedOrg" name="SelectedOrg" class="form-control" onchange="this.form.submit()">
        <option value="">-- Select Organization --</option>
        @foreach (var org in @Model.OrgList)
        {
            <option value="@org" selected="@(org == @Model.SelectedOrg ? "selected" : null)">
                @org
            </option>
        }
    </select>
</form>
Enter fullscreen mode Exit fullscreen mode
  • Organization and store filters auto-submit on change
  • Razor logic ensures selected values persist

πŸ“Š Chart.js Visualizatio

const data = {
    labels: @Html.Raw(JsonConvert.SerializeObject(Model.results.Select(a => a.Index))),
    datasets: [
        {
            label: 'Sales Score',
            data: @Html.Raw(JsonConvert.SerializeObject(Model.results.Select(a => a.Score))),
            borderColor: 'blue',
            fill: false
        },
        {
            label: 'Anomalies',
            data: @Html.Raw(JsonConvert.SerializeObject(Model.results.Select(a => a.IsAnomaly ? a.Score : (double?)null))),
            borderColor: 'red',
            pointBackgroundColor: 'red',
            pointRadius: 5,
            showLine: false
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode
  • Blue line for normal scores
  • Red dots for anomalies
  • Responsive chart with title plugin

πŸ“‹ Tabular Insights

<table class="table">
    <thead>
        <tr><th>OrgName</th><th>StoreName</th><th>Score</th><th>P-Value</th><th>Status</th></tr>
    </thead>
    <tbody>
        @foreach (var a in Model.results)
        {
            <tr>
                <td>@a.OrgName</td>
                <td>@a.StoreName</td>
                <td>@a.Score</td>
                <td>@a.PValue</td>
                <td>
                    <span class="@(a.IsAnomaly ? "text-danger" : "text-primary")">
                        @(a.IsAnomaly ? "Anomaly" : "Normal")
                    </span>
                </td>
            </tr>
        }
    </tbody>
</table>
Enter fullscreen mode Exit fullscreen mode
  • Conditional styling for anomaly status
  • Clean layout using Bootstrap’s table classes

This powers the dropdown filters and summary counts on the dashboard.

πŸ“ˆ Output
β€’ βœ… Total anomalies detected
β€’ 🏒 Unique orgs and stores affected
β€’ πŸ“‹ Filterable results by Store and Org

Detail Code:

πŸ“ Visit GitHub

πŸš€ Conclusion
ML.NET makes it easy to build intelligent dashboards that detect and visualize anomalies in real time. This project combines SQL, ML.NET, and Razor Pages

Top comments (0)