+ # this scores each candidate according to its skimmer's QRG score (i.e. how often it agrees with its peers)
+ # what happens is hash of all QRGs in candidates are incremented by that skimmer's reputation for "accuracy"
+ # or, more exactly, past agreement with the consensus. This score can be from -5 -> +5.
+ my %qrg = ();
+ my $skimmer;
+ my $sk;
+ my $band;
+ my %seen = ();
+ foreach $r (@$cand) {
+ next unless ref $r;
+ if (exists $seen{$r->[ROrigin]}) {
+ $r = 0;
+ next;
+ }
+ $seen{$r->[ROrigin]} = 1;
+ $band ||= int $r->[RQrg] / 1000;
+ $sk = "SKIM|$r->[ROrigin]|$band"; # thus only once per set of candidates
+ $skimmer = $spots->{$sk};
+ unless ($skimmer) {
+ $skimmer = $spots->{$sk} = [1, 0, 0, $now, []]; # this first time, this new skimmer gets the benefit of the doubt on frequency.
+ dbg("RBN:SKIM new slot $sk " . $json->encode($skimmer)) if $rbnskim && isdbg('rbnskim');
+ }
+ $qrg{$r->[RQrg]} += ($skimmer->[DScore] || 1);
+ }
+
+ # determine the most likely qrg and then set it - NOTE (-)ve votes, generated by the skimmer scoring system above, are ignored
+ my @deviant;
+ my $c = 0;
+ my $mv = 0;
+ my $qrg = 0;
+ while (my ($k, $votes) = each %qrg) {
+ if ($votes >= $mv) {
+ $qrg = $k;
+ $mv = $votes;
+ }
+ ++$c;
+ }
+
+ # Ignore possible spots with 0 QRG score - as determined by the skimmer scoring system above - as they are likely to be wrong
+ unless ($qrg > 0) {
+ if ( $rbnskim && isdbg('rbnskim')) {
+ my $keys;
+ while (my ($k, $v) = (each %qrg)) {
+ $keys .= "$k=>$v, ";
+ }
+ $keys =~ /,\s*$/;
+ my $i = 0;
+ foreach $r (@$cand) {
+ next unless $r && ref $r;
+ dbg "RBN:SKIM cand $i QRG likely wrong from '$sp' = $r->[RCall] on $r->[RQrg] by $r->[ROrigin] \@ $r->[RTime] (qrgs: $keys c: $c) route: $dxchan->{call}, ignored";
+ ++$i;
+ }
+ }
+ delete $spots->{$sp}; # get rid
+ delete $dxchan->{queue}->{$sp};
+ next;
+ }
+
+ # detemine and spit out the deviants. Then adjust the scores according to whether it is a deviant or good
+ # NOTE: deviant nodes can become good (or less bad), and good nodes bad (or less good) on each spot that
+ # they generate. This is based solely on each skimmer's agreement (or not) with the "consensus" score generated
+ # above ($qrg). The resultant score + good + bad is stored per band and will be used the next time a spot
+ # appears on this band from each skimmer.
+ foreach $r (@$cand) {
+ next unless $r && ref $r;
+ my $diff = $c > 1 ? nearest(.1, $r->[RQrg] - $qrg) : 0;
+ $sk = "SKIM|$r->[ROrigin]|$band";
+ $skimmer = $spots->{$sk};
+ if ($diff) {
+ ++$skimmer->[DBad] if $skimmer->[DBad] < $maxdeviants;
+ --$skimmer->[DGood] if $skimmer->[DGood] > 0;
+ push @deviant, sprintf("$r->[ROrigin]:%+.1f", $diff);
+ push @{$skimmer->[DEviants]}, $diff;
+ shift @{$skimmer->[DEviants]} while @{$skimmer->[DEviants]} > $maxdeviants;
+ } else {
+ ++$skimmer->[DGood] if $skimmer->[DGood] < $maxdeviants;
+ --$skimmer->[DBad] if $skimmer->[DBad] > 0;
+ shift @{$skimmer->[DEviants]};