mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
5288b64d40
A provider policy refusal was classified as a connection failure, while summary tools mixed high-utilisation heuristics with unchecked health claims. Preserve explicit refusals before tool recovery and provide retained metrics with source scope, observation timestamps and bucket extrema for diagnosis. Record the live qualification limits and the shared temporal tier-query gap.
90 lines
3.1 KiB
Go
90 lines
3.1 KiB
Go
package reporting
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// MetricEvidenceProvider exposes the same retained metrics as reports without
|
|
// invoking a narrator or assigning health, urgency, or recommended actions.
|
|
type MetricEvidenceProvider interface {
|
|
MetricEvidenceFor(context.Context, MetricReportRequest) (*MetricEvidence, error)
|
|
}
|
|
|
|
type MetricEvidence struct {
|
|
ResourceID string `json:"resource_id"`
|
|
ResourceType string `json:"resource_type"`
|
|
GeneratedAt time.Time `json:"generated_at"`
|
|
WindowStart time.Time `json:"window_start"`
|
|
WindowEnd time.Time `json:"window_end"`
|
|
Metrics map[string]RetainedMetricStats `json:"metrics"`
|
|
}
|
|
|
|
// RetainedMetricStats describes returned points, which may already be retention
|
|
// aggregates. Their count and time span do not prove continuous observation.
|
|
type RetainedMetricStats struct {
|
|
Unit string `json:"unit,omitempty"`
|
|
RetainedPoints int `json:"retained_points"`
|
|
Min float64 `json:"min"`
|
|
Max float64 `json:"max"`
|
|
Mean float64 `json:"mean"`
|
|
Latest float64 `json:"latest"`
|
|
MinBucketAt time.Time `json:"min_bucket_at"`
|
|
MaxBucketAt time.Time `json:"max_bucket_at"`
|
|
FirstAt time.Time `json:"first_at"`
|
|
LastAt time.Time `json:"last_at"`
|
|
MaxGapSeconds float64 `json:"max_gap_seconds"`
|
|
}
|
|
|
|
func (e *ReportEngine) MetricEvidenceFor(ctx context.Context, req MetricReportRequest) (*MetricEvidence, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
if e.getMetricsStore() == nil {
|
|
return nil, fmt.Errorf("metrics store not initialized")
|
|
}
|
|
data, err := e.queryMetrics(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
evidence := &MetricEvidence{
|
|
ResourceID: data.ResourceID, ResourceType: data.ResourceType,
|
|
GeneratedAt: data.GeneratedAt, WindowStart: data.Start, WindowEnd: data.End,
|
|
Metrics: make(map[string]RetainedMetricStats, len(data.Metrics)),
|
|
}
|
|
for name, points := range data.Metrics {
|
|
if len(points) == 0 {
|
|
continue
|
|
}
|
|
stats := data.Summary.ByMetric[name]
|
|
entry := RetainedMetricStats{
|
|
Unit: GetMetricUnit(name), RetainedPoints: len(points),
|
|
Min: points[0].Value, Max: points[0].Value, Mean: stats.Avg, Latest: stats.Current,
|
|
MinBucketAt: points[0].Timestamp, MaxBucketAt: points[0].Timestamp,
|
|
FirstAt: points[0].Timestamp, LastAt: points[len(points)-1].Timestamp,
|
|
}
|
|
for i, point := range points {
|
|
// Retention buckets preserve extrema separately from their mean.
|
|
low, high := point.Value, point.Value
|
|
if point.Min <= point.Value && point.Max >= point.Value {
|
|
low, high = point.Min, point.Max
|
|
}
|
|
if low < entry.Min {
|
|
entry.Min, entry.MinBucketAt = low, point.Timestamp
|
|
}
|
|
if high > entry.Max {
|
|
entry.Max, entry.MaxBucketAt = high, point.Timestamp
|
|
}
|
|
if i > 0 {
|
|
entry.MaxGapSeconds = max(entry.MaxGapSeconds, point.Timestamp.Sub(points[i-1].Timestamp).Seconds())
|
|
}
|
|
}
|
|
evidence.Metrics[name] = entry
|
|
}
|
|
return evidence, nil
|
|
}
|