Skip to content

Commit 4e87ef0

Browse files
authored
Fix missing fields and unset fields (#22)
* Centralize DXCC info building to fix missing flag field - Added BuildDxccInfo() function in utils/dxcc_lookup.go as single source of truth - Added toUcWord() helper in utils for title case conversion - Created internal buildDxccInfo() in backend/dxcc/client.go with applyTitleCase parameter - Updated GetPrefix() and GetException() to use buildDxccInfo() with flag population - Updated buildDxccInfoFromPrefix() and buildDxccInfoFromEntity() to use buildDxccInfo() - Added comprehensive tests to verify flag field is populated in all scenarios - All existing tests pass without regressions * Change the API output to match the original project better - Correct `lotw_user` output to display days since last update instead of hard-coded "2" - Added `ituz` to output to go alongside `cqz` - Added `mode`, `submode`, and `band` to cluster spots (for unified format) - Added `mode` and `submode` to POTA spots (for unified format) This should compliment the `flag` output and start some refactoring of repeated functions
1 parent d3c8369 commit 4e87ef0

9 files changed

Lines changed: 468 additions & 76 deletions

File tree

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,13 @@ All endpoints return JSON. Base URL: `http://x.x.x.x:8192`
7979
{
8080
"spotter": "W1AW",
8181
"spotted": "K7RA",
82-
"frequency": 14250000,
82+
"frequency": 14250,
8383
"message": "CQ DX",
84-
"when": "2025-01-01T10:00:00Z",
84+
"when": "2025-01-01T10:00:00.000Z",
8585
"source": "dx.n9jr.com",
8686
"band": "20m",
87+
"mode": "phone",
88+
"submode": "USB",
8789
"dxcc_spotter": {
8890
"cont": "NA",
8991
"entity": "United States",
@@ -92,8 +94,8 @@ All endpoints return JSON. Base URL: `http://x.x.x.x:8192`
9294
"cqz": "5",
9395
"ituz": "8",
9496
"lotw_user": false,
95-
"lat": "39",
96-
"lng": "-98"
97+
"lat": "39.0",
98+
"lng": "-98.0"
9799
},
98100
"dxcc_spotted": {
99101
"cont": "NA",
@@ -102,15 +104,21 @@ All endpoints return JSON. Base URL: `http://x.x.x.x:8192`
102104
"dxcc_id": "291",
103105
"cqz": "5",
104106
"ituz": "8",
105-
"lotw_user": "2",
106-
"lat": "39",
107-
"lng": "-98"
107+
"lotw_user": 2,
108+
"lat": "39.0",
109+
"lng": "-98.0"
108110
}
109111
}
110112
]
111113
```
112114

113-
**Note:** `lotw_user` is `"2"` (string) if LoTW user, `false` (boolean) if not.
115+
**Note:**
116+
- `frequency` is in kHz (integer, e.g., 14250)
117+
- `band` is calculated from frequency (e.g., "20m", "40m", "2m")
118+
- `mode` and `submode` are available for POTA spots; empty strings for DX Cluster spots
119+
- `cqz` (CQ Zone) and `ituz` (ITU Zone) are included in DXCC data
120+
- `lotw_user` is the number of days since last LoTW upload (integer) if a LoTW member, `false` (boolean) if not
121+
- `lat` and `lng` are strings with 1 decimal place precision
114122

115123
## Configuration
116124

backend/dxcc/client.go

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,71 +1390,89 @@ func (c *Client) determineEffectivePrefix(callsign string) string {
13901390
return prefix
13911391
}
13921392

1393-
// buildDxccInfoFromPrefix constructs DxccInfo from a DxccPrefix.
1394-
func (c *Client) buildDxccInfoFromPrefix(pfx *DxccPrefix) *DxccInfo {
1393+
// buildDxccInfo is the centralized function for building DxccInfo objects.
1394+
// It ensures all fields including the flag are consistently populated.
1395+
// If applyTitleCase is true, entity names are converted to title case.
1396+
func buildDxccInfo(adif int, continent, entity string, cqz, ituz int, latitude, longitude float64, applyTitleCase bool) *DxccInfo {
1397+
entityName := entity
1398+
if applyTitleCase {
1399+
entityName = toUcWord(entity)
1400+
}
13951401
info := &DxccInfo{
1396-
Cont: pfx.Cont,
1397-
Entity: toUcWord(pfx.Entity), // Apply title case as in original Node.js
1398-
DXCCID: pfx.ADIF,
1399-
CQZ: pfx.CQZ,
1400-
Latitude: pfx.Lat,
1401-
Longitude: pfx.Long,
1402+
Cont: continent,
1403+
Entity: entityName,
1404+
DXCCID: adif,
1405+
CQZ: cqz,
1406+
ITUZ: ituz,
1407+
Latitude: latitude,
1408+
Longitude: longitude,
1409+
Flag: FlagEmojis[strconv.Itoa(adif)],
14021410
}
1411+
return info
1412+
}
14031413

1414+
// buildDxccInfoFromPrefix constructs DxccInfo from a DxccPrefix.
1415+
func (c *Client) buildDxccInfoFromPrefix(pfx *DxccPrefix) *DxccInfo {
14041416
// Prefer exported EntitiesMap if populated (tests may set it) and use it to enrich prefix info.
14051417
entities := c.entitiesMap
14061418
if len(c.EntitiesMap) != 0 {
14071419
entities = c.EntitiesMap
14081420
}
1409-
if entity, ok := entities[pfx.ADIF]; ok {
1421+
1422+
// Start with data from prefix
1423+
entity := pfx.Entity
1424+
latitude := pfx.Lat
1425+
longitude := pfx.Long
1426+
cqz := pfx.CQZ
1427+
cont := pfx.Cont
1428+
ituz := 0
1429+
1430+
// Enrich with entity data if available
1431+
if ent, ok := entities[pfx.ADIF]; ok {
14101432
// Use entity name for a canonical Entity value and entity lat/long when available
1411-
if entity.Name != "" {
1412-
info.Entity = toUcWord(entity.Name)
1433+
if ent.Name != "" {
1434+
entity = ent.Name
14131435
}
1414-
if entity.Long != 0.0 {
1415-
info.Longitude = entity.Long
1436+
if ent.Long != 0.0 {
1437+
longitude = ent.Long
14161438
}
1417-
if entity.Lat != 0.0 {
1418-
info.Latitude = entity.Lat
1439+
if ent.Lat != 0.0 {
1440+
latitude = ent.Lat
14191441
}
1420-
info.ITUZ = entity.ITUZ
1442+
ituz = ent.ITUZ
14211443
// Copy CQZ and Cont when available to match test expectations
1422-
if entity.CQZ != 0 {
1423-
info.CQZ = entity.CQZ
1444+
if ent.CQZ != 0 {
1445+
cqz = ent.CQZ
14241446
}
1425-
if entity.Cont != "" {
1426-
info.Cont = entity.Cont
1447+
if ent.Cont != "" {
1448+
cont = ent.Cont
14271449
}
14281450
}
1429-
logging.Debug("DXCC build info from prefix ADIF=%d -> Entity=%q ITUZ=%d Lat=%f Lng=%f Flag=%q", pfx.ADIF, info.Entity, info.ITUZ, info.Latitude, info.Longitude, info.Flag)
1430-
1431-
// Look up flag from static map
1432-
info.Flag = FlagEmojis[strconv.Itoa(pfx.ADIF)]
1451+
1452+
// Use centralized function to build complete DxccInfo
1453+
info := buildDxccInfo(pfx.ADIF, cont, entity, cqz, ituz, latitude, longitude, true)
1454+
1455+
logging.Debug("DXCC build info from prefix ADIF=%d -> Entity=%q ITUZ=%d Lat=%f Lng=%f Flag=%q",
1456+
info.DXCCID, info.Entity, info.ITUZ, info.Latitude, info.Longitude, info.Flag)
1457+
14331458
return info
14341459
}
14351460

14361461
// buildDxccInfoFromEntity constructs DxccInfo from a DxccEntity. Used for ADIF 0 (NONE).
14371462
func (c *Client) buildDxccInfoFromEntity(entity *DxccEntity) *DxccInfo {
1438-
info := &DxccInfo{
1439-
Cont: entity.Cont,
1440-
Entity: toUcWord(entity.Name),
1441-
DXCCID: entity.ADIF,
1442-
CQZ: entity.CQZ,
1443-
ITUZ: entity.ITUZ,
1444-
Latitude: entity.Lat,
1445-
Longitude: entity.Long,
1446-
}
1463+
entityName := entity.Name
1464+
14471465
// If ADIF 0, tests expect the specific '- None - (e.g. /MM, /AM)' entity name; normalize to that
14481466
if entity.ADIF == 0 {
14491467
// Accept several variants and normalize to test-expected capitalization/format
14501468
name := strings.TrimSpace(entity.Name)
14511469
if strings.EqualFold(name, "- none - (e.g. /mm, /am)") || strings.EqualFold(name, "- none -") || name == "" {
1452-
info.Entity = "- None - (e.g. /MM, /AM)"
1453-
} else {
1454-
info.Entity = toUcWord(name)
1470+
entityName = "- None - (e.g. /MM, /AM)"
14551471
}
14561472
}
1457-
info.Flag = FlagEmojis[strconv.Itoa(entity.ADIF)] // Should be empty for ADIF 0
1473+
1474+
// Use centralized function to build complete DxccInfo
1475+
info := buildDxccInfo(entity.ADIF, entity.Cont, entityName, entity.CQZ, entity.ITUZ, entity.Lat, entity.Long, true)
14581476
return info
14591477
}
14601478

@@ -1528,12 +1546,16 @@ func (c *Client) GetException(call string) (*DxccInfo, bool) {
15281546
if !found {
15291547
return nil, false
15301548
}
1531-
info := &DxccInfo{
1532-
DXCCID: exc.ADIF,
1533-
Entity: exc.Entity,
1534-
CQZ: exc.CQZ,
1535-
Cont: exc.Cont,
1549+
1550+
// Get ITUZ from entities map if available
1551+
ituz := 0
1552+
if entity, ok := c.entitiesMap[exc.ADIF]; ok {
1553+
ituz = entity.ITUZ
15361554
}
1555+
1556+
// Build complete DxccInfo with all fields including flag
1557+
// Don't apply title case - use entity name as-is from database
1558+
info := buildDxccInfo(exc.ADIF, exc.Cont, exc.Entity, exc.CQZ, ituz, exc.Lat, exc.Long, false)
15371559
return info, true
15381560
}
15391561

@@ -1546,11 +1568,15 @@ func (c *Client) GetPrefix(prefix string) (*DxccInfo, bool) {
15461568
if !found {
15471569
return nil, false
15481570
}
1549-
info := &DxccInfo{
1550-
DXCCID: pfx.ADIF,
1551-
Entity: pfx.Entity,
1552-
CQZ: pfx.CQZ,
1553-
Cont: pfx.Cont,
1571+
1572+
// Get ITUZ from entities map if available
1573+
ituz := 0
1574+
if entity, ok := c.entitiesMap[pfx.ADIF]; ok {
1575+
ituz = entity.ITUZ
15541576
}
1577+
1578+
// Build complete DxccInfo with all fields including flag
1579+
// Don't apply title case - use entity name as-is from database
1580+
info := buildDxccInfo(pfx.ADIF, pfx.Cont, pfx.Entity, pfx.CQZ, ituz, pfx.Lat, pfx.Long, false)
15551581
return info, true
15561582
}

0 commit comments

Comments
 (0)