feat(db): fill in the distances nothing ever recorded
The published page derives a distance as it renders, which fixed the empty column there but not the cause: the field is empty in the database, so it goes out empty in every ADIF export and leaves the same gap in whoever imports it. BackfillDistances computes it from the two locators for QSOs that have both. Only where it is EMPTY: a stored distance came from the log that recorded the contact, which knew the real positions, and two four-character squares are a worse answer that must not overwrite a better one. Whole kilometres, for the same reason the published column is. In the Database panel rather than beside the county backfill, which lives under US Counties: this one touches the whole logbook, whatever country the QSOs are in.
This commit is contained in:
@@ -10337,6 +10337,63 @@ func (a *App) DownloadULSCounties() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BackfillUSCountiesResult summarises a bulk county/grid backfill over the log.
|
// BackfillUSCountiesResult summarises a bulk county/grid backfill over the log.
|
||||||
|
// BackfillDistancesResult reports what a distance backfill did.
|
||||||
|
type BackfillDistancesResult struct {
|
||||||
|
Scanned int `json:"scanned"` // QSOs examined
|
||||||
|
Filled int `json:"filled"` // QSOs that gained a distance
|
||||||
|
NoGrid int `json:"no_grid"` // skipped: one of the two locators is missing
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackfillDistances computes DISTANCE for past QSOs that have both locators and
|
||||||
|
// no distance recorded.
|
||||||
|
//
|
||||||
|
// Nothing has ever computed it when logging — the column is only filled by an
|
||||||
|
// ADIF import that carried one — so a log made in OpsLog has it empty
|
||||||
|
// throughout. The published web page works around that by deriving the distance
|
||||||
|
// as it renders, but the column still travels empty into every ADIF export, and
|
||||||
|
// that is what leaves the gap in someone else's log.
|
||||||
|
//
|
||||||
|
// Only fills what is EMPTY. A stored distance came from the log that recorded
|
||||||
|
// the contact, which knew the real positions; two four-character squares are a
|
||||||
|
// worse answer and must not overwrite a better one.
|
||||||
|
func (a *App) BackfillDistances() (BackfillDistancesResult, error) {
|
||||||
|
var res BackfillDistancesResult
|
||||||
|
if a.qso == nil {
|
||||||
|
return res, fmt.Errorf("db not initialized")
|
||||||
|
}
|
||||||
|
rows, err := a.qso.List(a.ctx, qso.ListFilter{Limit: 1_000_000})
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
for i := range rows {
|
||||||
|
q := rows[i]
|
||||||
|
res.Scanned++
|
||||||
|
if q.Distance != nil && *q.Distance > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
km, ok := geo.DistanceBetweenGrids(q.MyGrid, q.Grid)
|
||||||
|
if !ok || km <= 0 {
|
||||||
|
res.NoGrid++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Whole kilometres: the squares are tens of kilometres across and a
|
||||||
|
// decimal would assert an accuracy the grids do not carry.
|
||||||
|
v := math.Round(km)
|
||||||
|
q.Distance = &v
|
||||||
|
if err := a.qso.Update(a.ctx, q); err != nil {
|
||||||
|
applog.Printf("backfill distance: QSO %d: %v", q.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res.Filled++
|
||||||
|
}
|
||||||
|
applog.Printf("backfill distance: %d scanned, %d filled, %d without both grids",
|
||||||
|
res.Scanned, res.Filled, res.NoGrid)
|
||||||
|
if res.Filled > 0 {
|
||||||
|
a.invalidateAwardStats()
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
type BackfillUSCountiesResult struct {
|
type BackfillUSCountiesResult struct {
|
||||||
Scanned int `json:"scanned"` // US QSOs examined
|
Scanned int `json:"scanned"` // US QSOs examined
|
||||||
County int `json:"county"` // QSOs that gained a county
|
County int `json:"county"` // QSOs that gained a county
|
||||||
|
|||||||
+4
-2
@@ -5,12 +5,14 @@
|
|||||||
"en": [
|
"en": [
|
||||||
"Bulk edit can now set mode, submode and RST. They were excluded as per-QSO fields, which missed the point: bulk edit is for repairing a batch — an import that mapped every contact to SSB, an ADIF with no mode at all — and refusing meant editing a hundred rows one at a time. Setting the mode clears the submode, since one left over from the old mode contradicts the new one. Band stays with frequency, which already sets the two together.",
|
"Bulk edit can now set mode, submode and RST. They were excluded as per-QSO fields, which missed the point: bulk edit is for repairing a batch — an import that mapped every contact to SSB, an ADIF with no mode at all — and refusing meant editing a hundred rows one at a time. Setting the mode clears the submode, since one left over from the old mode contradicts the new one. Band stays with frequency, which already sets the two together.",
|
||||||
"Web publishing: the Distance column is no longer empty. Nothing computes a distance when a QSO is logged — the stored field is only ever filled by an ADIF import that carried one — so it is worked out from the two locators instead, rounded to whole kilometres.",
|
"Web publishing: the Distance column is no longer empty. Nothing computes a distance when a QSO is logged — the stored field is only ever filled by an ADIF import that carried one — so it is worked out from the two locators instead, rounded to whole kilometres.",
|
||||||
"Web publishing: the page now widens to fit the table. It was capped at a comfortable reading width, so with more than about eight columns the rest sat behind a scrollbar on a screen wide enough to show them all — and Windows hides that scrollbar until something moves, which made a table that scrolls look like a table missing columns. Columns also take the width their contents need instead of being squeezed to fit first."
|
"Web publishing: the page now widens to fit the table. It was capped at a comfortable reading width, so with more than about eight columns the rest sat behind a scrollbar on a screen wide enough to show them all — and Windows hides that scrollbar until something moves, which made a table that scrolls look like a table missing columns. Columns also take the width their contents need instead of being squeezed to fit first.",
|
||||||
|
"A Fill distances button (Settings › Database) works out DISTANCE for past QSOs from their two locators. Nothing has ever recorded one when logging, so the field goes out empty in every ADIF export; this fills what is empty and leaves any distance already recorded alone."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"L édition groupée sait enfin régler le mode, le sous-mode et le RST. Ils étaient exclus comme champs propres à chaque QSO, ce qui manquait l essentiel : l édition groupée sert à RÉPARER un lot — un import qui a tout mis en SSB, un ADIF sans aucun mode — et refuser obligeait à corriger cent lignes une par une. Régler le mode efface le sous-mode, celui de l ancien mode contredisant le nouveau. La bande reste avec la fréquence, qui pose déjà les deux ensemble.",
|
"L édition groupée sait enfin régler le mode, le sous-mode et le RST. Ils étaient exclus comme champs propres à chaque QSO, ce qui manquait l essentiel : l édition groupée sert à RÉPARER un lot — un import qui a tout mis en SSB, un ADIF sans aucun mode — et refuser obligeait à corriger cent lignes une par une. Régler le mode efface le sous-mode, celui de l ancien mode contredisant le nouveau. La bande reste avec la fréquence, qui pose déjà les deux ensemble.",
|
||||||
"Publication web : la colonne Distance n est plus vide. Rien ne calcule de distance à l enregistrement d un QSO — le champ stocké n est rempli que par un ADIF importé qui en portait une — elle est donc déduite des deux locators, arrondie au kilomètre.",
|
"Publication web : la colonne Distance n est plus vide. Rien ne calcule de distance à l enregistrement d un QSO — le champ stocké n est rempli que par un ADIF importé qui en portait une — elle est donc déduite des deux locators, arrondie au kilomètre.",
|
||||||
"Publication web : la page s élargit désormais à la taille du tableau. Elle était bridée à une largeur de lecture confortable, donc au-delà de huit colonnes environ le reste passait derrière une barre de défilement sur un écran assez large pour tout montrer — et Windows masque cette barre tant que rien ne bouge, si bien qu un tableau qui défile ressemblait à un tableau amputé. Les colonnes prennent aussi la largeur qu il leur faut au lieu d être comprimées d abord."
|
"Publication web : la page s élargit désormais à la taille du tableau. Elle était bridée à une largeur de lecture confortable, donc au-delà de huit colonnes environ le reste passait derrière une barre de défilement sur un écran assez large pour tout montrer — et Windows masque cette barre tant que rien ne bouge, si bien qu un tableau qui défile ressemblait à un tableau amputé. Les colonnes prennent aussi la largeur qu il leur faut au lieu d être comprimées d abord.",
|
||||||
|
"Un bouton Compléter les distances (Paramètres › Base de données) déduit DISTANCE des deux locators pour les QSO passés. Rien ne l a jamais enregistrée à la journalisation, le champ part donc vide dans chaque export ADIF ; ceci remplit ce qui est vide et ne touche pas à une distance déjà enregistrée."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
TestLoTWUpload, ListTQSLStationLocations,
|
TestLoTWUpload, ListTQSLStationLocations,
|
||||||
DownloadLoTWUsers, GetLoTWUsersStatus,
|
DownloadLoTWUsers, GetLoTWUsersStatus,
|
||||||
GetScpStatus, SetScpEnabled, DownloadScp,
|
GetScpStatus, SetScpEnabled, DownloadScp,
|
||||||
DownloadULSCounties, ULSStatus, BackfillUSCounties,
|
DownloadULSCounties, ULSStatus, BackfillUSCounties, BackfillDistances,
|
||||||
ComputeStationInfo,
|
ComputeStationInfo,
|
||||||
GetUIPref, SetUIPref,
|
GetUIPref, SetUIPref,
|
||||||
GetFlexState, GetFlexBandAntennas, SaveFlexBandAntennas, GetFlexBandPower, SaveFlexBandPower,
|
GetFlexState, GetFlexBandAntennas, SaveFlexBandAntennas, GetFlexBandPower, SaveFlexBandPower,
|
||||||
@@ -1495,6 +1495,14 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
try { await DownloadULSCounties(); }
|
try { await DownloadULSCounties(); }
|
||||||
catch (e: any) { setUlsBusy(false); setUlsProgress(null); setUlsMsg({ ok: false, text: String(e?.message ?? e) }); }
|
catch (e: any) { setUlsBusy(false); setUlsProgress(null); setUlsMsg({ ok: false, text: String(e?.message ?? e) }); }
|
||||||
};
|
};
|
||||||
|
const [distBusy, setDistBusy] = useState(false);
|
||||||
|
const [distMsg, setDistMsg] = useState<string | null>(null);
|
||||||
|
const runDistBackfill = async () => {
|
||||||
|
setDistBusy(true); setDistMsg(null);
|
||||||
|
try { const r: any = await BackfillDistances(); setDistMsg(t("db.distDone", { f: r?.filled ?? 0, s: r?.scanned ?? 0, n: r?.no_grid ?? 0 })); }
|
||||||
|
catch (e: any) { setDistMsg(String(e?.message ?? e)); }
|
||||||
|
finally { setDistBusy(false); }
|
||||||
|
};
|
||||||
const runBackfill = async () => {
|
const runBackfill = async () => {
|
||||||
setBackfillBusy(true); setBackfillMsg(null);
|
setBackfillBusy(true); setBackfillMsg(null);
|
||||||
try { const r: any = await BackfillUSCounties(); setBackfillMsg(t('uscty.backfillDone', { c: r?.county ?? 0, g: r?.grid ?? 0, s: r?.scanned ?? 0 })); }
|
try { const r: any = await BackfillUSCounties(); setBackfillMsg(t('uscty.backfillDone', { c: r?.county ?? 0, g: r?.grid ?? 0, s: r?.scanned ?? 0 })); }
|
||||||
@@ -5323,6 +5331,21 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
|||||||
<>
|
<>
|
||||||
<SectionHeader title={t('sec.database')} />
|
<SectionHeader title={t('sec.database')} />
|
||||||
|
|
||||||
|
{/* Distance backfill. Here rather than beside the county one, which lives
|
||||||
|
in the US Counties panel: this touches the whole logbook, whatever
|
||||||
|
country the QSOs are in. */}
|
||||||
|
<div className="rounded-md border border-border p-3 space-y-2 max-w-2xl mb-5">
|
||||||
|
<div className="text-xs font-medium">{t('db.distTitle')}</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground leading-relaxed">{t('db.distIntro')}</p>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Button size="sm" variant="secondary" onClick={runDistBackfill} disabled={distBusy}>
|
||||||
|
{distBusy ? <Loader2 className="size-3.5 animate-spin mr-1.5" /> : null}
|
||||||
|
{t('db.distRun')}
|
||||||
|
</Button>
|
||||||
|
{distMsg && <span className="text-xs text-muted-foreground">{distMsg}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Settings / application database (settings + profiles) — always shown,
|
{/* Settings / application database (settings + profiles) — always shown,
|
||||||
distinct from the QSO logbook so the two are never confused. */}
|
distinct from the QSO logbook so the two are never confused. */}
|
||||||
<div className="space-y-2 max-w-2xl mb-5 border border-border/60 rounded-md p-3">
|
<div className="space-y-2 max-w-2xl mb-5 border border-border/60 rounded-md p-3">
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ const en: Dict = {
|
|||||||
'uscty.download': 'Download',
|
'uscty.download': 'Download',
|
||||||
'uscty.update': 'Update',
|
'uscty.update': 'Update',
|
||||||
'uscty.done': 'County database ready — {n} callsigns.',
|
'uscty.done': 'County database ready — {n} callsigns.',
|
||||||
'uscty.backfillTitle': 'Fill existing QSOs',
|
'db.distTitle': 'Fill in distances', 'db.distIntro': 'Nothing records a distance when a QSO is logged, so the field is empty in every ADIF you export. This works it out from the two locators, for QSOs that have both and no distance yet. A distance already recorded is left alone.', 'db.distRun': 'Fill distances', 'db.distDone': '{f} filled out of {s} scanned; {n} had no pair of locators.', 'uscty.backfillTitle': 'Fill existing QSOs',
|
||||||
'uscty.backfillIntro': 'Resolve county (and grid) for US QSOs already in your log that are missing them. Existing values are kept — only blanks are filled.',
|
'uscty.backfillIntro': 'Resolve county (and grid) for US QSOs already in your log that are missing them. Existing values are kept — only blanks are filled.',
|
||||||
'uscty.backfillRun': 'Fill missing counties',
|
'uscty.backfillRun': 'Fill missing counties',
|
||||||
'uscty.backfillDone': '{s} US QSOs scanned · {c} counties, {g} grids filled.',
|
'uscty.backfillDone': '{s} US QSOs scanned · {c} counties, {g} grids filled.',
|
||||||
@@ -578,7 +578,7 @@ const fr: Dict = {
|
|||||||
'uscty.download': 'Télécharger',
|
'uscty.download': 'Télécharger',
|
||||||
'uscty.update': 'Mettre à jour',
|
'uscty.update': 'Mettre à jour',
|
||||||
'uscty.done': 'Base des comtés prête — {n} indicatifs.',
|
'uscty.done': 'Base des comtés prête — {n} indicatifs.',
|
||||||
'uscty.backfillTitle': 'Compléter les QSO existants',
|
'db.distTitle': 'Compléter les distances', 'db.distIntro': 'Rien n enregistre de distance quand un QSO est journalisé, le champ part donc vide dans chaque ADIF exporté. Ceci la déduit des deux locators, pour les QSO qui ont les deux et pas encore de distance. Une distance déjà enregistrée n est pas touchée.', 'db.distRun': 'Compléter les distances', 'db.distDone': '{f} complétés sur {s} examinés ; {n} sans les deux locators.', 'uscty.backfillTitle': 'Compléter les QSO existants',
|
||||||
'uscty.backfillIntro': "Résout le comté (et le locator) pour les QSO US déjà dans ton log qui n'en ont pas. Les valeurs existantes sont conservées — seuls les vides sont remplis.",
|
'uscty.backfillIntro': "Résout le comté (et le locator) pour les QSO US déjà dans ton log qui n'en ont pas. Les valeurs existantes sont conservées — seuls les vides sont remplis.",
|
||||||
'uscty.backfillRun': 'Remplir les comtés manquants',
|
'uscty.backfillRun': 'Remplir les comtés manquants',
|
||||||
'uscty.backfillDone': '{s} QSO US analysés · {c} comtés, {g} locators remplis.',
|
'uscty.backfillDone': '{s} QSO US analysés · {c} comtés, {g} locators remplis.',
|
||||||
|
|||||||
Vendored
+2
@@ -88,6 +88,8 @@ export function AwardRefsForQSOs(arg1:Array<number>):Promise<Record<number, Reco
|
|||||||
|
|
||||||
export function AwardsFolder():Promise<string>;
|
export function AwardsFolder():Promise<string>;
|
||||||
|
|
||||||
|
export function BackfillDistances():Promise<main.BackfillDistancesResult>;
|
||||||
|
|
||||||
export function BackfillUSCounties():Promise<main.BackfillUSCountiesResult>;
|
export function BackfillUSCounties():Promise<main.BackfillUSCountiesResult>;
|
||||||
|
|
||||||
export function BandSlotQSOs(arg1:string,arg2:number,arg3:string,arg4:string):Promise<Array<qso.QSO>>;
|
export function BandSlotQSOs(arg1:string,arg2:number,arg3:string,arg4:string):Promise<Array<qso.QSO>>;
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ export function AwardsFolder() {
|
|||||||
return window['go']['main']['App']['AwardsFolder']();
|
return window['go']['main']['App']['AwardsFolder']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BackfillDistances() {
|
||||||
|
return window['go']['main']['App']['BackfillDistances']();
|
||||||
|
}
|
||||||
|
|
||||||
export function BackfillUSCounties() {
|
export function BackfillUSCounties() {
|
||||||
return window['go']['main']['App']['BackfillUSCounties']();
|
return window['go']['main']['App']['BackfillUSCounties']();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1931,6 +1931,22 @@ export namespace main {
|
|||||||
this.to = source["to"];
|
this.to = source["to"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class BackfillDistancesResult {
|
||||||
|
scanned: number;
|
||||||
|
filled: number;
|
||||||
|
no_grid: number;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new BackfillDistancesResult(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.scanned = source["scanned"];
|
||||||
|
this.filled = source["filled"];
|
||||||
|
this.no_grid = source["no_grid"];
|
||||||
|
}
|
||||||
|
}
|
||||||
export class BackfillUSCountiesResult {
|
export class BackfillUSCountiesResult {
|
||||||
scanned: number;
|
scanned: number;
|
||||||
county: number;
|
county: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user