This commit is contained in:
2026-07-13 11:13:00 +08:00
parent ef96af7f21
commit dac7457c60
9 changed files with 297 additions and 28 deletions
+33 -10
View File
@@ -21,14 +21,15 @@ type History struct {
}
}
type Series struct {
PointID uuid.UUID `json:"point_id"`
PointName string `json:"point_name"`
DataType string `json:"data_type"`
Unit *string `json:"unit"`
Sampled bool `json:"sampled"`
RawCount int `json:"raw_count"`
SampleCount int `json:"sample_count"`
Data []td.Sample `json:"data"`
PointID uuid.UUID `json:"point_id"`
PointName string `json:"point_name"`
DataType string `json:"data_type"`
Unit *string `json:"unit"`
HistoryInterval int `json:"history_interval"`
Sampled bool `json:"sampled"`
RawCount int `json:"raw_count"`
SampleCount int `json:"sample_count"`
Data []td.Sample `json:"data"`
}
func (h *History) Tree(ctx context.Context) ([]map[string]any, error) {
@@ -108,13 +109,35 @@ func (h *History) Query(ctx context.Context, ids []uuid.UUID, start, end time.Ti
return nil, e
}
raw := len(data)
if raw > max {
data = withHistoryGaps(data, time.Duration(p.HistoryInterval)*time.Minute)
sampled := len(data) > max
if sampled {
data = minMax(data, max)
}
out = append(out, Series{id, p.Name, p.DataType, p.Unit, raw > len(data), raw, len(data), data})
out = append(out, Series{id, p.Name, p.DataType, p.Unit, p.HistoryInterval, sampled, raw, len(data), data})
}
return out, nil
}
func withHistoryGaps(data []td.Sample, interval time.Duration) []td.Sample {
if len(data) < 2 || interval <= 0 {
return data
}
threshold := interval * 3
out := make([]td.Sample, 0, len(data))
out = append(out, data[0])
for i := 1; i < len(data); i++ {
prev := data[i-1]
current := data[i]
gap := current.TS.Sub(prev.TS)
if gap > threshold {
out = append(out, td.Sample{TS: prev.TS.Add(gap / 2), Quality: "none"})
}
out = append(out, current)
}
return out
}
func minMax(data []td.Sample, max int) []td.Sample {
if len(data) <= max {
return data
+47
View File
@@ -0,0 +1,47 @@
package platform
import (
td "aquacontrolai/internal/repository/tdengine"
"testing"
"time"
)
func sampleAt(ts time.Time, value float64) td.Sample {
return td.Sample{TS: ts, Value: &value, Quality: "good"}
}
func TestWithHistoryGapsInsertsNullBreakpointForLongOutage(t *testing.T) {
start := time.Date(2026, 7, 13, 1, 59, 0, 0, time.UTC)
data := []td.Sample{
sampleAt(start, 1),
sampleAt(start.Add(time.Minute), 2),
sampleAt(start.Add(6*time.Hour), 3),
}
got := withHistoryGaps(data, time.Minute)
if len(got) != 4 {
t.Fatalf("expected gap breakpoint, got %d rows", len(got))
}
if got[2].Value != nil || got[2].Quality != "none" {
t.Fatalf("expected null gap marker, got value=%v quality=%q", got[2].Value, got[2].Quality)
}
if !got[2].TS.After(got[1].TS) || !got[2].TS.Before(got[3].TS) {
t.Fatalf("gap marker should be inside outage: marker=%v before=%v after=%v", got[2].TS, got[1].TS, got[3].TS)
}
}
func TestWithHistoryGapsAllowsExpectedIntervalJitter(t *testing.T) {
start := time.Date(2026, 7, 13, 1, 59, 0, 0, time.UTC)
data := []td.Sample{
sampleAt(start, 1),
sampleAt(start.Add(2*time.Minute), 2),
sampleAt(start.Add(3*time.Minute), 3),
}
got := withHistoryGaps(data, time.Minute)
if len(got) != len(data) {
t.Fatalf("expected no gap breakpoint for short jitter, got %d rows", len(got))
}
}