Files
egutierrez 53200cbc0d feat: funciones Go — core (cron, join_by_key, validate_struct), datascience (pivot, diff_entities), infra (http, cache, cron_ticker)
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>
2026-04-05 17:11:12 +02:00

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,
}
}