From a2dffcb112b9356289c22755edf6ad2a7f5c8e46 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Fri, 24 Apr 2026 13:38:23 +0200 Subject: [PATCH] fix(discovery): sort source_contributions by rank on read MergePendingSources re-aggregates the jsonb array with ORDER BY source_name for DB determinism, but the admin UI treats index 0 as "Rang 1 = winning source." Legacy auto-merged rows were therefore surfacing mittelalterkalender (alphabetically first) as Rang 1 instead of the actual rank-1 source mittelaltermarkt_online. - Export crawler.SourceRank (was unexported rankOf) so other packages in the discovery domain can reference the canonical rank map. - scanDiscoveredMarket: sort.SliceStable SourceContributions by rank after unmarshal. Every read path now sees contributions in rank order regardless of how they were persisted; legacy rows self-correct on next read, no migration needed. --- backend/internal/domain/discovery/crawler/merger.go | 4 ++-- backend/internal/domain/discovery/repository.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/internal/domain/discovery/crawler/merger.go b/backend/internal/domain/discovery/crawler/merger.go index db62b4c..52a1626 100644 --- a/backend/internal/domain/discovery/crawler/merger.go +++ b/backend/internal/domain/discovery/crawler/merger.go @@ -95,7 +95,7 @@ func mergeKey(r RawEvent) string { func mergeGroup(raws []RawEvent) MergedEvent { // Sort by source rank so "first non-empty" == "best source's value". sort.SliceStable(raws, func(i, j int) bool { - return rankOf(raws[i].SourceName) < rankOf(raws[j].SourceName) + return SourceRank(raws[i].SourceName) < SourceRank(raws[j].SourceName) }) m := MergedEvent{} @@ -176,7 +176,7 @@ func mergeGroup(raws []RawEvent) MergedEvent { return m } -func rankOf(name string) int { +func SourceRank(name string) int { if r, ok := sourceRank[name]; ok { return r } diff --git a/backend/internal/domain/discovery/repository.go b/backend/internal/domain/discovery/repository.go index 9f47913..6ba332b 100644 --- a/backend/internal/domain/discovery/repository.go +++ b/backend/internal/domain/discovery/repository.go @@ -6,12 +6,14 @@ import ( "encoding/json" "errors" "fmt" + "sort" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" + "marktvogt.de/backend/internal/domain/discovery/crawler" "marktvogt.de/backend/internal/domain/discovery/enrich" ) @@ -350,6 +352,14 @@ func scanDiscoveredMarket(s scanner) (DiscoveredMarket, error) { if d.SourceContributions == nil { d.SourceContributions = []SourceContribution{} } + // Persisted order (alphabetical from MergePendingSources) doesn't match the + // rank order the UI relies on for "Rang 1 = winning source". Sort by rank + // here so every read path sees contributions in rank order regardless of + // how they were stored. + sort.SliceStable(d.SourceContributions, func(i, j int) bool { + return crawler.SourceRank(d.SourceContributions[i].SourceName) < + crawler.SourceRank(d.SourceContributions[j].SourceName) + }) return d, nil }