mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat(vucc): per operated-grid VUCC counting
GridQso gains myGrid (ADIF MY_GRIDSQUARE) captured per QSO during LoTW parsing (handles alphabetical field order + EOR reset) and persisted as JSON key 'mg' (absent in pre-myGrid data -> null). Legacy syncs keep the global VUCC count; one resync unlocks per-grid counting.
This commit is contained in:
1 parent
6a6399b606
commit
25b982ac59
4 files changed
+37
-3
No files matched your search
+11
-1
@@ -169,6 +169,7 @@ class LoTWRepository : ILoTWRepository {
|
||||
var country: String? = null
|
||||
var cqz: Int? = null
|
||||
var state: String? = null
|
||||
var myGrid: String? = null
|
||||
val gridsInRecord = mutableListOf<String>()
|
||||
|
||||
fun emitRecord() {
|
||||
@@ -177,7 +178,8 @@ class LoTWRepository : ILoTWRepository {
|
||||
val qso = com.rtbishop.look4sat.core.domain.model.GridQso(
|
||||
call = call, epochMs = epochMs, satName = satName,
|
||||
mode = mode, bandUp = bandUp, bandDown = bandDown,
|
||||
dxcc = dxcc, country = country, cqz = cqz, state = state
|
||||
dxcc = dxcc, country = country, cqz = cqz, state = state,
|
||||
myGrid = myGrid
|
||||
)
|
||||
for (grid in gridsInRecord) {
|
||||
result.getOrPut(grid) { mutableListOf() }.add(qso)
|
||||
@@ -188,6 +190,7 @@ class LoTWRepository : ILoTWRepository {
|
||||
propMode = null; call = ""; qsoDate = ""; timeOn = ""
|
||||
satName = ""; mode = ""; bandUp = ""; bandDown = ""
|
||||
dxcc = null; country = null; cqz = null; state = null
|
||||
myGrid = null
|
||||
gridsInRecord.clear()
|
||||
}
|
||||
|
||||
@@ -222,6 +225,13 @@ class LoTWRepository : ILoTWRepository {
|
||||
cqz = adifValue(line).toIntOrNull()
|
||||
line.startsWith("<STATE:") ->
|
||||
state = adifValue(line).trim().ifBlank { null }?.let { normalizeState(it) }
|
||||
line.startsWith("<MY_GRIDSQUARE:") -> {
|
||||
// Own-station grid (must not be confused with GRIDSQUARE —
|
||||
// the opposite station's grid). Recorded per QSO so awards
|
||||
// can be counted per operated grid.
|
||||
val value = adifValue(line)
|
||||
if (value.length >= 4) myGrid = value.take(4).uppercase()
|
||||
}
|
||||
line.startsWith("<GRIDSQUARE:") || line.startsWith("<VUCC_GRIDS:") -> {
|
||||
// VUCC_GRIDS holds a comma-separated list of grids
|
||||
// ("EN52en,EN53fa"), up to four for contacts spanning
|
||||
|
||||
@@ -204,7 +204,9 @@ class SettingsRepo(
|
||||
dxcc = o.optInt("dx", 0).takeIf { it > 0 },
|
||||
country = o.optString("cty").ifBlank { null },
|
||||
cqz = o.optInt("cq", 0).takeIf { it > 0 },
|
||||
state = o.optString("st").ifBlank { null }
|
||||
state = o.optString("st").ifBlank { null },
|
||||
// Own-grid field: absent in pre-myGrid data -> null.
|
||||
myGrid = o.optString("mg").ifBlank { null }
|
||||
)
|
||||
}
|
||||
if (list.isNotEmpty()) result[grid] = list
|
||||
@@ -232,6 +234,7 @@ class SettingsRepo(
|
||||
.put("cty", q.country ?: "")
|
||||
.put("cq", q.cqz ?: 0)
|
||||
.put("st", q.state ?: "")
|
||||
.put("mg", q.myGrid ?: "")
|
||||
)
|
||||
}
|
||||
root.put(grid, array)
|
||||
|
||||
+19
@@ -3,6 +3,7 @@ package com.rtbishop.look4sat.core.data.repository
|
||||
import com.rtbishop.look4sat.core.domain.repository.LoTWResult
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class LoTWRepositoryTest {
|
||||
@@ -150,6 +151,24 @@ class LoTWRepositoryTest {
|
||||
assertNull(repo.parseConfirmedGridQsos("<HTML>Username/password incorrect</HTML>"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQsosCapturesOwnGridPerQso() {
|
||||
// MY_GRIDSQUARE arrives BEFORE PROP_MODE (ADIF fields are emitted
|
||||
// alphabetically); the per-record buffer must still attach it to the QSO.
|
||||
val qso1 = "<CALL:5>A50QO\n<QSO_DATE:8>20260820\n<MY_GRIDSQUARE:4>OL62\n" +
|
||||
"<PROP_MODE:3>SAT\n<SAT_NAME:5>FO-29\n<MODE:2>CW\n" +
|
||||
"<BAND:3>70CM\n<BAND_RX:3>2M\n<GRIDSQUARE:4>NL47\n<EOR>\n"
|
||||
val qso2 = "<CALL:6>BG7ZFK\n<QSO_DATE:8>20260819\n<MY_GRIDSQUARE:4>OL72\n" +
|
||||
"<PROP_MODE:3>SAT\n<SAT_NAME:5>SO-50\n<MODE:3>FM\n" +
|
||||
"<BAND:3>70CM\n<BAND_RX:3>2M\n<GRIDSQUARE:4>OK48\n<EOR>\n"
|
||||
val result = repo.parseConfirmedGridQsos(report(qso1, qso2))!!
|
||||
assertEquals("OL62", result["NL47"]!!.first().myGrid)
|
||||
assertEquals("OL72", result["OK48"]!!.first().myGrid)
|
||||
// Ground QSO (no PROP_MODE=SAT) must not leak its MY_GRIDSQUARE.
|
||||
val ground = "<CALL:6>BA7OPF\n<QSO_DATE:8>20260821\n<MY_GRIDSQUARE:4>OL62\n<GRIDSQUARE:4>NL47\n<EOR>\n"
|
||||
assertTrue(repo.parseConfirmedGridQsos(report(qso1, ground))!!.values.all { it.all { q -> q.myGrid == "OL62" } })
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region parseRoamedGrids (MY_GRIDSQUARE = grids the account operated from)
|
||||
|
||||
@@ -48,7 +48,9 @@ data class GridQso(
|
||||
val dxcc: Int? = null,
|
||||
val country: String? = null,
|
||||
val cqz: Int? = null,
|
||||
val state: String? = null
|
||||
val state: String? = null,
|
||||
/** 4-char grid the station itself operated from (ADIF MY_GRIDSQUARE). */
|
||||
val myGrid: String? = null
|
||||
) {
|
||||
/** Short uplink/downlink band label ("U/V", "V/A"), or "" when unknown. */
|
||||
val bandLabel: String
|
||||
|
||||
Reference in new issue
Block a user