Skip to content

Commit e0628b2

Browse files
committed
Task 7: Add operational hygiene AI context
1 parent 8f43d01 commit e0628b2

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-0
lines changed

DBADashAI/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
builder.Services.AddScoped<IAiTool, ReliabilityRiskSummaryTool>();
3535
builder.Services.AddScoped<IAiTool, CapacityForecastSummaryTool>();
3636
builder.Services.AddScoped<IAiTool, ConfigRiskDriftSummaryTool>();
37+
builder.Services.AddScoped<IAiTool, OperationalHygieneSummaryTool>();
3738

3839
var app = builder.Build();
3940

DBADashAI/Services/AiIntentRouter.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,21 @@ private static int ComputeScore(string question, IAiTool tool)
181181
}
182182
}
183183

184+
if (tool.Name == "operational-hygiene-summary")
185+
{
186+
var asksHygiene = question.Contains("hygiene", StringComparison.OrdinalIgnoreCase)
187+
|| question.Contains("acknowledge", StringComparison.OrdinalIgnoreCase)
188+
|| question.Contains("unacknowledged", StringComparison.OrdinalIgnoreCase)
189+
|| question.Contains("stale alert", StringComparison.OrdinalIgnoreCase)
190+
|| question.Contains("backlog", StringComparison.OrdinalIgnoreCase)
191+
|| question.Contains("alert debt", StringComparison.OrdinalIgnoreCase)
192+
|| question.Contains("workflow", StringComparison.OrdinalIgnoreCase);
193+
if (asksHygiene)
194+
{
195+
score += 5;
196+
}
197+
}
198+
184199
return score;
185200
}
186201
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using DBADashAI.Models;
2+
3+
namespace DBADashAI.Services.Tools;
4+
5+
public sealed class OperationalHygieneSummaryTool(SqlToolExecutor sql) : IAiTool
6+
{
7+
public const string ToolName = "operational-hygiene-summary";
8+
9+
public string Name => ToolName;
10+
11+
public string Description => "Summarize alert hygiene debt such as resolved-unacknowledged and aging alert backlog.";
12+
13+
public string InputHint => "Use for monitoring process hygiene, acknowledgment debt, and stale alert workflow questions.";
14+
15+
public string[] Keywords => ["hygiene", "acknowledge", "acknowledgment", "stale alert", "backlog", "resolved unacknowledged", "alert debt"];
16+
17+
public async Task<AiToolResult> RunAsync(AiAskRequest request, CancellationToken cancellationToken)
18+
{
19+
const string sqlText = """
20+
SELECT TOP 500
21+
i.InstanceDisplayName,
22+
aa.AlertType,
23+
aa.AlertKey,
24+
aa.Priority,
25+
aa.IsResolved,
26+
aa.IsAcknowledged,
27+
aa.TriggerDate,
28+
aa.UpdatedDate,
29+
aa.ResolvedDate,
30+
aa.LastMessage
31+
FROM Alert.ActiveAlerts aa
32+
INNER JOIN dbo.Instances i ON i.InstanceID = aa.InstanceID
33+
WHERE i.IsActive = 1
34+
AND aa.UpdatedDate >= DATEADD(DAY,-30,SYSUTCDATETIME())
35+
ORDER BY aa.UpdatedDate DESC;
36+
""";
37+
38+
var rows = await sql.QueryAsync(sqlText, Math.Max(request.MaxRows, 200), cancellationToken);
39+
40+
var resolvedUnacked = rows
41+
.Where(r => (Get(r, "IsResolved") == "1" || string.Equals(Get(r, "IsResolved"), "True", StringComparison.OrdinalIgnoreCase))
42+
&& (Get(r, "IsAcknowledged") == "0" || string.Equals(Get(r, "IsAcknowledged"), "False", StringComparison.OrdinalIgnoreCase)))
43+
.ToList();
44+
45+
var unresolved = rows
46+
.Where(r => Get(r, "IsResolved") == "0" || string.Equals(Get(r, "IsResolved"), "False", StringComparison.OrdinalIgnoreCase))
47+
.ToList();
48+
49+
var byInstance = rows
50+
.GroupBy(r => Get(r, "InstanceDisplayName"))
51+
.Select(g => new
52+
{
53+
Instance = g.Key,
54+
ResolvedUnacknowledged = g.Count(x => (Get(x, "IsResolved") == "1" || string.Equals(Get(x, "IsResolved"), "True", StringComparison.OrdinalIgnoreCase))
55+
&& (Get(x, "IsAcknowledged") == "0" || string.Equals(Get(x, "IsAcknowledged"), "False", StringComparison.OrdinalIgnoreCase))),
56+
UnresolvedCount = g.Count(x => Get(x, "IsResolved") == "0" || string.Equals(Get(x, "IsResolved"), "False", StringComparison.OrdinalIgnoreCase)),
57+
TotalRecentAlerts = g.Count()
58+
})
59+
.OrderByDescending(x => x.ResolvedUnacknowledged)
60+
.ThenByDescending(x => x.UnresolvedCount)
61+
.ToList();
62+
63+
return new AiToolResult
64+
{
65+
RowCount = rows.Count,
66+
Data = new
67+
{
68+
generatedUtc = DateTime.UtcNow,
69+
summary = new
70+
{
71+
totalRecentAlerts = rows.Count,
72+
resolvedUnacknowledgedCount = resolvedUnacked.Count,
73+
unresolvedCount = unresolved.Count
74+
},
75+
byInstance,
76+
resolvedUnacknowledgedRows = resolvedUnacked,
77+
unresolvedRows = unresolved,
78+
rows
79+
},
80+
Evidence =
81+
[
82+
new AiEvidenceItem
83+
{
84+
Source = "Alert.ActiveAlerts + dbo.Instances",
85+
Detail = "Operational hygiene indicators over last 30 days (resolved-unacknowledged backlog and unresolved debt)"
86+
}
87+
]
88+
};
89+
}
90+
91+
private static string Get(Dictionary<string, object?> row, string key)
92+
{
93+
return row.TryGetValue(key, out var value) && value is not null
94+
? value.ToString() ?? string.Empty
95+
: string.Empty;
96+
}
97+
}

DBADashGUI/AI/AIAssistantControl.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ private void SeedExampleQuestions()
220220
cboExamples.Items.Add("What high-risk SQL configuration changes happened in the last 14 days?");
221221
cboExamples.Items.Add("Which servers had frequent config changes that need review?");
222222

223+
// Add new operational hygiene examples
224+
cboExamples.Items.Add("Which instances have the highest resolved-but-unacknowledged alert backlog?");
225+
cboExamples.Items.Add("Where is alert hygiene debt accumulating right now?");
226+
cboExamples.Items.Add("Show unresolved vs resolved-unacknowledged alert counts by instance.");
227+
cboExamples.Items.Add("What stale alert workflow issues should be cleaned up today?");
228+
223229
cboExamples.SelectedIndex = 0;
224230
_seedingExamples = false;
225231
}

0 commit comments

Comments
 (0)