120 lines
2.8 KiB
Go
120 lines
2.8 KiB
Go
package award
|
|
|
|
import "strings"
|
|
|
|
// wpxPrefix derives the CQ WPX prefix from a callsign. This is an approximation
|
|
// of the official WPX rules — good enough to count distinct prefixes worked:
|
|
// - standard call: letters+digits up to and including the LAST digit of the
|
|
// first group (F4BPO→F4, EA8ABC→EA8, 9A1AA→9A1, OH2BH→OH2)
|
|
// - no digit: first two letters + "0" (RAEM→RA0)
|
|
// - portable "A/B": a short alpha(+digit) segment is treated as the prefix
|
|
// designator; a lone-digit segment replaces the call's digit (F4BPO/9→F9)
|
|
func wpxPrefix(call string) string {
|
|
c := strings.ToUpper(strings.TrimSpace(call))
|
|
if c == "" {
|
|
return ""
|
|
}
|
|
if strings.Contains(c, "/") {
|
|
return portablePrefix(c)
|
|
}
|
|
return standardPrefix(c)
|
|
}
|
|
|
|
func portablePrefix(c string) string {
|
|
parts := strings.Split(c, "/")
|
|
// Drop pure operating-modifier suffixes.
|
|
kept := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
switch p {
|
|
case "P", "M", "MM", "AM", "QRP", "A", "R", "B", "LH":
|
|
continue
|
|
}
|
|
if p != "" {
|
|
kept = append(kept, p)
|
|
}
|
|
}
|
|
if len(kept) == 0 {
|
|
kept = parts
|
|
}
|
|
// Pick a base = the longest segment (the actual call).
|
|
base := kept[0]
|
|
for _, p := range kept[1:] {
|
|
if len(p) > len(base) {
|
|
base = p
|
|
}
|
|
}
|
|
for _, p := range kept {
|
|
if p == base {
|
|
continue
|
|
}
|
|
if isAllDigits(p) {
|
|
// Lone digit replaces the call's region digit: F4BPO/9 → F9.
|
|
return replaceLastDigit(standardPrefix(base), p)
|
|
}
|
|
if len(p) <= 4 && hasLetter(p) {
|
|
// Prefix designator wins: VP8/F4BPO → VP8 (+digit if missing).
|
|
return ensureTrailingDigit(p)
|
|
}
|
|
}
|
|
return standardPrefix(base)
|
|
}
|
|
|
|
// standardPrefix applies the basic WPX rule to a plain callsign: the prefix is
|
|
// the call up to and including its last digit (9A1AA→9A1, EA8ABC→EA8). Standard
|
|
// callsigns carry no digit in the suffix, so "last digit" is the prefix digit.
|
|
func standardPrefix(c string) string {
|
|
lastDigit := -1
|
|
for i := 0; i < len(c); i++ {
|
|
if c[i] >= '0' && c[i] <= '9' {
|
|
lastDigit = i
|
|
}
|
|
}
|
|
if lastDigit < 0 {
|
|
// No digit at all: first two letters + 0.
|
|
if len(c) >= 2 {
|
|
return c[:2] + "0"
|
|
}
|
|
return c + "0"
|
|
}
|
|
return c[:lastDigit+1]
|
|
}
|
|
|
|
func ensureTrailingDigit(p string) string {
|
|
for i := 0; i < len(p); i++ {
|
|
if p[i] >= '0' && p[i] <= '9' {
|
|
return p
|
|
}
|
|
}
|
|
return p + "0"
|
|
}
|
|
|
|
func replaceLastDigit(prefix, digit string) string {
|
|
for i := len(prefix) - 1; i >= 0; i-- {
|
|
if prefix[i] >= '0' && prefix[i] <= '9' {
|
|
return prefix[:i] + digit
|
|
}
|
|
}
|
|
return prefix + digit
|
|
}
|
|
|
|
func isAllDigits(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] < '0' || s[i] > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func hasLetter(s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if (s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z') {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|