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>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user