53200cbc0d
Nuevas funciones Go con tests en tres dominios: - core: parse_cron_expr, next_cron_time, join_by_key, validate_struct_fields + tipo CronSchedule - datascience: pivot (tabla dinámica), diff_entities (comparación de entidades) - infra: http_get_json, http_post_json, http_download_file, cache_to_sqlite, cron_ticker Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
2.2 KiB
Go
97 lines
2.2 KiB
Go
package datascience
|
|
|
|
import "fmt"
|
|
|
|
// DiffEntities compares two snapshots of entities and returns field-level differences.
|
|
// Detects added, removed, modified, and unchanged entities.
|
|
// ignoreFields specifies fields to exclude from comparison (defaults to ["created_at", "updated_at"] when nil).
|
|
func DiffEntities(before, after []map[string]any, key string, ignoreFields []string) map[string]any {
|
|
if ignoreFields == nil {
|
|
ignoreFields = []string{"created_at", "updated_at"}
|
|
}
|
|
|
|
ignoreSet := make(map[string]bool, len(ignoreFields))
|
|
for _, f := range ignoreFields {
|
|
ignoreSet[f] = true
|
|
}
|
|
|
|
beforeMap := make(map[string]map[string]any, len(before))
|
|
for _, e := range before {
|
|
if k, ok := e[key]; ok {
|
|
beforeMap[fmt.Sprintf("%v", k)] = e
|
|
}
|
|
}
|
|
|
|
afterMap := make(map[string]map[string]any, len(after))
|
|
for _, e := range after {
|
|
if k, ok := e[key]; ok {
|
|
afterMap[fmt.Sprintf("%v", k)] = e
|
|
}
|
|
}
|
|
|
|
added := []map[string]any{}
|
|
for k, e := range afterMap {
|
|
if _, exists := beforeMap[k]; !exists {
|
|
added = append(added, e)
|
|
}
|
|
}
|
|
|
|
removed := []map[string]any{}
|
|
for k, e := range beforeMap {
|
|
if _, exists := afterMap[k]; !exists {
|
|
removed = append(removed, e)
|
|
}
|
|
}
|
|
|
|
modified := []map[string]any{}
|
|
unchanged := 0
|
|
|
|
for k, b := range beforeMap {
|
|
a, exists := afterMap[k]
|
|
if !exists {
|
|
continue
|
|
}
|
|
|
|
// Collect all fields from both entities
|
|
allFields := make(map[string]bool)
|
|
for f := range b {
|
|
allFields[f] = true
|
|
}
|
|
for f := range a {
|
|
allFields[f] = true
|
|
}
|
|
|
|
changes := map[string]any{}
|
|
for field := range allFields {
|
|
if ignoreSet[field] || field == key {
|
|
continue
|
|
}
|
|
oldVal := b[field]
|
|
newVal := a[field]
|
|
if fmt.Sprintf("%v", oldVal) != fmt.Sprintf("%v", newVal) {
|
|
changes[field] = map[string]any{"old": oldVal, "new": newVal}
|
|
}
|
|
}
|
|
|
|
if len(changes) > 0 {
|
|
modified = append(modified, map[string]any{"key": k, "changes": changes})
|
|
} else {
|
|
unchanged++
|
|
}
|
|
}
|
|
|
|
nAdded := len(added)
|
|
nRemoved := len(removed)
|
|
nModified := len(modified)
|
|
summary := fmt.Sprintf("%d added, %d removed, %d modified, %d unchanged",
|
|
nAdded, nRemoved, nModified, unchanged)
|
|
|
|
return map[string]any{
|
|
"added": added,
|
|
"removed": removed,
|
|
"modified": modified,
|
|
"unchanged": unchanged,
|
|
"summary": summary,
|
|
}
|
|
}
|