2025-11-14 16:39:56 +00:00
|
|
|
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
|
|
|
|
|
|
|
|
|
|
class Monthlyreport_model extends CI_Model {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate comprehensive monthly report data
|
|
|
|
|
*
|
|
|
|
|
* @param array $logbooks_locations_array Station location IDs
|
|
|
|
|
* @param string $year Year (YYYY)
|
|
|
|
|
* @param string $month Month (1-12)
|
|
|
|
|
* @return array Report data
|
|
|
|
|
*/
|
|
|
|
|
public function generate_report($logbooks_locations_array, $year, $month) {
|
|
|
|
|
// Calculate date range for the month
|
|
|
|
|
$start_date = sprintf("%04d-%02d-01 00:00:00", $year, $month);
|
|
|
|
|
$days_in_month = date('t', strtotime($start_date));
|
|
|
|
|
$end_date = sprintf("%04d-%02d-%02d 23:59:59", $year, $month, $days_in_month);
|
|
|
|
|
|
|
|
|
|
// Calculate previous month end date for "new" calculations
|
|
|
|
|
$prev_month_end = date('Y-m-d 23:59:59', strtotime($start_date . ' -1 day'));
|
|
|
|
|
|
2025-11-14 17:13:11 +00:00
|
|
|
// PERFORMANCE OPTIMIZATION: Pre-load all historical data for "new" checks
|
|
|
|
|
// This reduces hundreds of queries to just a few
|
|
|
|
|
$this->build_historical_caches($logbooks_locations_array, $start_date);
|
|
|
|
|
|
2025-11-14 16:39:56 +00:00
|
|
|
$report = array(
|
|
|
|
|
'total_qsos' => $this->get_total_qsos($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'unique_callsigns' => $this->get_unique_callsigns($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'callsign_list' => $this->get_callsign_list($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'new_dxcc' => $this->get_new_dxcc($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
|
|
|
|
|
'new_dxcc_by_band' => $this->get_new_dxcc_by_band($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
|
|
|
|
|
'new_dxcc_satellite' => $this->get_new_dxcc_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'SAT'),
|
|
|
|
|
'new_dxcc_eme' => $this->get_new_dxcc_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'EME'),
|
|
|
|
|
'new_grids' => $this->get_new_gridsquares($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
|
2025-11-14 17:49:25 +00:00
|
|
|
'new_grids_by_band' => $this->get_new_gridsquares_by_band($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
|
2025-11-14 16:39:56 +00:00
|
|
|
'new_grids_satellite' => $this->get_new_gridsquares_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'SAT'),
|
|
|
|
|
'new_grids_eme' => $this->get_new_gridsquares_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'EME'),
|
|
|
|
|
'new_grids_hf' => $this->get_new_gridsquares_hf($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
|
|
|
|
|
'countries_worked' => $this->get_countries_worked($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'modes' => $this->get_modes_breakdown($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'bands' => $this->get_bands_breakdown($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'continents' => $this->get_continents_breakdown($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'satellite_qsos' => $this->get_satellite_qsos($logbooks_locations_array, $start_date, $end_date),
|
2025-11-14 17:33:08 +00:00
|
|
|
'satellite_breakdown' => $this->get_satellite_breakdown($logbooks_locations_array, $start_date, $end_date),
|
2025-11-14 16:39:56 +00:00
|
|
|
'eme_qsos' => $this->get_eme_qsos($logbooks_locations_array, $start_date, $end_date),
|
|
|
|
|
'top_band' => '',
|
|
|
|
|
'top_mode' => ''
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Calculate top band and mode
|
|
|
|
|
if (!empty($report['bands'])) {
|
|
|
|
|
arsort($report['bands']);
|
|
|
|
|
$report['top_band'] = array_key_first($report['bands']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!empty($report['modes'])) {
|
|
|
|
|
arsort($report['modes']);
|
|
|
|
|
$report['top_mode'] = array_key_first($report['modes']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $report;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 17:13:11 +00:00
|
|
|
// Cache variables for historical data lookups
|
|
|
|
|
private $historical_grids = array();
|
|
|
|
|
private $historical_grids_by_prop = array();
|
|
|
|
|
private $historical_grids_hf = array();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Build historical caches for fast "new" checks
|
|
|
|
|
* This prevents hundreds of individual queries
|
|
|
|
|
*/
|
|
|
|
|
private function build_historical_caches($locations, $start_date) {
|
|
|
|
|
// Get ALL historical gridsquares (before this month)
|
|
|
|
|
$this->db->select('COL_GRIDSQUARE, COL_VUCC_GRIDS, COL_PROP_MODE');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON <', $start_date);
|
|
|
|
|
$this->db->where("(COL_GRIDSQUARE IS NOT NULL AND COL_GRIDSQUARE != '') OR (COL_VUCC_GRIDS IS NOT NULL AND COL_VUCC_GRIDS != '')");
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$prop_mode = !empty($row->COL_PROP_MODE) ? $row->COL_PROP_MODE : '';
|
|
|
|
|
|
|
|
|
|
// Process main gridsquare
|
|
|
|
|
if (!empty($row->COL_GRIDSQUARE) && strlen($row->COL_GRIDSQUARE) >= 4) {
|
|
|
|
|
$grid_4char = strtoupper(substr($row->COL_GRIDSQUARE, 0, 4));
|
|
|
|
|
$this->historical_grids[$grid_4char] = true;
|
|
|
|
|
|
|
|
|
|
// Track by prop mode
|
|
|
|
|
if ($prop_mode === 'SAT' || $prop_mode === 'EME') {
|
|
|
|
|
$this->historical_grids_by_prop[$prop_mode][$grid_4char] = true;
|
|
|
|
|
} else {
|
|
|
|
|
$this->historical_grids_hf[$grid_4char] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process VUCC grids
|
|
|
|
|
if (!empty($row->COL_VUCC_GRIDS)) {
|
|
|
|
|
$vucc_grids = explode(',', $row->COL_VUCC_GRIDS);
|
|
|
|
|
foreach ($vucc_grids as $grid) {
|
|
|
|
|
$grid = trim($grid);
|
|
|
|
|
if (strlen($grid) >= 4) {
|
|
|
|
|
$grid_4char = strtoupper(substr($grid, 0, 4));
|
|
|
|
|
$this->historical_grids[$grid_4char] = true;
|
|
|
|
|
|
|
|
|
|
// Track by prop mode
|
|
|
|
|
if ($prop_mode === 'SAT' || $prop_mode === 'EME') {
|
|
|
|
|
$this->historical_grids_by_prop[$prop_mode][$grid_4char] = true;
|
|
|
|
|
} else {
|
|
|
|
|
$this->historical_grids_hf[$grid_4char] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Sort bands in amateur radio order (160m, 80m, 40m, 20m, etc.)
|
|
|
|
|
*/
|
|
|
|
|
private function sort_bands_array($bands_array) {
|
|
|
|
|
// Define standard band order
|
|
|
|
|
$band_order = array(
|
|
|
|
|
'2190m' => 1, '630m' => 2, '560m' => 3, '160m' => 4, '80m' => 5, '60m' => 6,
|
|
|
|
|
'40m' => 7, '30m' => 8, '20m' => 9, '17m' => 10, '15m' => 11, '12m' => 12,
|
|
|
|
|
'10m' => 13, '8m' => 14, '6m' => 15, '5m' => 16, '4m' => 17, '2m' => 18,
|
|
|
|
|
'1.25m' => 19, '70cm' => 20, '33cm' => 21, '23cm' => 22, '13cm' => 23,
|
|
|
|
|
'9cm' => 24, '6cm' => 25, '3cm' => 26, '1.25cm' => 27, '6mm' => 28, '4mm' => 29,
|
|
|
|
|
'2.5mm' => 30, '2mm' => 31, '1mm' => 32
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
uksort($bands_array, function($a, $b) use ($band_order) {
|
|
|
|
|
$pos_a = isset($band_order[$a]) ? $band_order[$a] : 999;
|
|
|
|
|
$pos_b = isset($band_order[$b]) ? $band_order[$b] : 999;
|
|
|
|
|
|
|
|
|
|
if ($pos_a == $pos_b) {
|
|
|
|
|
return strcmp($a, $b);
|
|
|
|
|
}
|
|
|
|
|
return $pos_a - $pos_b;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return $bands_array;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get total QSO count for the period
|
|
|
|
|
*/
|
|
|
|
|
private function get_total_qsos($locations, $start_date, $end_date) {
|
|
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
$row = $query->row();
|
|
|
|
|
return $row ? (int)$row->count : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get count of unique callsigns worked
|
|
|
|
|
*/
|
|
|
|
|
private function get_unique_callsigns($locations, $start_date, $end_date) {
|
|
|
|
|
$this->db->select('COUNT(DISTINCT COL_CALL) as count', FALSE);
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
$row = $query->row();
|
|
|
|
|
return $row ? (int)$row->count : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get list of unique callsigns worked with QSO counts
|
|
|
|
|
*/
|
|
|
|
|
private function get_callsign_list($locations, $start_date, $end_date) {
|
|
|
|
|
$this->db->select('COL_CALL, COUNT(*) as qso_count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->group_by('COL_CALL');
|
|
|
|
|
$this->db->order_by('qso_count', 'DESC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
$callsigns = array();
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$callsigns[] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'qso_count' => (int)$row->qso_count
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $callsigns;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get new DXCC entities worked this month (first time ever)
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_dxcc($locations, $start_date, $end_date, $prev_month_end) {
|
|
|
|
|
$new_dxcc = array();
|
|
|
|
|
|
|
|
|
|
// Get all unique DXCC entities worked this month (group by DXCC)
|
2025-11-14 16:41:55 +00:00
|
|
|
$this->db->select('COL_DXCC, MAX(COL_COUNTRY) as COL_COUNTRY', FALSE);
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_DXCC IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_DXCC !=', '');
|
|
|
|
|
$this->db->group_by('COL_DXCC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
// Check if this DXCC was worked before this month
|
|
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_DXCC', $row->COL_DXCC);
|
|
|
|
|
$this->db->where('COL_TIME_ON <', $start_date);
|
|
|
|
|
|
|
|
|
|
$prev_query = $this->db->get();
|
|
|
|
|
$prev_row = $prev_query->row();
|
|
|
|
|
|
|
|
|
|
// If not worked before, it's new this month
|
|
|
|
|
if ($prev_row->count == 0) {
|
|
|
|
|
$new_dxcc[] = array(
|
|
|
|
|
'dxcc_id' => $row->COL_DXCC,
|
|
|
|
|
'name' => $row->COL_COUNTRY
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $new_dxcc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get new DXCC entities by band
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_dxcc_by_band($locations, $start_date, $end_date, $prev_month_end) {
|
|
|
|
|
$new_dxcc_by_band = array();
|
|
|
|
|
|
2025-11-14 17:40:27 +00:00
|
|
|
// Get all bands worked this month, plus propagation mode to separate SAT/EME
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->distinct();
|
2025-11-14 17:40:27 +00:00
|
|
|
$this->db->select('COL_BAND, COL_PROP_MODE');
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_BAND IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_BAND !=', '');
|
|
|
|
|
$this->db->where('COL_DXCC IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_DXCC !=', '');
|
|
|
|
|
$bands_query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($bands_query->result() as $band_row) {
|
|
|
|
|
$band = $band_row->COL_BAND;
|
2025-11-14 17:40:27 +00:00
|
|
|
$prop_mode = $band_row->COL_PROP_MODE;
|
2025-11-14 16:39:56 +00:00
|
|
|
|
2025-11-14 17:40:27 +00:00
|
|
|
// Group SAT and EME separately, not by frequency band
|
|
|
|
|
if ($prop_mode == 'SAT') {
|
|
|
|
|
$band_key = 'Satellite';
|
|
|
|
|
} elseif ($prop_mode == 'EME') {
|
|
|
|
|
$band_key = 'EME';
|
|
|
|
|
} else {
|
|
|
|
|
$band_key = $band;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isset($new_dxcc_by_band[$band_key])) {
|
|
|
|
|
$new_dxcc_by_band[$band_key] = array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all unique DXCC entities worked on this band/prop this month (group by DXCC, get earliest callsign)
|
2025-11-14 17:11:18 +00:00
|
|
|
$this->db->select('COL_DXCC, MAX(COL_COUNTRY) as COL_COUNTRY, MIN(COL_TIME_ON) as FIRST_QSO', FALSE);
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_BAND', $band);
|
|
|
|
|
$this->db->where('COL_DXCC IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_DXCC !=', '');
|
2025-11-14 17:40:27 +00:00
|
|
|
|
|
|
|
|
// Filter by propagation mode
|
|
|
|
|
if ($prop_mode) {
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
} else {
|
|
|
|
|
// For terrestrial, exclude SAT and EME
|
|
|
|
|
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->group_by('COL_DXCC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
2025-11-14 17:40:27 +00:00
|
|
|
// Check if this DXCC was worked before this month on this band/prop
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_DXCC', $row->COL_DXCC);
|
|
|
|
|
$this->db->where('COL_BAND', $band);
|
2025-11-14 17:40:27 +00:00
|
|
|
if ($prop_mode) {
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
|
|
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->where('COL_TIME_ON <', $start_date);
|
|
|
|
|
|
|
|
|
|
$prev_query = $this->db->get();
|
|
|
|
|
$prev_row = $prev_query->row();
|
|
|
|
|
|
2025-11-14 17:40:27 +00:00
|
|
|
// If not worked before on this band/prop, it's new
|
2025-11-14 16:39:56 +00:00
|
|
|
if ($prev_row->count == 0) {
|
2025-11-14 17:40:27 +00:00
|
|
|
// Get the callsign and mode from the first QSO with this DXCC on this band/prop
|
2025-11-14 17:33:08 +00:00
|
|
|
$this->db->select('COL_CALL, COL_MODE, COL_SUBMODE');
|
2025-11-14 17:11:18 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_DXCC', $row->COL_DXCC);
|
|
|
|
|
$this->db->where('COL_BAND', $band);
|
2025-11-14 17:40:27 +00:00
|
|
|
if ($prop_mode) {
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
|
|
|
|
|
}
|
2025-11-14 17:11:18 +00:00
|
|
|
$this->db->where('COL_TIME_ON', $row->FIRST_QSO);
|
|
|
|
|
$this->db->limit(1);
|
|
|
|
|
|
|
|
|
|
$call_query = $this->db->get();
|
|
|
|
|
$call_row = $call_query->row();
|
|
|
|
|
|
2025-11-14 17:33:08 +00:00
|
|
|
$mode = '';
|
|
|
|
|
if ($call_row) {
|
|
|
|
|
$mode = !empty($call_row->COL_SUBMODE) ? $call_row->COL_SUBMODE : $call_row->COL_MODE;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 17:40:27 +00:00
|
|
|
// Avoid duplicates in the same band_key
|
|
|
|
|
$already_added = false;
|
|
|
|
|
foreach ($new_dxcc_by_band[$band_key] as $existing) {
|
|
|
|
|
if ($existing['dxcc_id'] == $row->COL_DXCC) {
|
|
|
|
|
$already_added = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$already_added) {
|
|
|
|
|
$new_dxcc_by_band[$band_key][] = array(
|
|
|
|
|
'dxcc_id' => $row->COL_DXCC,
|
|
|
|
|
'name' => $row->COL_COUNTRY,
|
|
|
|
|
'callsign' => $call_row ? $call_row->COL_CALL : '',
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove bands with no new DXCC
|
|
|
|
|
$new_dxcc_by_band = array_filter($new_dxcc_by_band, function($arr) {
|
|
|
|
|
return !empty($arr);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Sort bands in proper amateur radio order
|
|
|
|
|
$new_dxcc_by_band = $this->sort_bands_array($new_dxcc_by_band);
|
|
|
|
|
|
|
|
|
|
return $new_dxcc_by_band;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get new DXCC entities by propagation mode (SAT or EME)
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_dxcc_by_prop($locations, $start_date, $end_date, $prev_month_end, $prop_mode) {
|
|
|
|
|
$new_dxcc = array();
|
|
|
|
|
|
2025-11-14 17:40:27 +00:00
|
|
|
// Get all unique DXCC entities worked this month via this prop mode (group by DXCC, get earliest time)
|
|
|
|
|
$this->db->select('COL_DXCC, MAX(COL_COUNTRY) as COL_COUNTRY, MIN(COL_TIME_ON) as FIRST_QSO', FALSE);
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
$this->db->where('COL_DXCC IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_DXCC !=', '');
|
|
|
|
|
$this->db->group_by('COL_DXCC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
// Check if this DXCC was worked before this month via this prop mode
|
|
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_DXCC', $row->COL_DXCC);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
$this->db->where('COL_TIME_ON <', $start_date);
|
|
|
|
|
|
|
|
|
|
$prev_query = $this->db->get();
|
|
|
|
|
$prev_row = $prev_query->row();
|
|
|
|
|
|
|
|
|
|
// If not worked before via this prop mode, it's new
|
|
|
|
|
if ($prev_row->count == 0) {
|
2025-11-14 17:40:27 +00:00
|
|
|
// Get the callsign and mode from the first QSO
|
|
|
|
|
$this->db->select('COL_CALL, COL_MODE, COL_SUBMODE');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_DXCC', $row->COL_DXCC);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
$this->db->where('COL_TIME_ON', $row->FIRST_QSO);
|
|
|
|
|
$this->db->limit(1);
|
|
|
|
|
|
|
|
|
|
$call_query = $this->db->get();
|
|
|
|
|
$call_row = $call_query->row();
|
|
|
|
|
|
|
|
|
|
$mode = '';
|
|
|
|
|
if ($call_row) {
|
|
|
|
|
$mode = !empty($call_row->COL_SUBMODE) ? $call_row->COL_SUBMODE : $call_row->COL_MODE;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:39:56 +00:00
|
|
|
$new_dxcc[] = array(
|
|
|
|
|
'dxcc_id' => $row->COL_DXCC,
|
2025-11-14 17:40:27 +00:00
|
|
|
'name' => $row->COL_COUNTRY,
|
|
|
|
|
'callsign' => $call_row ? $call_row->COL_CALL : '',
|
|
|
|
|
'mode' => $mode
|
2025-11-14 16:39:56 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $new_dxcc;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 17:49:25 +00:00
|
|
|
/**
|
|
|
|
|
* Get new gridsquares by band
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_gridsquares_by_band($locations, $start_date, $end_date, $prev_month_end) {
|
|
|
|
|
$new_grids_by_band = array();
|
|
|
|
|
|
|
|
|
|
// Get all bands worked this month, plus propagation mode to separate SAT/EME
|
|
|
|
|
$this->db->distinct();
|
|
|
|
|
$this->db->select('COL_BAND, COL_PROP_MODE');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_BAND IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_BAND !=', '');
|
|
|
|
|
$bands_query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($bands_query->result() as $band_row) {
|
|
|
|
|
$band = $band_row->COL_BAND;
|
|
|
|
|
$prop_mode = $band_row->COL_PROP_MODE;
|
|
|
|
|
|
|
|
|
|
// Group SAT and EME separately, not by frequency band
|
|
|
|
|
if ($prop_mode == 'SAT') {
|
|
|
|
|
$band_key = 'Satellite';
|
|
|
|
|
} elseif ($prop_mode == 'EME') {
|
|
|
|
|
$band_key = 'EME';
|
|
|
|
|
} else {
|
|
|
|
|
$band_key = $band;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isset($new_grids_by_band[$band_key])) {
|
|
|
|
|
$new_grids_by_band[$band_key] = array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all gridsquares worked on this band/prop this month
|
|
|
|
|
$this->db->select('COL_CALL, COL_GRIDSQUARE, COL_VUCC_GRIDS, COL_TIME_ON, COL_MODE, COL_SUBMODE, COL_SAT_NAME');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_BAND', $band);
|
|
|
|
|
|
|
|
|
|
// Filter by propagation mode
|
|
|
|
|
if ($prop_mode) {
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
} else {
|
|
|
|
|
// For terrestrial, exclude SAT and EME
|
|
|
|
|
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->db->order_by('COL_TIME_ON', 'ASC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
$grid_data = array();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$mode = !empty($row->COL_SUBMODE) ? $row->COL_SUBMODE : $row->COL_MODE;
|
|
|
|
|
|
|
|
|
|
// Process main gridsquare (first 4 characters only)
|
|
|
|
|
if (!empty($row->COL_GRIDSQUARE) && strlen($row->COL_GRIDSQUARE) >= 4) {
|
|
|
|
|
$grid_4char = strtoupper(substr($row->COL_GRIDSQUARE, 0, 4));
|
|
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'satellite' => !empty($row->COL_SAT_NAME) ? $row->COL_SAT_NAME : '',
|
|
|
|
|
'mode' => $mode,
|
|
|
|
|
'time' => $row->COL_TIME_ON
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process VUCC grids (first 4 characters only)
|
|
|
|
|
if (!empty($row->COL_VUCC_GRIDS)) {
|
|
|
|
|
$vucc_grids = explode(',', $row->COL_VUCC_GRIDS);
|
|
|
|
|
foreach ($vucc_grids as $grid) {
|
|
|
|
|
$grid = trim($grid);
|
|
|
|
|
if (strlen($grid) >= 4) {
|
|
|
|
|
$grid_4char = strtoupper(substr($grid, 0, 4));
|
|
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'satellite' => !empty($row->COL_SAT_NAME) ? $row->COL_SAT_NAME : '',
|
|
|
|
|
'mode' => $mode,
|
|
|
|
|
'time' => $row->COL_TIME_ON
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each grid to see if it was worked before on this band/prop
|
|
|
|
|
foreach ($grid_data as $grid => $data) {
|
|
|
|
|
// Check if this grid was worked before this month on this band/prop
|
|
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_BAND', $band);
|
|
|
|
|
if ($prop_mode) {
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
|
|
|
|
} else {
|
|
|
|
|
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
|
|
|
|
|
}
|
|
|
|
|
$this->db->where('COL_TIME_ON <', $start_date);
|
|
|
|
|
$this->db->group_start();
|
|
|
|
|
$this->db->where('UPPER(SUBSTR(COL_GRIDSQUARE, 1, 4)) =', $grid);
|
|
|
|
|
$this->db->or_like('COL_VUCC_GRIDS', $grid);
|
|
|
|
|
$this->db->group_end();
|
|
|
|
|
|
|
|
|
|
$prev_query = $this->db->get();
|
|
|
|
|
$prev_row = $prev_query->row();
|
|
|
|
|
|
|
|
|
|
// If not worked before on this band/prop, it's new
|
|
|
|
|
if ($prev_row->count == 0) {
|
|
|
|
|
// Avoid duplicates in the same band_key
|
|
|
|
|
$already_added = false;
|
|
|
|
|
foreach ($new_grids_by_band[$band_key] as $existing) {
|
|
|
|
|
if ($existing['grid'] == $grid) {
|
|
|
|
|
$already_added = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$already_added) {
|
|
|
|
|
$new_grids_by_band[$band_key][] = array(
|
|
|
|
|
'grid' => $grid,
|
|
|
|
|
'callsign' => $data['callsign'],
|
|
|
|
|
'satellite' => $data['satellite'],
|
|
|
|
|
'mode' => $data['mode']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove bands with no new grids
|
|
|
|
|
$new_grids_by_band = array_filter($new_grids_by_band, function($arr) {
|
|
|
|
|
return !empty($arr);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Sort bands in proper amateur radio order
|
|
|
|
|
$new_grids_by_band = $this->sort_bands_array($new_grids_by_band);
|
|
|
|
|
|
|
|
|
|
return $new_grids_by_band;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Get new gridsquares worked this month (first time ever) - All modes
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_gridsquares($locations, $start_date, $end_date, $prev_month_end) {
|
|
|
|
|
$new_grids = array();
|
|
|
|
|
|
|
|
|
|
// Get all gridsquares worked this month (including VUCC grids)
|
2025-11-14 17:33:08 +00:00
|
|
|
$this->db->select('COL_CALL, COL_GRIDSQUARE, COL_VUCC_GRIDS, COL_TIME_ON, COL_MODE, COL_SUBMODE');
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
2025-11-14 17:11:18 +00:00
|
|
|
$this->db->order_by('COL_TIME_ON', 'ASC');
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
2025-11-14 17:33:08 +00:00
|
|
|
$grid_data = array();
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
2025-11-14 17:33:08 +00:00
|
|
|
$mode = !empty($row->COL_SUBMODE) ? $row->COL_SUBMODE : $row->COL_MODE;
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
// Process main gridsquare (first 4 characters only)
|
2025-11-14 16:39:56 +00:00
|
|
|
if (!empty($row->COL_GRIDSQUARE) && strlen($row->COL_GRIDSQUARE) >= 4) {
|
2025-11-14 16:46:58 +00:00
|
|
|
$grid_4char = strtoupper(substr($row->COL_GRIDSQUARE, 0, 4));
|
2025-11-14 17:33:08 +00:00
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
2025-11-14 17:11:18 +00:00
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
// Process VUCC grids (comma-separated, first 4 characters only)
|
2025-11-14 16:39:56 +00:00
|
|
|
if (!empty($row->COL_VUCC_GRIDS)) {
|
|
|
|
|
$vucc_grids = explode(',', $row->COL_VUCC_GRIDS);
|
|
|
|
|
foreach ($vucc_grids as $grid) {
|
|
|
|
|
$grid = trim($grid);
|
|
|
|
|
if (strlen($grid) >= 4) {
|
2025-11-14 16:46:58 +00:00
|
|
|
$grid_4char = strtoupper(substr($grid, 0, 4));
|
2025-11-14 17:33:08 +00:00
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
2025-11-14 17:11:18 +00:00
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each grid to see if it's new
|
2025-11-14 17:33:08 +00:00
|
|
|
foreach ($grid_data as $grid => $data) {
|
2025-11-14 16:39:56 +00:00
|
|
|
if ($this->is_grid_new($locations, $grid, $start_date)) {
|
2025-11-14 17:33:08 +00:00
|
|
|
$new_grids[] = array(
|
|
|
|
|
'grid' => $grid,
|
|
|
|
|
'callsign' => $data['callsign'],
|
|
|
|
|
'mode' => $data['mode']
|
|
|
|
|
);
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort alphabetically
|
|
|
|
|
usort($new_grids, function($a, $b) {
|
|
|
|
|
return strcmp($a['grid'], $b['grid']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return $new_grids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get new gridsquares for specific propagation mode
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_gridsquares_by_prop($locations, $start_date, $end_date, $prev_month_end, $prop_mode) {
|
|
|
|
|
$new_grids = array();
|
|
|
|
|
|
|
|
|
|
// Get all gridsquares worked this month with this prop mode
|
2025-11-14 17:33:08 +00:00
|
|
|
$this->db->select('COL_CALL, COL_GRIDSQUARE, COL_VUCC_GRIDS, COL_TIME_ON, COL_SAT_NAME, COL_MODE, COL_SUBMODE');
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', $prop_mode);
|
2025-11-14 17:11:18 +00:00
|
|
|
$this->db->order_by('COL_TIME_ON', 'ASC');
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
2025-11-14 17:33:08 +00:00
|
|
|
$grid_data = array();
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
2025-11-14 17:33:08 +00:00
|
|
|
$mode = !empty($row->COL_SUBMODE) ? $row->COL_SUBMODE : $row->COL_MODE;
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
// Process main gridsquare (first 4 characters only)
|
2025-11-14 16:39:56 +00:00
|
|
|
if (!empty($row->COL_GRIDSQUARE) && strlen($row->COL_GRIDSQUARE) >= 4) {
|
2025-11-14 16:46:58 +00:00
|
|
|
$grid_4char = strtoupper(substr($row->COL_GRIDSQUARE, 0, 4));
|
2025-11-14 17:33:08 +00:00
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'satellite' => !empty($row->COL_SAT_NAME) ? $row->COL_SAT_NAME : '',
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
2025-11-14 17:11:18 +00:00
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
// Process VUCC grids (first 4 characters only)
|
2025-11-14 16:39:56 +00:00
|
|
|
if (!empty($row->COL_VUCC_GRIDS)) {
|
|
|
|
|
$vucc_grids = explode(',', $row->COL_VUCC_GRIDS);
|
|
|
|
|
foreach ($vucc_grids as $grid) {
|
|
|
|
|
$grid = trim($grid);
|
|
|
|
|
if (strlen($grid) >= 4) {
|
2025-11-14 16:46:58 +00:00
|
|
|
$grid_4char = strtoupper(substr($grid, 0, 4));
|
2025-11-14 17:33:08 +00:00
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'satellite' => !empty($row->COL_SAT_NAME) ? $row->COL_SAT_NAME : '',
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
2025-11-14 17:11:18 +00:00
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each grid to see if it's new for this prop mode
|
2025-11-14 17:33:08 +00:00
|
|
|
foreach ($grid_data as $grid => $data) {
|
2025-11-14 16:39:56 +00:00
|
|
|
if ($this->is_grid_new_for_prop($locations, $grid, $start_date, $prop_mode)) {
|
2025-11-14 17:33:08 +00:00
|
|
|
$new_grids[] = array(
|
|
|
|
|
'grid' => $grid,
|
|
|
|
|
'callsign' => $data['callsign'],
|
|
|
|
|
'satellite' => $data['satellite'],
|
|
|
|
|
'mode' => $data['mode']
|
|
|
|
|
);
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort alphabetically
|
|
|
|
|
usort($new_grids, function($a, $b) {
|
|
|
|
|
return strcmp($a['grid'], $b['grid']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return $new_grids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get new HF gridsquares (non-SAT, non-EME)
|
|
|
|
|
*/
|
|
|
|
|
private function get_new_gridsquares_hf($locations, $start_date, $end_date, $prev_month_end) {
|
|
|
|
|
$new_grids = array();
|
|
|
|
|
|
|
|
|
|
// Get all gridsquares worked this month via HF
|
2025-11-14 17:33:08 +00:00
|
|
|
$this->db->select('COL_CALL, COL_GRIDSQUARE, COL_VUCC_GRIDS, COL_TIME_ON, COL_MODE, COL_SUBMODE');
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where("(COL_PROP_MODE IS NULL OR COL_PROP_MODE = '' OR COL_PROP_MODE NOT IN ('SAT','EME'))");
|
2025-11-14 17:11:18 +00:00
|
|
|
$this->db->order_by('COL_TIME_ON', 'ASC');
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
2025-11-14 17:33:08 +00:00
|
|
|
$grid_data = array();
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
2025-11-14 17:33:08 +00:00
|
|
|
$mode = !empty($row->COL_SUBMODE) ? $row->COL_SUBMODE : $row->COL_MODE;
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
// Process main gridsquare (first 4 characters only)
|
2025-11-14 16:39:56 +00:00
|
|
|
if (!empty($row->COL_GRIDSQUARE) && strlen($row->COL_GRIDSQUARE) >= 4) {
|
2025-11-14 16:46:58 +00:00
|
|
|
$grid_4char = strtoupper(substr($row->COL_GRIDSQUARE, 0, 4));
|
2025-11-14 17:33:08 +00:00
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
2025-11-14 17:11:18 +00:00
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
// Process VUCC grids (first 4 characters only)
|
2025-11-14 16:39:56 +00:00
|
|
|
if (!empty($row->COL_VUCC_GRIDS)) {
|
|
|
|
|
$vucc_grids = explode(',', $row->COL_VUCC_GRIDS);
|
|
|
|
|
foreach ($vucc_grids as $grid) {
|
|
|
|
|
$grid = trim($grid);
|
|
|
|
|
if (strlen($grid) >= 4) {
|
2025-11-14 16:46:58 +00:00
|
|
|
$grid_4char = strtoupper(substr($grid, 0, 4));
|
2025-11-14 17:33:08 +00:00
|
|
|
if (!isset($grid_data[$grid_4char])) {
|
|
|
|
|
$grid_data[$grid_4char] = array(
|
|
|
|
|
'callsign' => $row->COL_CALL,
|
|
|
|
|
'mode' => $mode
|
|
|
|
|
);
|
2025-11-14 17:11:18 +00:00
|
|
|
}
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each grid to see if it's new for HF
|
2025-11-14 17:33:08 +00:00
|
|
|
foreach ($grid_data as $grid => $data) {
|
2025-11-14 16:39:56 +00:00
|
|
|
if ($this->is_grid_new_for_hf($locations, $grid, $start_date)) {
|
2025-11-14 17:33:08 +00:00
|
|
|
$new_grids[] = array(
|
|
|
|
|
'grid' => $grid,
|
|
|
|
|
'callsign' => $data['callsign'],
|
|
|
|
|
'mode' => $data['mode']
|
|
|
|
|
);
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort alphabetically
|
|
|
|
|
usort($new_grids, function($a, $b) {
|
|
|
|
|
return strcmp($a['grid'], $b['grid']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return $new_grids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a grid is new (never worked before)
|
|
|
|
|
*/
|
|
|
|
|
private function is_grid_new($locations, $grid, $start_date) {
|
2025-11-14 17:13:11 +00:00
|
|
|
// Use pre-built cache for instant lookup
|
|
|
|
|
return !isset($this->historical_grids[strtoupper($grid)]);
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a grid is new for a specific propagation mode
|
|
|
|
|
*/
|
|
|
|
|
private function is_grid_new_for_prop($locations, $grid, $start_date, $prop_mode) {
|
2025-11-14 17:13:11 +00:00
|
|
|
// Use pre-built cache for instant lookup
|
|
|
|
|
return !isset($this->historical_grids_by_prop[$prop_mode][strtoupper($grid)]);
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a grid is new for HF (non-SAT, non-EME)
|
|
|
|
|
*/
|
|
|
|
|
private function is_grid_new_for_hf($locations, $grid, $start_date) {
|
2025-11-14 17:13:11 +00:00
|
|
|
// Use pre-built cache for instant lookup
|
|
|
|
|
return !isset($this->historical_grids_hf[strtoupper($grid)]);
|
2025-11-14 16:39:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all countries worked this month
|
|
|
|
|
*/
|
|
|
|
|
private function get_countries_worked($locations, $start_date, $end_date) {
|
|
|
|
|
$countries = array();
|
|
|
|
|
|
2025-11-14 16:46:58 +00:00
|
|
|
$this->db->select('COL_DXCC, MAX(COL_COUNTRY) as COL_COUNTRY', FALSE);
|
2025-11-14 16:39:56 +00:00
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_DXCC IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_DXCC !=', '');
|
2025-11-14 16:46:58 +00:00
|
|
|
$this->db->group_by('COL_DXCC');
|
2025-11-14 16:39:56 +00:00
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$countries[] = array(
|
|
|
|
|
'dxcc_id' => $row->COL_DXCC,
|
|
|
|
|
'name' => $row->COL_COUNTRY
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $countries;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get modes breakdown with QSO counts
|
|
|
|
|
*/
|
|
|
|
|
private function get_modes_breakdown($locations, $start_date, $end_date) {
|
|
|
|
|
$modes = array();
|
|
|
|
|
|
|
|
|
|
// Use COALESCE to prefer submode over mode
|
|
|
|
|
$this->db->select('COALESCE(NULLIF(COL_SUBMODE, ""), COL_MODE) as mode_used, COUNT(*) as count', FALSE);
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->group_by('mode_used');
|
|
|
|
|
$this->db->order_by('count', 'DESC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$mode = $row->mode_used ? $row->mode_used : 'Unknown';
|
|
|
|
|
$modes[$mode] = (int)$row->count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $modes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get bands breakdown with QSO counts
|
|
|
|
|
*/
|
|
|
|
|
private function get_bands_breakdown($locations, $start_date, $end_date) {
|
|
|
|
|
$bands = array();
|
|
|
|
|
|
|
|
|
|
$this->db->select('COL_BAND, COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->group_by('COL_BAND');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$band = $row->COL_BAND ? $row->COL_BAND : 'Unknown';
|
|
|
|
|
$bands[$band] = (int)$row->count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort bands in proper amateur radio order
|
|
|
|
|
$bands = $this->sort_bands_array($bands);
|
|
|
|
|
|
|
|
|
|
return $bands;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get continents breakdown with QSO counts
|
|
|
|
|
*/
|
|
|
|
|
private function get_continents_breakdown($locations, $start_date, $end_date) {
|
|
|
|
|
$continents = array();
|
|
|
|
|
|
|
|
|
|
$this->db->select('COL_CONT, COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_CONT IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_CONT !=', '');
|
|
|
|
|
$this->db->group_by('COL_CONT');
|
|
|
|
|
$this->db->order_by('count', 'DESC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$continents[$row->COL_CONT] = (int)$row->count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $continents;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get satellite QSO count (via SAT propagation mode)
|
|
|
|
|
*/
|
|
|
|
|
private function get_satellite_qsos($locations, $start_date, $end_date) {
|
|
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', 'SAT');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
$row = $query->row();
|
|
|
|
|
return $row ? (int)$row->count : 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 17:33:08 +00:00
|
|
|
/**
|
|
|
|
|
* Get breakdown of QSOs per satellite
|
|
|
|
|
*/
|
|
|
|
|
private function get_satellite_breakdown($locations, $start_date, $end_date) {
|
|
|
|
|
$satellites = array();
|
|
|
|
|
|
|
|
|
|
$this->db->select('COL_SAT_NAME, COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', 'SAT');
|
|
|
|
|
$this->db->where('COL_SAT_NAME IS NOT NULL');
|
|
|
|
|
$this->db->where('COL_SAT_NAME !=', '');
|
|
|
|
|
$this->db->group_by('COL_SAT_NAME');
|
|
|
|
|
$this->db->order_by('count', 'DESC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$satellites[$row->COL_SAT_NAME] = (int)$row->count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $satellites;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 16:39:56 +00:00
|
|
|
/**
|
|
|
|
|
* Get EME (moonbounce) QSO count
|
|
|
|
|
*/
|
|
|
|
|
private function get_eme_qsos($locations, $start_date, $end_date) {
|
|
|
|
|
$this->db->select('COUNT(*) as count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->where('COL_TIME_ON >=', $start_date);
|
|
|
|
|
$this->db->where('COL_TIME_ON <=', $end_date);
|
|
|
|
|
$this->db->where('COL_PROP_MODE', 'EME');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
$row = $query->row();
|
|
|
|
|
return $row ? (int)$row->count : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract prefix from callsign
|
|
|
|
|
*/
|
|
|
|
|
private function extract_prefix($callsign) {
|
|
|
|
|
// Simple prefix extraction - take everything before first digit
|
|
|
|
|
if (preg_match('/^([A-Z0-9]+?)\d/', $callsign, $matches)) {
|
|
|
|
|
return $matches[1];
|
|
|
|
|
}
|
|
|
|
|
return $callsign;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get list of months with QSO activity
|
|
|
|
|
* Useful for populating month/year selectors
|
|
|
|
|
*/
|
|
|
|
|
public function get_active_months($locations) {
|
|
|
|
|
$this->db->select('YEAR(COL_TIME_ON) as year, MONTH(COL_TIME_ON) as month, COUNT(*) as qso_count');
|
|
|
|
|
$this->db->from($this->config->item('table_name'));
|
|
|
|
|
$this->db->where_in('station_id', $locations);
|
|
|
|
|
$this->db->group_by('YEAR(COL_TIME_ON), MONTH(COL_TIME_ON)');
|
|
|
|
|
$this->db->order_by('year DESC, month DESC');
|
|
|
|
|
|
|
|
|
|
$query = $this->db->get();
|
|
|
|
|
|
|
|
|
|
$months = array();
|
|
|
|
|
foreach ($query->result() as $row) {
|
|
|
|
|
$months[] = array(
|
|
|
|
|
'year' => $row->year,
|
|
|
|
|
'month' => $row->month,
|
|
|
|
|
'qso_count' => $row->qso_count
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $months;
|
|
|
|
|
}
|
|
|
|
|
}
|