diff --git a/internal/classifier/rules.go b/internal/classifier/rules.go index b7f3149..1381d7d 100644 --- a/internal/classifier/rules.go +++ b/internal/classifier/rules.go @@ -106,6 +106,7 @@ func RuleClassify(feat Features) Classification { BW3dB: bw, Features: feat, SecondBest: second, + Scores: scores, } } diff --git a/internal/classifier/types.go b/internal/classifier/types.go index d70539f..379e3a6 100644 --- a/internal/classifier/types.go +++ b/internal/classifier/types.go @@ -39,11 +39,12 @@ type Features struct { // Classification is the classifier output attached to signals/events. type Classification struct { - ModType SignalClass `json:"mod_type"` - Confidence float64 `json:"confidence"` - BW3dB float64 `json:"bw_3db_hz"` - Features Features `json:"features,omitempty"` - SecondBest SignalClass `json:"second_best,omitempty"` + ModType SignalClass `json:"mod_type"` + Confidence float64 `json:"confidence"` + BW3dB float64 `json:"bw_3db_hz"` + Features Features `json:"features,omitempty"` + SecondBest SignalClass `json:"second_best,omitempty"` + Scores map[SignalClass]float64 `json:"scores,omitempty"` } // SignalInput is the minimal input needed for classification. diff --git a/web/app.js b/web/app.js index 59a1043..86ed31e 100644 --- a/web/app.js +++ b/web/app.js @@ -86,6 +86,7 @@ const decodeEventBtn = qs('decodeEventBtn'); const decodeModeSelect = qs('decodeMode'); const recordingMetaEl = qs('recordingMeta'); const decodeResultEl = qs('decodeResult'); +const classifierScoresEl = qs('classifierScores'); const recordingMetaLink = qs('recordingMetaLink'); const recordingIQLink = qs('recordingIQLink'); const recordingAudioLink = qs('recordingAudioLink'); @@ -1007,6 +1008,19 @@ function openDrawer(ev) { detailSnrEl.textContent = `${(ev.snr_db || 0).toFixed(1)} dB`; detailDurEl.textContent = fmtMs(ev.duration_ms || 0); detailClassEl.textContent = ev.class?.mod_type || '-'; + if (classifierScoresEl) { + const scores = ev.class?.scores; + if (scores && typeof scores === 'object') { + const rows = Object.entries(scores) + .sort((a, b) => b[1] - a[1]) + .slice(0, 6) + .map(([k, v]) => `${k}:${v.toFixed(2)}`) + .join(' · '); + classifierScoresEl.textContent = rows ? `Classifier scores: ${rows}` : 'Classifier scores: -'; + } else { + classifierScoresEl.textContent = 'Classifier scores: -'; + } + } if (recordingMetaEl) { recordingMetaEl.textContent = 'Recording: -'; } @@ -1427,6 +1441,19 @@ if (recordingList) { const rds = meta.rds_ps ? `RDS: ${meta.rds_ps}` : ''; decodeResultEl.textContent = `Decode: ${rds}`; } + if (classifierScoresEl) { + const scores = meta.classification?.scores; + if (scores && typeof scores === 'object') { + const rows = Object.entries(scores) + .sort((a, b) => b[1] - a[1]) + .slice(0, 6) + .map(([k, v]) => `${k}:${v.toFixed(2)}`) + .join(' · '); + classifierScoresEl.textContent = rows ? `Classifier scores: ${rows}` : 'Classifier scores: -'; + } else { + classifierScoresEl.textContent = 'Classifier scores: -'; + } + } } catch {} }); } diff --git a/web/index.html b/web/index.html index 024b8a1..8c3a3e4 100644 --- a/web/index.html +++ b/web/index.html @@ -80,7 +80,14 @@