mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
Add recent changes to intelligence summary
This commit is contained in:
@@ -167,3 +167,6 @@ The per-resource intelligence payload returned from
|
||||
`/api/ai/intelligence?resource_id=...` should also include the canonical
|
||||
`recent_changes` history so UI and API consumers can read the same timeline
|
||||
slice that the prompt context uses.
|
||||
The system-wide `/api/ai/intelligence` summary should also surface the same
|
||||
canonical recent-change slice, alongside the count, so the aggregate payload
|
||||
and the prompt context stay aligned on the same shared timeline source.
|
||||
|
||||
@@ -98,8 +98,9 @@ type IntelligenceSummary struct {
|
||||
UpcomingRisks []patterns.FailurePrediction `json:"upcoming_risks,omitempty"`
|
||||
|
||||
// Recent activity
|
||||
RecentChangesCount int `json:"recent_changes_count"`
|
||||
RecentRemediations []memory.RemediationRecord `json:"recent_remediations,omitempty"`
|
||||
RecentChangesCount int `json:"recent_changes_count"`
|
||||
RecentChanges []unifiedresources.ResourceChange `json:"recent_changes,omitempty"`
|
||||
RecentRemediations []memory.RemediationRecord `json:"recent_remediations,omitempty"`
|
||||
|
||||
// Learning progress
|
||||
Learning LearningStats `json:"learning"`
|
||||
@@ -247,12 +248,22 @@ func (i *Intelligence) GetSummary() *IntelligenceSummary {
|
||||
if resourceTimelineStore != nil {
|
||||
if recent, err := resourceTimelineStore.GetRecentChanges("", time.Now().Add(-24*time.Hour), 100); err == nil {
|
||||
summary.RecentChangesCount = len(recent)
|
||||
if len(recent) > 0 {
|
||||
summary.RecentChanges = append([]unifiedresources.ResourceChange{}, recent[:min(len(recent), 5)]...)
|
||||
}
|
||||
usedCanonicalRecentChanges = true
|
||||
}
|
||||
}
|
||||
if !usedCanonicalRecentChanges && changes != nil {
|
||||
recent := changes.GetRecentChanges(100, time.Now().Add(-24*time.Hour))
|
||||
summary.RecentChangesCount = len(recent)
|
||||
if len(recent) > 0 {
|
||||
converted := make([]unifiedresources.ResourceChange, 0, len(recent))
|
||||
for _, change := range recent {
|
||||
converted = append(converted, convertMemoryChangeToResourceChange(change))
|
||||
}
|
||||
summary.RecentChanges = append([]unifiedresources.ResourceChange{}, converted[:min(len(converted), 5)]...)
|
||||
}
|
||||
}
|
||||
|
||||
if remediations != nil {
|
||||
|
||||
@@ -466,6 +466,12 @@ func TestIntelligence_GetSummary_WithSubsystems(t *testing.T) {
|
||||
if summary.RecentChangesCount == 0 {
|
||||
t.Error("expected recent changes in summary")
|
||||
}
|
||||
if len(summary.RecentChanges) == 0 {
|
||||
t.Error("expected recent change entries in summary")
|
||||
}
|
||||
if summary.RecentChanges[0].SourceType != ur.SourceHeuristic {
|
||||
t.Fatalf("expected heuristic source type for fallback summary, got %s", summary.RecentChanges[0].SourceType)
|
||||
}
|
||||
if len(summary.RecentRemediations) == 0 {
|
||||
t.Error("expected recent remediations in summary")
|
||||
}
|
||||
@@ -495,6 +501,12 @@ func TestIntelligence_GetSummary_UsesCanonicalResourceTimeline(t *testing.T) {
|
||||
if summary.RecentChangesCount != 1 {
|
||||
t.Fatalf("expected canonical recent change count, got %d", summary.RecentChangesCount)
|
||||
}
|
||||
if len(summary.RecentChanges) != 1 {
|
||||
t.Fatalf("expected canonical recent change slice, got %d", len(summary.RecentChanges))
|
||||
}
|
||||
if summary.RecentChanges[0].Kind != ur.ChangeConfigUpdate {
|
||||
t.Fatalf("expected canonical recent change kind, got %s", summary.RecentChanges[0].Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntelligence_GetResourceIntelligence_WithAllSubsystems(t *testing.T) {
|
||||
|
||||
@@ -89,6 +89,19 @@ func TestHandleGetIntelligence_ResourceIDTooLong(t *testing.T) {
|
||||
func TestHandleGetIntelligence_WithPatrolService(t *testing.T) {
|
||||
t.Parallel()
|
||||
svc := newEnabledAIService(t)
|
||||
canonicalStore := ur.NewMemoryStore()
|
||||
if err := canonicalStore.RecordChange(ur.ResourceChange{
|
||||
ID: "change-summary",
|
||||
ObservedAt: time.Now().Add(-20 * time.Minute),
|
||||
ResourceID: "vm-summary",
|
||||
Kind: ur.ChangeRestart,
|
||||
SourceType: ur.SourcePlatformEvent,
|
||||
Reason: "guest restarted",
|
||||
}); err != nil {
|
||||
t.Fatalf("record canonical change: %v", err)
|
||||
}
|
||||
setUnexportedField(t, svc, "resourceExportStore", canonicalStore)
|
||||
setUnexportedField(t, svc.GetPatrolService(), "aiService", svc)
|
||||
handler := &AISettingsHandler{defaultAIService: svc}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence", nil)
|
||||
@@ -107,6 +120,13 @@ func TestHandleGetIntelligence_WithPatrolService(t *testing.T) {
|
||||
if _, ok := payload["timestamp"]; !ok {
|
||||
t.Fatalf("expected timestamp in intelligence summary, got %v", payload)
|
||||
}
|
||||
recentChanges, ok := payload["recent_changes"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected recent_changes array in summary response, got %T", payload["recent_changes"])
|
||||
}
|
||||
if len(recentChanges) != 1 {
|
||||
t.Fatalf("expected 1 recent change in summary, got %d", len(recentChanges))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetIntelligence_WithResourceID(t *testing.T) {
|
||||
|
||||
@@ -323,6 +323,45 @@ func TestContract_ResourceIntelligenceIncludesRecentChanges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContract_IntelligenceSummaryIncludesRecentChanges(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
canonicalStore := unifiedresources.NewMemoryStore()
|
||||
if err := canonicalStore.RecordChange(unifiedresources.ResourceChange{
|
||||
ID: "change-summary",
|
||||
ObservedAt: time.Now().Add(-15 * time.Minute),
|
||||
ResourceID: "vm-100",
|
||||
Kind: unifiedresources.ChangeRestart,
|
||||
SourceType: unifiedresources.SourcePlatformEvent,
|
||||
Reason: "guest restarted",
|
||||
}); err != nil {
|
||||
t.Fatalf("record canonical change: %v", err)
|
||||
}
|
||||
setUnexportedField(t, svc, "resourceExportStore", canonicalStore)
|
||||
setUnexportedField(t, svc.GetPatrolService(), "aiService", svc)
|
||||
|
||||
handlers := &AISettingsHandler{defaultAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handlers.HandleGetIntelligence(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
recentChanges, ok := payload["recent_changes"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected recent_changes array in response, got %T", payload["recent_changes"])
|
||||
}
|
||||
if len(recentChanges) != 1 {
|
||||
t.Fatalf("expected 1 recent change, got %d", len(recentChanges))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContract_ResolveAuthEnvPathUsesCanonicalRuntimeDataDir(t *testing.T) {
|
||||
envDir := t.TempDir()
|
||||
t.Setenv("PULSE_DATA_DIR", envDir)
|
||||
|
||||
Reference in New Issue
Block a user