From 20143cf096c3caff9dcf6e76dff7581fe350a3d0 Mon Sep 17 00:00:00 2001 From: atsunatsu Date: Tue, 8 Sep 2026 11:22:24 +0800 Subject: [PATCH] fix(lotw): strip STATE ' // Name' suffix for award stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoTW returns STATE as 'CODE // Name' (verified with a real report 2026-09), e.g. 'GD // Guangdong' (China), '34 // Tottori-ken' (Japan), 'CA // California' (US). The award calculator matched on the bare CODE, so every mainland China province (WAPC), all Japan prefectures (WAJA) and US states (WAS) failed — WAPC only showed HK/MO/TW (entity branch, no STATE needed) and WAJA was empty. Normalize STATE once at parse time: strip the ' // Name' suffix so the stored GridQso.state is the clean code consumed by persistence and AwardCalculator. Add a test covering China/Japan/US formats. --- .../core/data/repository/LoTWRepository.kt | 12 ++++++++++- .../data/repository/LoTWRepositoryTest.kt | 21 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/LoTWRepository.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/LoTWRepository.kt index afc54f19..e0055629 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/LoTWRepository.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/LoTWRepository.kt @@ -212,7 +212,7 @@ class LoTWRepository : ILoTWRepository { line.startsWith(" cqz = adifValue(line).toIntOrNull() line.startsWith(" - state = adifValue(line).trim().ifBlank { null } + state = adifValue(line).trim().ifBlank { null }?.let { normalizeState(it) } line.startsWith(" { // VUCC_GRIDS holds a comma-separated list of grids // ("EN52en,EN53fa"), up to four for contacts spanning @@ -231,6 +231,16 @@ class LoTWRepository : ILoTWRepository { private fun adifValue(line: String): String = line.substringAfter('>').substringBefore("E<").trim() + /** + * LoTW returns STATE as "CODE // NAME" (e.g. "HB // Hubei" for China, + * "34 // Tottori-ken" for Japan, "CA // California" for the US). The award + * statistics match on the short CODE only (China 2-letter province pinyin, + * Japan 2-digit prefecture, US 2-letter state), so strip the " // NAME" + * suffix here once, storing the clean code for every downstream consumer + * (persistence, AwardCalculator). + */ + private fun normalizeState(raw: String): String = raw.substringBefore(" // ").trim() + /** "20260820" + "1130" (or "113000") -> UTC epoch ms; 0 when unparseable. */ private fun adifTimestampToEpoch(date: String, time: String): Long = try { val d = date.trim() diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/LoTWRepositoryTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/LoTWRepositoryTest.kt index 49f59ad7..1abc3a57 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/LoTWRepositoryTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/LoTWRepositoryTest.kt @@ -156,11 +156,13 @@ class LoTWRepositoryTest { @Test fun parseQsosExtractsAwardFields() { // Award statistics rely on the DXCC/CQZ/STATE fields coming straight - // from LoTW's qso_qsldetail report (STATE depends on DXCC). + // from LoTW's qso_qsldetail report (STATE depends on DXCC). LoTW + // writes STATE as "CODE // NAME" (verified with a real report 2026-09); + // the parser must strip the name suffix and keep the bare code. val qso = "BG7XYZ\n" + "20260820\n1130\n" + "SAT\nFO-29\nCW\n" + - "318\nCHINA\n24\nGD\n" + + "318\nCHINA\n24\nGD // Guangdong\n" + "OL62\n\n" val result = repo.parseConfirmedGridQsos(report(qso))!! val parsed = result["OL62"]!!.first() @@ -170,6 +172,21 @@ class LoTWRepositoryTest { assertEquals("GD", parsed.state) } + @Test + fun parseQsosStripsStateNameSuffixForAllEntities() { + // Japan prefecture: "34 // Tottori-ken" -> "34" (WAJA matching needs + // the 2-digit code). US states also arrive as "CODE // Name". + val jp = "JH0ABC\n20260820\n1130\n" + + "SAT\nRS-44\n339\nJAPAN\n" + + "34 // Tottori-ken\nPM95\n\n" + val us = "K1ABC\n20260820\n1130\n" + + "SAT\nAO-07\n291\nUNITED STATES\n" + + "CA // California\nEM40\n\n" + val result = repo.parseConfirmedGridQsos(report(jp, us))!! + assertEquals("34", result["PM95"]!!.first().state) + assertEquals("CA", result["EM40"]!!.first().state) + } + @Test fun parseQsosLeavesAwardFieldsNullWhenAbsent() { val qso = "JH0ABC\n20260820\n1130\n" +