Merge pull request #2690 from AndreasK79/calltester

Calltester
This commit is contained in:
Andreas Kristiansen
2025-12-23 13:13:31 +01:00
committed by GitHub
6 changed files with 506 additions and 55 deletions

View File

@@ -1,6 +1,8 @@
<?php
use Wavelog\Dxcc\Dxcc;
require_once APPPATH . '../src/Dxcc/Dxcc.php';
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Calltester extends CI_Controller {
@@ -12,37 +14,74 @@ class Calltester extends CI_Controller {
}
public function db() {
public function index() {
set_time_limit(3600);
// Starting clock time in seconds
$start_time = microtime(true);
$this->load->model('logbook_model');
$callarray = $this->getQsos(null);
$this->load->model('stations');
$sql = 'select distinct col_country, col_call, col_dxcc, date(col_time_on) date from ' . $this->config->item('table_name');
$query = $this->db->query($sql);
$data['station_profile'] = $this->stations->all_of_user();
$callarray = $query->result();
$footerData = [];
$footerData['scripts'] = [
'assets/js/sections/calltester.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/calltester.js"))
];
$result = array();
$data['page_title'] = __("Call Tester");
$this->load->view('interface_assets/header', $data);
$this->load->view('calltester/index');
$this->load->view('interface_assets/footer', $footerData);
}
$i = 0;
function doDxccCheck() {
set_time_limit(3600);
$de = $this->input->post('de', true);
$compare = $this->input->post('compare', true);
if ($compare == "true") {
$result = $this->doClassCheck($de);
$result2 = $this->doDxccCheckModel($de);
return $this->compareDxccChecks($result, $result2);
}
$result = $this->doClassCheck($de);
$this->loadView($result);
}
/* Uses DXCC Class. Much faster */
function doClassCheck($de) {
$i = 0;
$result = array();
$callarray = $this->getQsos($de);
// Starting clock time in seconds
$start_time = microtime(true);
$dxccobj = new Dxcc(null);
foreach ($callarray->result() as $call) {
foreach ($callarray as $call) {
$i++;
$dxcc = $this->logbook_model->dxcc_lookup($call->col_call, $call->date);
//$dxcc = $this->logbook_model->dxcc_lookup($call->col_call, $call->date);
$dxcc = $dxccobj->dxcc_lookup($call->col_call, $call->date);
$dxcc['adif'] = (isset($dxcc['adif'])) ? $dxcc['adif'] : 0;
$dxcc['entity'] = (isset($dxcc['entity'])) ? $dxcc['entity'] : 0;
$dxcc['entity'] = (isset($dxcc['entity'])) ? $dxcc['entity'] : 'None';
if ($call->col_dxcc != $dxcc['adif']) {
$result[] = array(
'Callsign' => $call->col_call,
'Expected country' => $call->col_country,
'Expected adif' => $call->col_dxcc,
'Result country' => ucwords(strtolower($dxcc['entity']), "- (/"),
'Result adif' => $dxcc['adif'],
'callsign' => $call->col_call,
'qso_date' => $call->date,
'station_profile' => $call->station_profile_name,
'existing_dxcc' => $call->col_country,
'existing_adif' => $call->col_dxcc,
'result_country' => ucwords(strtolower($dxcc['entity']), "- (/"),
'result_adif' => $dxcc['adif'],
'id' => $call->col_primary_key,
);
}
}
@@ -53,16 +92,117 @@ class Calltester extends CI_Controller {
// Calculate script execution time
$execution_time = ($end_time - $start_time);
echo " Execution time of script = ".$execution_time." sec <br/>";
echo $i . " calls tested. <br/>";
$count = 0;
if ($result) {
$this->array_to_table($result);
}
$data['execution_time'] = $execution_time;
$data['calls_tested'] = $i;
$data['result'] = $result;
return $data;
}
/* Uses Logbook_model and the normal dxcc lookup, which is slow */
function doDxccCheckModel($de) {
$this->load->model('logbook_model');
$i = 0;
$result = array();
$callarray = $this->getQsos($de);
// Starting clock time in seconds
$start_time = microtime(true);
foreach ($callarray->result() as $call) {
$i++;
$dxcc = $this->logbook_model->dxcc_lookup($call->col_call, $call->date);
$dxcc['adif'] = (isset($dxcc['adif'])) ? $dxcc['adif'] : 0;
$dxcc['entity'] = (isset($dxcc['entity'])) ? $dxcc['entity'] : 0;
if ($call->col_dxcc != $dxcc['adif']) {
$result[] = array(
'callsign' => $call->col_call,
'qso_date' => $call->date,
'station_profile' => $call->station_profile_name,
'existing_dxcc' => $call->col_country,
'existing_adif' => $call->col_dxcc,
'result_country' => ucwords(strtolower($dxcc['entity']), "- (/"),
'result_adif' => $dxcc['adif'],
'id' => $call->col_primary_key,
);
}
}
// End clock time in seconds
$end_time = microtime(true);
// Calculate script execution time
$execution_time = ($end_time - $start_time);
$data['execution_time'] = $execution_time;
$data['calls_tested'] = $i;
$data['result'] = $result;
return $data;
}
function loadView($data) {
$this->load->view('calltester/result', $data);
}
function compareDxccChecks($result, $result2) {
// Convert arrays to comparable format using callsign, qso_date, and id as unique keys
$classCheckItems = [];
$modelCheckItems = [];
// Create associative arrays for easier comparison
foreach ($result['result'] as $item) {
$key = $item['callsign'] . '|' . $item['qso_date'] . '|' . $item['id'];
$classCheckItems[$key] = $item;
}
foreach ($result2['result'] as $item) {
$key = $item['callsign'] . '|' . $item['qso_date'] . '|' . $item['id'];
$modelCheckItems[$key] = $item;
}
// Find items that are in class check but not in model check
$onlyInClass = array_diff_key($classCheckItems, $modelCheckItems);
// Find items that are in model check but not in class check
$onlyInModel = array_diff_key($modelCheckItems, $classCheckItems);
// Prepare comparison data
$comparisonData = [];
$comparisonData['class_execution_time'] = $result['execution_time'];
$comparisonData['model_execution_time'] = $result2['execution_time'];
$comparisonData['class_calls_tested'] = $result['calls_tested'];
$comparisonData['model_calls_tested'] = $result2['calls_tested'];
$comparisonData['class_total_issues'] = count($result['result']);
$comparisonData['model_total_issues'] = count($result2['result']);
$comparisonData['only_in_class'] = $onlyInClass;
$comparisonData['only_in_model'] = $onlyInModel;
$comparisonData['common_issues'] = array_intersect_key($classCheckItems, $modelCheckItems);
$this->load->view('calltester/comparison_result', $comparisonData);
}
function getQsos($station_id) {
$sql = 'select distinct col_country, col_call, col_dxcc, date(col_time_on) date, station_profile.station_profile_name, col_primary_key
from ' . $this->config->item('table_name') . '
join station_profile on ' . $this->config->item('table_name') . '.station_id = station_profile.station_id
where station_profile.user_id = ?';
$params[] = array($this->session->userdata('user_id'));
if ($station_id && is_numeric($station_id)) {
$sql .= ' and ' . $this->config->item('table_name') . '.station_id = ?';
$params[] = $station_id;
}
$sql .= ' order by station_profile.station_profile_name asc, date desc';
$query = $this->db->query($sql, $params);
return $query;
}
function array_to_table($table) {
echo '<style>

View File

@@ -0,0 +1,164 @@
<?php
$i = 0;
// Get Date format
if($this->session->userdata('user_date_format')) {
// If Logged in and session exists
$custom_date_format = $this->session->userdata('user_date_format');
} else {
// Get Default date format from /config/wavelog.php
$custom_date_format = $this->config->item('qso_date_format');
}
?>
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="card-title mb-0"><?= __("DXCC Class Results"); ?></h6>
</div>
<div class="card-body">
<p><strong><?= __("Calls tested:"); ?></strong> <?= $class_calls_tested; ?></p>
<p><strong><?= __("Execution time:"); ?></strong> <?= round($class_execution_time, 2); ?>s</p>
<p><strong><?= __("Issues found:"); ?></strong> <?= $class_total_issues; ?></p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="card-title mb-0"><?= __("Logbook Model Results"); ?></h6>
</div>
<div class="card-body">
<p><strong><?= __("Calls tested:"); ?></strong> <?= $model_calls_tested; ?></p>
<p><strong><?= __("Execution time:"); ?></strong> <?= round($model_execution_time, 2); ?>s</p>
<p><strong><?= __("Issues found:"); ?></strong> <?= $model_total_issues; ?></p>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-info">
<strong><?= __("Comparison Summary"); ?></strong><br>
- <?= __("Only found in DXCC Class:"); ?> <?= count($only_in_class); ?><br>
- <?= __("Only found in Logbook Model:"); ?> <?= count($only_in_model); ?><br>
- <?= __("Found in both methods:"); ?> <?= count($common_issues); ?>
</div>
</div>
</div>
<?php if ($only_in_class): ?>
<div class="mb-4">
<h6 class="text-danger"><?= __("Issues found only in DXCC Class (not in Logbook Model):"); ?> <?= count($only_in_class); ?></h6>
<div class="table-responsive" style="max-height:50vh; overflow:auto;">
<table class="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th><?= __("Callsign"); ?></th>
<th><?= __("QSO Date"); ?></th>
<th><?= __("Station Profile"); ?></th>
<th><?= __("Existing DXCC"); ?></th>
<th><?= __("Existing ADIF"); ?></th>
<th><?= __("Result DXCC"); ?></th>
<th><?= __("Result ADIF"); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($only_in_class as $qso): ?>
<tr>
<td><?php echo ++$i; ?></td>
<td><?php echo '<a id="edit_qso" href="javascript:displayQso(' . $qso['id'] . ')">' . htmlspecialchars($qso['callsign']) . '</a>'; ?></td>
<td><?php echo date($custom_date_format, strtotime($qso['qso_date'])); ?></td>
<td><?php echo $qso['station_profile']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['existing_dxcc']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['existing_adif']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['result_country']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['result_adif']; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php if ($only_in_model): ?>
<div class="mb-4">
<h6 class="text-warning"><?= __("Issues found only in Logbook Model (not in DXCC Class):"); ?> <?= count($only_in_model); ?></h6>
<div class="table-responsive" style="max-height:50vh; overflow:auto;">
<table class="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th><?= __("Callsign"); ?></th>
<th><?= __("QSO Date"); ?></th>
<th><?= __("Station Profile"); ?></th>
<th><?= __("Existing DXCC"); ?></th>
<th><?= __("Existing ADIF"); ?></th>
<th><?= __("Result DXCC"); ?></th>
<th><?= __("Result ADIF"); ?></th>
</tr>
</thead>
<tbody>
<?php $i = 0; foreach ($only_in_model as $qso): ?>
<tr>
<td><?php echo ++$i; ?></td>
<td><?php echo '<a id="edit_qso" href="javascript:displayQso(' . $qso['id'] . ')">' . htmlspecialchars($qso['callsign']) . '</a>'; ?></td>
<td><?php echo date($custom_date_format, strtotime($qso['qso_date'])); ?></td>
<td><?php echo $qso['station_profile']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['existing_dxcc']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['existing_adif']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['result_country']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['result_adif']; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php if ($common_issues): ?>
<div class="mb-4">
<h6 class="text-success"><?= __("Issues found in both methods:"); ?> <?= count($common_issues); ?></h6>
<div class="table-responsive" style="max-height:50vh; overflow:auto;">
<table class="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th><?= __("Callsign"); ?></th>
<th><?= __("QSO Date"); ?></th>
<th><?= __("Station Profile"); ?></th>
<th><?= __("Existing DXCC"); ?></th>
<th><?= __("Existing ADIF"); ?></th>
<th><?= __("Result DXCC"); ?></th>
<th><?= __("Result ADIF"); ?></th>
</tr>
</thead>
<tbody>
<?php $i = 0; foreach ($common_issues as $qso): ?>
<tr>
<td><?php echo ++$i; ?></td>
<td><?php echo '<a id="edit_qso" href="javascript:displayQso(' . $qso['id'] . ')">' . htmlspecialchars($qso['callsign']) . '</a>'; ?></td>
<td><?php echo date($custom_date_format, strtotime($qso['qso_date'])); ?></td>
<td><?php echo $qso['station_profile']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['existing_dxcc']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['existing_adif']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['result_country']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['result_adif']; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php if (!$only_in_class && !$only_in_model && !$common_issues): ?>
<div class="alert alert-success">
<?= __("No DXCC issues found in either method. All calls have correct DXCC information."); ?>
</div>
<?php endif; ?>

View File

@@ -0,0 +1,23 @@
<div class="container">
<br />
<h5><?= __("Callsign DXCC identification"); ?></h5>
<div class="d-flex align-items-center mb-3">
<label class="me-2" for="de"><?= __("Station Location"); ?></label>
<select class="form-select form-select-sm w-auto me-2" id="de" name="de">
<option value="all">All</option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>">
<?= __("Callsign: ") . " " ?>
<?php echo str_replace("0", "&Oslash;", strtoupper($station->station_callsign)); ?> (<?php echo $station->station_profile_name; ?>)
</option>
<?php } ?>
</select>
<button id="startDxccCheck" class="btn btn-primary btn-sm"><?= __("Start DXCC Check"); ?></button>
<div class="form-check me-2 mx-2">
<input type="checkbox" class="form-check-input" id="compareDxccClass" name="compareDxccClass">
<label class="form-check-label" for="compareDxccClass"><?= __("Compare DXCC class and logbook model"); ?></label>
</div>
</div>
<div class='result'>
</div>
</div>

View File

@@ -0,0 +1,48 @@
<?php
$i = 0;
echo "<p>" . __("Callsigns tested: ") . $calls_tested . "</p>";
echo "<p>" . __("Execution time: ") . round($execution_time, 2) . "s</p>";
echo "<p>" . __("Number of potential QSOs with wrong DXCC: ") . count($result) . "</p>";
// Get Date format
if($this->session->userdata('user_date_format')) {
// If Logged in and session exists
$custom_date_format = $this->session->userdata('user_date_format');
} else {
// Get Default date format from /config/wavelog.php
$custom_date_format = $this->config->item('qso_date_format');
}
if ($result) { ?>
<div class="table-responsive" style="max-height:70vh; overflow:auto;">
<table class="table table-sm table-striped table-bordered table-condensed mb-0">
<thead>
<tr>
<th>#</th>
<th><?= __("Callsign"); ?></th>
<th><?= __("QSO Date"); ?></th>
<th><?= __("Station Profile"); ?></th>
<th><?= __("Existing DXCC"); ?></th>
<th><?= __("Existing ADIF"); ?></th>
<th><?= __("Result DXCC"); ?></th>
<th><?= __("Result ADIF"); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($result as $qso): ?>
<tr>
<td><?php echo ++$i; ?></td>
<td><?php echo '<a id="edit_qso" href="javascript:displayQso(' . $qso['id'] . ')">' . htmlspecialchars($qso['callsign']) . '</a>'; ?></td>
<td><?php echo date($custom_date_format, strtotime($qso['qso_date'])); ?></td>
<td><?php echo $qso['station_profile']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['existing_dxcc']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['existing_adif']; ?></td>
<td><?php echo htmlspecialchars(ucwords(strtolower($qso['result_country']), "- (/"), ENT_QUOTES, 'UTF-8'); ?></td>
<td><?php echo $qso['result_adif']; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php } ?>

View File

@@ -0,0 +1,18 @@
$('#startDxccCheck').on('click', function() {
let de = $('#de').val();
let compare = $('#compareDxccClass').prop('checked');
$('.result').html('<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div> <?= __("Processing...") ?>');
$.ajax({
url: site_url + '/calltester/doDxccCheck',
type: "POST",
data: {de: de,
compare: compare
},
success: function(response) {
$('.result').html(response);
},
error: function(xhr, status, error) {
$('.result').html('<div class="alert alert-danger" role="alert"><?= __("An error occurred while processing the request.") ?></div>');
}
});
});

View File

@@ -17,8 +17,23 @@ class Dxcc {
$CI =& get_instance();
if (array_key_exists($call, $this->dxccexceptions)) {
return $this->dxccexceptions[$call];
} else {
$exceptions = $this->dxccexceptions[$call];
// Loop through all exceptions for this call
foreach ($exceptions as $exception) {
$startDate = !empty($exception['start']) ? $exception['start'] : null;
$endDate = !empty($exception['end']) ? $exception['end'] : null;
if ($startDate == null && $endDate == null)
return $exception;
if ($date <= $endDate && $date >= $startDate)
return $exception;
if ($endDate == null && $date >= $startDate)
return $exception;
if ($date <= $endDate && $startDate == null)
return $exception;
}
}
if (preg_match('/(^KG4)[A-Z09]{3}/', $call)) { // KG4/ and KG4 5 char calls are Guantanamo Bay. If 4 or 6 char, it is USA
$call = "K";
@@ -76,10 +91,25 @@ class Dxcc {
$result = '';
if (array_key_exists(substr($call, 0, $i), $this->dxcc)) {
return $this->dxcc[substr($call, 0, $i)];
$arraykey = substr($call, 0, $i);
$dxccEntries = $this->dxcc[substr($call, 0, $i)];
// Loop through all entries for this call prefix
foreach ($dxccEntries as $dxccEntry) {
$startDate = !empty($dxccEntry['start']) ? $dxccEntry['start'] : null;
$endDate = !empty($dxccEntry['end']) ? $dxccEntry['end'] : null;
if ($startDate == null && $endDate == null)
return $dxccEntry;
if ($date <= $endDate && $date >= $startDate)
return $dxccEntry;
if ($endDate == null && $date >= $startDate)
return $dxccEntry;
if ($date <= $endDate && $startDate == null)
return $dxccEntry;
}
}
}
}
return array("Not Found", "Not Found");
}
@@ -223,45 +253,73 @@ class Dxcc {
/*
* Read cty.dat from AD1C
*/
function read_data($date) {
function read_data($date = null) {
$CI =& get_instance();
$dxcc_exceptions = $CI->db->select('`entity`, `adif`, `cqz`, `start`, `end`, `call`, `cont`, `long`, `lat`')
->where('(start <= ', $date)
->or_where('start is null)', NULL, false)
->where('(end >= ', $date)
->or_where('end is null)', NULL, false)
->get('dxcc_exceptions');
if ($date == null) {
$dxcc_exceptions = $CI->db->select('entity, adif, cqz, start, end, call, cont, long, lat')
->order_by('start desc, end desc')
->get('dxcc_exceptions');
} else {
$dxcc_exceptions = $CI->db->select('entity, adif, cqz, start, end, call, cont, long, lat')
->group_start()
->where('start <=', $date)
->or_where('start IS NULL')
->group_end()
->group_start()
->where('end >=', $date)
->or_where('end IS NULL')
->group_end()
->order_by('start desc, end desc')
->get('dxcc_exceptions');
}
if ($dxcc_exceptions->num_rows() > 0){
foreach ($dxcc_exceptions->result() as $dxcce) {
$this->dxccexceptions[$dxcce->call]['adif'] = $dxcce->adif;
$this->dxccexceptions[$dxcce->call]['cont'] = $dxcce->cont;
$this->dxccexceptions[$dxcce->call]['entity'] = $dxcce->entity;
$this->dxccexceptions[$dxcce->call]['cqz'] = $dxcce->cqz;
$this->dxccexceptions[$dxcce->call]['start'] = $dxcce->start;
$this->dxccexceptions[$dxcce->call]['end'] = $dxcce->end;
$this->dxccexceptions[$dxcce->call]['long'] = $dxcce->long;
$this->dxccexceptions[$dxcce->call]['lat'] = $dxcce->lat;
$this->dxccexceptions[$dxcce->call][] = [
'adif' => $dxcce->adif,
'cont' => $dxcce->cont,
'entity' => $dxcce->entity,
'cqz' => $dxcce->cqz,
'start' => $dxcce->start,
'end' => $dxcce->end,
'long' => $dxcce->long,
'lat' => $dxcce->lat
];
}
}
$dxcc_result = $CI->db->select('*')
->where('(start <= ', $date)
->or_where("start is null)", NULL, false)
->where('(end >= ', $date)
->or_where("end is null)", NULL, false)
->get('dxcc_prefixes');
if ($date == null) {
$dxcc_result = $CI->db->select('*')
->order_by('start desc, end desc')
->get('dxcc_prefixes');
} else {
$dxcc_result = $CI->db->select('*')
->group_start()
->where('start <=', $date)
->or_where('start IS NULL')
->group_end()
->group_start()
->where('end >=', $date)
->or_where('end IS NULL')
->group_end()
->order_by('start desc, end desc')
->get('dxcc_prefixes');
}
if ($dxcc_result->num_rows() > 0){
foreach ($dxcc_result->result() as $dx) {
$this->dxcc[$dx->call]['adif'] = $dx->adif;
$this->dxcc[$dx->call]['cont'] = $dx->cont;
$this->dxcc[$dx->call]['entity'] = $dx->entity;
$this->dxcc[$dx->call]['cqz'] = $dx->cqz;
$this->dxcc[$dx->call]['start'] = $dx->start;
$this->dxcc[$dx->call]['end'] = $dx->end;
$this->dxcc[$dx->call]['long'] = $dx->long;
$this->dxcc[$dx->call]['lat'] = $dx->lat;
$this->dxcc[$dx->call][] = [
'adif' => $dx->adif,
'cont' => $dx->cont,
'entity' => $dx->entity,
'cqz' => $dx->cqz,
'start' => $dx->start,
'end' => $dx->end,
'long' => $dx->long,
'lat' => $dx->lat
];
}
}