feat(dxped): the announced DX, judged against your own log

A DXpeditions tab holding the two feeds the DX world announces itself
on: NG3K's ADXO (structured — dates, entity, calls, bands, modes, QSL
route) and DX-World's headlines. What a logger can say that a news
reader cannot is whether the operation is worth chasing, so every
announcement is put through the SAME verdict the cluster paints on a
spot — one badge, strongest wins, dimmed when the need is only a missing
QSL — with an 'only what I need' filter. One click watches every
callsign of an operation; the news headlines are mined for callsigns so
they can be watched the same way.

Parsers ported from DXHunter's and pinned with table tests against real
feed text: the 'as PJ2/W2APF' mining, the DXCC-prefix-first
normalisation without which no spot ever matches, both ADXO date forms,
and the '160-6m' span whose unit is written once (DXHunter read that as
6m alone and lost the low end).

Watchlist membership now announces itself app-wide, on the same card as
a new version: the bindings emit watchlist:changed, so a call added from
the cluster or this tab is confirmed where the operator is actually
looking — the panel's own inline message never was.
This commit is contained in:
2026-08-31 17:51:45 +02:00
parent 629bd8d84f
commit 187ac9aa84
13 changed files with 1202 additions and 9 deletions
+69
View File
@@ -1466,6 +1466,37 @@ export namespace contest {
}
export namespace dxped {
export class News {
title: string;
link: string;
pub_date: string;
excerpt: string;
creator: string;
image_url: string;
tag: string;
calls: string[];
static createFrom(source: any = {}) {
return new News(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.title = source["title"];
this.link = source["link"];
this.pub_date = source["pub_date"];
this.excerpt = source["excerpt"];
this.creator = source["creator"];
this.image_url = source["image_url"];
this.tag = source["tag"];
this.calls = source["calls"];
}
}
}
export namespace extsvc {
export class ServiceConfig {
@@ -2674,6 +2705,44 @@ export namespace main {
this.rec_slot = source["rec_slot"];
}
}
export class DXpedition {
dxcc: string;
callsign: string;
calls: string[];
start_date: string;
end_date: string;
bands: string[];
modes: string[];
qsl: string;
operators: string;
source: string;
link: string;
status: string;
status_chase: string;
unconfirmed: boolean;
static createFrom(source: any = {}) {
return new DXpedition(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.dxcc = source["dxcc"];
this.callsign = source["callsign"];
this.calls = source["calls"];
this.start_date = source["start_date"];
this.end_date = source["end_date"];
this.bands = source["bands"];
this.modes = source["modes"];
this.qsl = source["qsl"];
this.operators = source["operators"];
this.source = source["source"];
this.link = source["link"];
this.status = source["status"];
this.status_chase = source["status_chase"];
this.unconfirmed = source["unconfirmed"];
}
}
export class DatabaseSettings {
path: string;
default_path: string;