diff --git a/backend/internal/domain/discovery/crawler/plz.go b/backend/internal/domain/discovery/crawler/plz.go new file mode 100644 index 0000000..115849c --- /dev/null +++ b/backend/internal/domain/discovery/crawler/plz.go @@ -0,0 +1,45 @@ +package crawler + +import "strings" + +// InferLand maps a PLZ to a DACH country. 5-digit -> DE. 4-digit is ambiguous +// between AT and CH; we recognize common CH ranges (1000-1299 Geneva/Vaud, +// 3000-3999 Bern, 4000-4999 Basel/Aarau, 6000-6999 Luzern/Ticino/Wallis, +// 8000-8999 Zurich, 9000-9999 St. Gallen) and fall back to AT for everything +// else 4-digit. Unknown -> "". The Land string is the form used throughout +// discovery (not ISO-2). +func InferLand(plz string) string { + plz = strings.TrimSpace(plz) + switch len(plz) { + case 5: + if !isAllDigits(plz) { + return "" + } + return "Deutschland" + case 4: + if !isAllDigits(plz) { + return "" + } + switch plz[0] { + case '1': + if plz >= "1200" && plz <= "1299" { + return "Schweiz" + } + return "Oesterreich" + case '3', '4', '6', '8', '9': + return "Schweiz" + default: + return "Oesterreich" + } + } + return "" +} + +func isAllDigits(s string) bool { + for _, r := range s { + if r < '0' || r > '9' { + return false + } + } + return true +} diff --git a/backend/internal/domain/discovery/crawler/plz_test.go b/backend/internal/domain/discovery/crawler/plz_test.go new file mode 100644 index 0000000..f6e9559 --- /dev/null +++ b/backend/internal/domain/discovery/crawler/plz_test.go @@ -0,0 +1,29 @@ +package crawler + +import "testing" + +func TestInferLand(t *testing.T) { + tests := []struct { + name string + plz string + want string + }{ + {"empty", "", ""}, + {"de 5-digit", "49186", "Deutschland"}, + {"de 5-digit low", "01067", "Deutschland"}, + {"at 4-digit typical", "1010", "Oesterreich"}, + {"ch 4-digit typical", "8001", "Schweiz"}, + {"ch 4-digit zurich range", "8000", "Schweiz"}, + {"ch 4-digit bern range", "3000", "Schweiz"}, + {"at 4-digit outside ch ranges", "2500", "Oesterreich"}, + {"short garbage", "12", ""}, + {"non-numeric", "abcde", ""}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := InferLand(tc.plz); got != tc.want { + t.Errorf("InferLand(%q) = %q; want %q", tc.plz, got, tc.want) + } + }) + } +}