52 lines
1.2 KiB
Go
52 lines
1.2 KiB
Go
// ©AngelaMos | 2026
|
|
// rebuild.go
|
|
|
|
package cluster
|
|
|
|
import (
|
|
"github.com/CarterPerez-dev/nadezhda/internal/store"
|
|
)
|
|
|
|
type Stats struct {
|
|
Total int
|
|
MultiSource int
|
|
LargestSize int
|
|
}
|
|
|
|
func Rebuild(st *store.Store, jaccardThreshold float64, windowSeconds, sinceUnix int64) (Stats, error) {
|
|
candidates, err := st.ClusterCandidates(sinceUnix)
|
|
if err != nil {
|
|
return Stats{}, err
|
|
}
|
|
cveMap, err := st.ArticleCVEMap()
|
|
if err != nil {
|
|
return Stats{}, err
|
|
}
|
|
|
|
items := make([]Item, len(candidates))
|
|
for i, c := range candidates {
|
|
items[i] = Item{ID: c.ID, SourceID: c.SourceID, Title: c.Title, Time: c.Time, CVEs: cveMap[c.ID]}
|
|
}
|
|
|
|
clusters := Compute(items, jaccardThreshold, windowSeconds)
|
|
|
|
rows := make([]store.ClusterRow, len(clusters))
|
|
stats := Stats{Total: len(clusters)}
|
|
for i, c := range clusters {
|
|
rows[i] = store.ClusterRow{
|
|
Key: c.Key, Members: c.Members, FirstSeen: c.FirstSeen, LastSeen: c.LastSeen,
|
|
}
|
|
if c.SourceCount > 1 {
|
|
stats.MultiSource++
|
|
}
|
|
if size := len(c.Members); size > stats.LargestSize {
|
|
stats.LargestSize = size
|
|
}
|
|
}
|
|
|
|
if err := st.ReplaceClusters(rows); err != nil {
|
|
return Stats{}, err
|
|
}
|
|
return stats, nil
|
|
}
|